Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 9 additions & 27 deletions deployments/function-pods/README.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this file need to exist? i understand the deployment.yaml example in the same dir but this is mainly just a readme of how the feature works + pointing to the docs anyway. cant this just be in the docs alone?

Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
### Function Pod Template
# Function pod templates

In order to leverage custom manifests for Pod and frontend Service of Function Pods created by Function Runner, the following additional Kubernetes Resource Manifests (KRM) need to be provisioned in the Porch environment
The function-runner builds evaluator pods from a `PodTemplate` named `base-pod-template` and a `ServiceTemplate` named `base-service-template` in `porch-fn-system`.
The default Porch install already deploys those objects (see `deployments/porch/22-function-templates.yaml`).
There is no `--function-pod-template` flag and no ConfigMap template.

* A ConfigMap containing 2 data elements: a) a KRM of type Pod under the `template` key and b) a KRM of type Service under `serviceTemplate` key
* A Kubernetes Role providing read access to resource type ConfigMap in the porch-system namespace
* A Kubernetes RoleBinding, binding to the above listed Role to the ServiceAccount (porch-fn-runner) used by Function Runner Pod

All of the above KRMs are predefined in `deployment.yaml` file present in this folder.

### How to enable Function Pod Template use by Function Runner

* Apply the [deployment.yaml manifest](deployment.yaml) from this directory
To customize every function pod, edit the live objects or apply a replacement such as [deployment.yaml](deployment.yaml) from this directory:

```
kubectl apply -f deployment.yaml
```

* Add an additional argument `--function-pod-template` in command section of function-runner deployment instructing it to use the Function Pod Template ConfigMap, as shown below

```
kubectl edit deployment -n porch-system function-runner
```

```
command:
- /server
- --config=/config.yaml
- --functions=/functions
- --pod-namespace=porch-fn-system
- --function-pod-template=kpt-function-eval-pod-template
- --max-request-body-size=6291456 # Keep this in sync with porch-server's corresponding argument
```
Per-function CPU, memory, env, or service account belongs on FunctionConfig `spec.podExecutor.templateOverrides` instead.
Details are in the [Pod Templates](../../docs/content/en/docs/6_configuration_and_deployments/configurations/components/function-runner-config/pod-templates.md) documentation.

After the function-runner Pods restart, they will start using the Pod and Service templates from ConfigMap.
Existing function pods keep the previous template until they are reused or garbage-collected.
After a template change, the next evaluation for that image creates a replacement pod.
133 changes: 70 additions & 63 deletions deployments/function-pods/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,73 +11,80 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Sample replacement for the function-runner base templates. Applying this
# overwrites base-pod-template and base-service-template in porch-fn-system.
# The function-runner looks these up by name; no extra CLI flags are required.
---
apiVersion: v1
kind: ConfigMap
kind: PodTemplate
metadata:
name: kpt-function-eval-pod-template
namespace: porch-system
data:
template: |
apiVersion: v1
kind: Pod
name: base-pod-template
namespace: porch-fn-system
template:
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: true
spec:
initContainers:
- name: copy-wrapper-server
image: ghcr.io/kptdev/porch-wrapper-server:latest
command:
- cp
- -a
- /home/nonroot/wrapper-server/.
- /wrapper-server-tools
volumeMounts:
- name: wrapper-server-tools
mountPath: /wrapper-server-tools
containers:
- name: function
image: image-replaced-by-kpt-func-image
command:
- /wrapper-server-tools/wrapper-server
volumeMounts:
- name: wrapper-server-tools
mountPath: /wrapper-server-tools
volumes:
- name: wrapper-server-tools
emptyDir: {}
serviceTemplate: |
apiVersion: v1
kind: Service
spec:
ports:
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
spec:
initContainers:
- name: copy-wrapper-server
image: ghcr.io/kptdev/porch-wrapper-server:latest
command:
- cp
- -a
- /home/nonroot/wrapper-server/.
- /wrapper-server-tools
volumeMounts:
- name: wrapper-server-tools
mountPath: /wrapper-server-tools
imagePullPolicy: IfNotPresent
containers:
- name: function
image: image-replaced-by-kpt-func-image
command:
- /wrapper-server-tools/wrapper-server
env:
- name: OTEL_METRICS_EXPORTER
value: prometheus
- name: OTEL_TRACES_EXPORTER
value: none
- name: OTEL_EXPORTER_PROMETHEUS_HOST
value: 0.0.0.0
- name: OTEL_EXPORTER_PROMETHEUS_PORT
value: "9464"
ports:
- containerPort: 9464
name: metrics
readinessProbe:
exec:
command: [ "/wrapper-server-tools/grpc-health-probe", "-addr", "localhost:9446" ]
livenessProbe:
exec:
command: [ "/wrapper-server-tools/grpc-health-probe", "-addr", "localhost:9446" ]
volumeMounts:
- name: wrapper-server-tools
mountPath: /wrapper-server-tools
imagePullPolicy: IfNotPresent
volumes:
- name: wrapper-server-tools
emptyDir: {}
---
apiVersion: config.porch.kpt.dev/v1alpha1
kind: ServiceTemplate
metadata:
name: base-service-template
namespace: porch-fn-system
template:
spec:
ports:
- port: 9446
protocol: TCP
targetPort: 9446
selector:
fn.kpt.dev/image: to-be-replaced
type: ClusterIP
---
# Need to lookup and access Configmap containing Function Pod Template
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: porch-fn-runner
namespace: porch-system
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: porch-fn-runner
namespace: porch-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: porch-fn-runner
subjects:
- kind: ServiceAccount
name: porch-fn-runner
name: server
- port: 9464
protocol: TCP
targetPort: 9464
name: metrics
selector:
fn.kpt.dev/image: to-be-replaced
type: ClusterIP
55 changes: 52 additions & 3 deletions docs/content/en/docs/2_concepts/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: |
## What are Functions in Porch?

**Functions** in Porch are [KRM (Kubernetes Resource Model) functions](https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md) -
containerized programs that transform or validate Kubernetes resource manifests within a package's files. Functions are
programs (usually containerized) that transform or validate Kubernetes resource manifests within a package's files. Functions are
declared in a package's Kptfile and executed by Porch when rendering the package.

Functions enable:
Expand All @@ -20,9 +20,57 @@ Functions enable:

For details on how to declare and configure functions in the Kptfile pipeline, see the [kpt functions documentation](https://kpt.dev/book/04-using-functions/).

## Function Configuration

Porch uses **FunctionConfig** custom resources to choose an executor for each function image and to supply executor-specific settings.
A FunctionConfig names the function image and optional registry prefixes, then attaches a pod executor, a binary executor, a Go executor, or any combination of the three.
Tags on each executor select which image versions use that path.

The default Porch install deploys FunctionConfig objects for common catalog functions into `porch-fn-system`.
porch-server, function-runner, and porch-controllers each run an embedded reconciler that copies those objects into an in-memory store used at evaluation time.

```yaml
apiVersion: config.porch.kpt.dev/v1alpha1
kind: FunctionConfig
metadata:
name: set-namespace
namespace: porch-fn-system
spec:
image: set-namespace
prefixes:
- ""
- ghcr.io/kptdev/krm-functions-catalog
podExecutor:
tags:
- v0.4.1
timeToLive: 30m
binaryExecutor:
tags:
- v0.4.2
path: set-namespace
goExecutor:
id: set-namespace
tags:
- v0.4
- v0.4.5
```

The spec, status, and matching rules are documented in [Function Configuration]({{% relref "/docs/6_configuration_and_deployments/configurations/components/function-runner-config/function-configuration.md" %}}).

## Function Execution in Porch

Porch executes functions through a **function runner** component that calls kpt to orchestrate containerized function execution. The functions run in isolated containers (Kubernetes pods managed by the `function-runner` microservice). Porch passes the package's resources to kpt, which passes the resources on as a [ResourceList](https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md#resourcelist) to each function in the pipeline in turn. kpt executes the functions sequentially in the order declared in the Kptfile pipeline and passes the function results back to Porch, which stores them in the PackageRevisionResources's `status.renderStatus` field. Execution is triggered automatically following creation or clone of a package revision, update of a package revision, and when a package revision is proposed.
Porch executes functions through the Engine's function runtime.
The builtin runtime (in porch-server and porch-controllers) handles images listed on a FunctionConfig `goExecutor`.
Everything else is sent over gRPC to the **function-runner**, which tries a local binary from `binaryExecutor` first and falls back to a Kubernetes pod from `podExecutor`.

The **pod executor** is the default path for arbitrary function images: the function-runner creates (or reuses) a pod, injects a wrapper gRPC server, and runs the function image in isolation.
Time to Live (TTL), parallelism, and pod-spec overrides come from the matching FunctionConfig.
The **binary executor** runs a pre-built binary inside the function-runner process, which avoids pod startup cost.
The **Go executor** calls a compiled-in `ResourceListProcessor` (today: apply-replacements, set-namespace, and starlark) with no extra process at all.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the "today" needed?


Regardless of executor, Porch passes the package's resources to [kpt](https://kpt.dev), which passes them on as a [ResourceList](https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md#resourcelist) to each function in the pipeline in order.
kpt runs the functions sequentially and returns the results to Porch, which stores them in the PackageRevisionResources `status.renderStatus` field.
Rendering is triggered automatically after creating or cloning a package revision, after updating a package revision, and when a package revision is proposed.
Comment on lines +62 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my opinion this is a bit too brutal level of complexity for the concepts sections. this is meant to be for high level detail. its great detail mind you but id recommend a bit higher level here and bring this detail into the later sections of the docs.


## When Functions Execute

Expand Down Expand Up @@ -77,7 +125,8 @@ enabling iterative development on incomplete packages.
## Key Points

- Functions are standard KRM functions declared in the Kptfile pipeline (see [kpt functions docs](https://kpt.dev/book/04-using-functions/))
- Porch invokes kpt to execute functions via a function-runner component using containerized execution
- Function execution is configured with FunctionConfig custom resources that select a pod, binary, or Go executor per image tag
- porch-server, function-runner, and porch-controllers each reconcile FunctionConfig objects into an in-memory store used at evaluation time
- Functions automatically execute during package rendering on Draft package revisions
- Function results are stored in `status.renderStatus` of the PackageRevisionResources view of a package revision
- Published packages are immutable - functions don't re-execute after publication
Expand Down
16 changes: 16 additions & 0 deletions docs/content/en/docs/3_getting_started/installing-porch.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,31 @@ kubectl api-resources | grep porch
You should see Porch API resources:

```bash
functionconfigs config.porch.kpt.dev/v1alpha1 true FunctionConfig
packagerevs config.porch.kpt.dev/v1alpha1 true PackageRev
packagevariants config.porch.kpt.dev/v1alpha1 true PackageVariant
packagevariantsets config.porch.kpt.dev/v1alpha2 true PackageVariantSet
repositories config.porch.kpt.dev/v1alpha1 true Repository
servicetemplates config.porch.kpt.dev/v1alpha1 true ServiceTemplate
packagerevisionresources porch.kpt.dev/v1alpha1 true PackageRevisionResources
packagerevisions porch.kpt.dev/v1alpha1 true PackageRevision
packages porch.kpt.dev/v1alpha1 true PorchPackage
```

Verify that FunctionConfig resources were deployed into the function-pod namespace (default `porch-fn-system`, configured on the function-runner with `--pod-namespace`).

```bash
kubectl get functionconfigs -n porch-fn-system
```

A healthy install shows one FunctionConfig per bundled catalog function (apply-replacements, set-namespace, starlark, kubeconform, and others).
The `Server Applied`, `FnRunner Applied`, and `Controller Applied` columns are the generations each component has loaded. They should match the resource generation when the spec is in sync.

These FunctionConfig objects replace the older static config-file / ConfigMap approach.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would a reader here know what the old approach was? i think this might just confuse people about what the old approach even is. i think we have versioned docs now so if someone wanted to see what the older approach was they can just go back.

They tell porch-server, function-runner, and porch-controllers which executor (pod, binary, or Go) to use for each function image.
See [Function Configuration]({{% relref "/docs/6_configuration_and_deployments/configurations/components/function-runner-config/function-configuration.md" %}}) for the spec
and [Pod Templates]({{% relref "/docs/6_configuration_and_deployments/configurations/components/function-runner-config/pod-templates.md" %}}) for the `PodTemplate` and `ServiceTemplate` used by the pod executor.

## Troubleshooting

### Pods not starting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,14 @@ the draft. The Task handler has no direct repository access.

### Function Runtime Integration

The task handler uses function runtimes configured in the engine:
The task handler uses function runtimes configured in the engine.
Which runtime handles a given image is driven by FunctionConfig (see [Function Configuration]({{% relref "/docs/6_configuration_and_deployments/configurations/components/function-runner-config/function-configuration.md" %}})):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FunctionConfig decides which runtimes handles a given image (?)


- **Builtin Runtime**: For built-in functions (set-namespace, etc.)
- **gRPC Runtime**: For external function runner service
- **Multi-Runtime**: Chains multiple runtimes together
The **builtin runtime** runs compiled-in Go processors (`apply-replacements`, `set-namespace`, `starlark`) for tags listed on `goExecutor`.

The **gRPC runtime** calls the function-runner for everything else (binary fast path, then pod).

The **multi-runtime** tries builtin first and falls back to gRPC.
Comment on lines +157 to +161

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think these were fine as bullet points but its fine either way really


The engine configures these runtimes during initialization and passes them to the task handler.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Choosing between evaluators depends on deployment requirements and function char

**Considerations:**
- Requires pre-cached function binaries
- Limited to functions in configuration file
- Limited to functions listed on a FunctionConfig `binaryExecutor`
- No container isolation (functions run in runner process)
- Manual configuration and binary management needed

Expand Down Expand Up @@ -194,14 +194,14 @@ Function Runner deploys with **pod evaluator by default** when no evaluators are
**Configuration method:**
- Use `--disable-runtimes` flag to disable specific evaluators
- Pod evaluator: No additional configuration needed
- Executable evaluator: Requires configuration file mapping images to binaries
- Executable evaluator: Requires FunctionConfig resources with `binaryExecutor` (and binaries under `--functions`)
- Multi-evaluator: Automatically used when multiple evaluators enabled

### Migration Considerations

Switching between evaluator configurations requires:
- Function Runner restart with new flags
- Executable evaluator requires configuration file with image-to-binary mappings
- Executable evaluator requires FunctionConfig `binaryExecutor` entries and binaries under `--functions`
- Pod evaluator requires Kubernetes cluster access and RBAC permissions
- No data migration needed (evaluators are stateless)

Expand All @@ -213,7 +213,7 @@ The evaluator implementations differ fundamentally in execution mechanism and pe
|--------|---------------|----------------------|
| **Execution Environment** | Kubernetes pods | Local processes |
| **Startup Latency** | Variable (pod creation, image pull, cluster speed) | Milliseconds (local process spawn) |
| **Function Discovery** | Dynamic (any image) | Static (configuration file) |
| **Function Discovery** | Dynamic (any image) | Static (FunctionConfig binaryExecutor) |
| **Isolation** | Container isolation | Process isolation |
| **Resource Management** | Kubernetes limits/quotas | OS process limits |
| **Kubernetes API Load** | High (pod/service CRUD) | None |
Expand Down Expand Up @@ -357,20 +357,20 @@ For detailed explanations of how these differences affect operations, see the in

### Executable Evaluator Configuration

**Decision**: Use static configuration file mapping images to binary paths.
**Decision**: Use FunctionConfig custom resources to map images to binary paths, watched by an embedded reconciler.

**Rationale:**
- Simple and explicit configuration
- No dynamic discovery complexity
- Clear mapping between function images and binaries
- Easy to audit and validate
- Same CRD configures pod, binary, and Go executors
- Spec changes apply without restarting the function-runner
- Prefix and tag matching is shared with the builtin runtime
- Easy to audit with `kubectl get functionconfigs`

**Alternatives considered:**
- **Static YAML config file**: Required a process restart and drifted from pod-executor settings
- **Directory scanning**: Implicit mapping, harder to debug
- **Database storage**: Unnecessary complexity
- **Dynamic download**: Security and caching concerns

**Trade-offs:**
- Requires manual configuration updates
- No automatic discovery of new binaries
- Configuration must be kept in sync with available binaries
- Binaries still have to be present under `--functions` (or an absolute path)
- Duplicate `spec.image` values on different FunctionConfig objects are ignored
- Only images listed on `binaryExecutor` take the fast path, everything else falls back to pods
Loading
Loading