mirror of https://github.com/crossplane/docs.git
Merge pull request #298 from negz/barely-functional
Document the alpha Composition Functions feature
This commit is contained in:
commit
90cbe784f4
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,994 @@
|
||||||
|
---
|
||||||
|
title: Composition Functions
|
||||||
|
state: alpha
|
||||||
|
---
|
||||||
|
|
||||||
|
Composition Functions allow you to supplement or replace your Compositions with
|
||||||
|
advanced logic. You can build a Function using general purpose programming
|
||||||
|
languages such as Go or Python, or relevant tools such as Helm, kustomize, or
|
||||||
|
CUE. Functions compliment contemporary "Patch and Transform" (P&T) style
|
||||||
|
Composition. It's possible to use only P&T, only Functions, or a mix of both in
|
||||||
|
the same Composition.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apiextensions.crossplane.io/v1
|
||||||
|
kind: Composition
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
compositeTypeRef:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
functions:
|
||||||
|
- name: my-cool-Function
|
||||||
|
type: Container
|
||||||
|
container:
|
||||||
|
image: xpkg.upbound.io/my-cool-Function:0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
A Composition Function is a short-lived OCI container that tells Crossplane how
|
||||||
|
to reconcile a Composite Resource (XR). The preceding example shows a minimal
|
||||||
|
`Composition` that uses a Composition Function. Note that it has a `functions`
|
||||||
|
array rather than the typical P&T style array of `resources`.
|
||||||
|
|
||||||
|
## Enabling functions
|
||||||
|
|
||||||
|
To enable support for Composition Functions you must:
|
||||||
|
|
||||||
|
* Enable the alpha feature flag in Crossplane.
|
||||||
|
* Deploy a runner that's responsible for running Functions.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl create namespace crossplane-system
|
||||||
|
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane \
|
||||||
|
--set "args={--debug,--enable-composition-functions}" \
|
||||||
|
--set "xfn.enabled=true" \
|
||||||
|
--set "xfn.args={--debug}"
|
||||||
|
```
|
||||||
|
|
||||||
|
The preceding Helm command installs Crossplane with the Composition Functions
|
||||||
|
feature flag enabled, and with the reference _xfn_ Composition Function runner
|
||||||
|
deployed as a sidecar pod. Confirm Composition Functions were enabled by looking
|
||||||
|
for a log line:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ kubectl -n crossplane-system logs -l app=crossplane
|
||||||
|
{"level":"info","ts":1674535093.36186,"logger":"crossplane","msg":"Alpha feature enabled","flag":"EnableAlphaCompositionFunctions"}
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the log line emitted shortly after Crossplane starts.
|
||||||
|
|
||||||
|
|
||||||
|
## Using functions
|
||||||
|
|
||||||
|
To use Composition Functions you must:
|
||||||
|
|
||||||
|
1. Find one or more Composition Functions, or write your own.
|
||||||
|
2. Create a `Composition` that uses your Functions.
|
||||||
|
3. Create an XR that uses your `Composition`.
|
||||||
|
|
||||||
|
Your XRs, claims, and providers don't need to be updated or otherwise aware
|
||||||
|
of Composition Functions to use them. They need only use a `Composition` that
|
||||||
|
includes one or more entries in its `spec.functions` array.
|
||||||
|
|
||||||
|
Composition Functions are designed to be run in a pipeline, so you can 'stack'
|
||||||
|
several of them together. Each Function is passed the output of the previous
|
||||||
|
Function as its input. Functions can also be used in conjunction with P&T
|
||||||
|
Composition (a `spec.resources` array).
|
||||||
|
|
||||||
|
In the following example P&T Composition composes an RDS instance. A pipeline of
|
||||||
|
(hypothetical) Composition Functions then mutates the desired RDS instance by
|
||||||
|
adding a randomly generated password, and composes an RDS security group.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apiextensions.crossplane.io/v1
|
||||||
|
kind: Composition
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
compositeTypeRef:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
resources:
|
||||||
|
- name: rds-instance
|
||||||
|
base:
|
||||||
|
apiVersion: rds.aws.upbound.io/v1beta1
|
||||||
|
kind: Instance
|
||||||
|
spec:
|
||||||
|
forProvider:
|
||||||
|
dbName: exmaple
|
||||||
|
instanceClass: db.t3.micro
|
||||||
|
region: us-west-2
|
||||||
|
skipFinalSnapshot: true
|
||||||
|
username: exampleuser
|
||||||
|
engine: postgres
|
||||||
|
engineVersion: "12"
|
||||||
|
patches:
|
||||||
|
- fromFieldPath: spec.parameters.storageGB
|
||||||
|
toFieldPath: spec.forProvider.allocatedStorage
|
||||||
|
connectionDetails:
|
||||||
|
- type: FromFieldPath
|
||||||
|
name: username
|
||||||
|
fromFieldPath: spec.forProvider.username
|
||||||
|
- type: FromConnectionSecretKey
|
||||||
|
name: password
|
||||||
|
fromConnectionSecretKey: attribute.password
|
||||||
|
functions:
|
||||||
|
- name: rds-instance-password
|
||||||
|
type: Container
|
||||||
|
container:
|
||||||
|
image: xpkg.upbound.io/provider-aws-xfns/random-rds-password:v0.1.0
|
||||||
|
- name: compose-dbsecuritygroup
|
||||||
|
type: Container
|
||||||
|
container:
|
||||||
|
image: xpkg.upbound.io/example-org/compose-rds-securitygroup:v0.9.0
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Use `kubectl explain` to explore the configuration options available when using
|
||||||
|
Composition Functions, or take a look at the following example.
|
||||||
|
|
||||||
|
{{< expand "View Composition Function configuration options" >}}
|
||||||
|
```shell
|
||||||
|
$ kubectl explain composition.spec.functions
|
||||||
|
KIND: Composition
|
||||||
|
VERSION: apiextensions.crossplane.io/v1
|
||||||
|
|
||||||
|
RESOURCE: Functions <[]Object>
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
Functions is list of Composition Functions that will be used when a
|
||||||
|
composite resource referring to this composition is created. At least one
|
||||||
|
of resources and Functions must be specified. If both are specified the
|
||||||
|
resources will be rendered first, then passed to the Functions for further
|
||||||
|
processing. THIS IS AN ALPHA FIELD. Do not use it in production. It is not
|
||||||
|
honored unless the relevant Crossplane feature flag is enabled, and may be
|
||||||
|
changed or removed without notice.
|
||||||
|
|
||||||
|
A Function represents a Composition Function.
|
||||||
|
|
||||||
|
FIELDS:
|
||||||
|
config <>
|
||||||
|
Config is an optional, arbitrary Kubernetes resource (i.e. a resource with
|
||||||
|
an apiVersion and kind) that will be passed to the Composition Function as
|
||||||
|
the 'config' block of its FunctionIO.
|
||||||
|
|
||||||
|
container <Object>
|
||||||
|
Container configuration of this Function.
|
||||||
|
|
||||||
|
name <string> -required-
|
||||||
|
Name of this Function. Must be unique within its Composition.
|
||||||
|
|
||||||
|
type <string> -required-
|
||||||
|
Type of this Function.
|
||||||
|
```
|
||||||
|
{{< /expand >}}
|
||||||
|
|
||||||
|
{{< expand "An example of most Composition Function configuration options" >}}
|
||||||
|
```yaml
|
||||||
|
apiVersion: apiextensions.crossplane.io/v1
|
||||||
|
kind: Composition
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
compositeTypeRef:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
functions:
|
||||||
|
- name: my-cool-Function
|
||||||
|
# Currently only Container is supported. Other types may be added in future.
|
||||||
|
type: Container
|
||||||
|
# Configuration specific to type: Container.
|
||||||
|
container:
|
||||||
|
# The OCI image to pull and run.
|
||||||
|
image: xkpg.io/my-cool-Function:0.1.0
|
||||||
|
# Whether to pull the Function image Never, Always, or IfNotPresent.
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
# Note that only resource limits are supported - not requests.
|
||||||
|
# The Function will be run with the specified resource limits, specified
|
||||||
|
# in Kubernetes-style resource.Quantity form.
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
# Defaults to 128Mi
|
||||||
|
memory: 64Mi
|
||||||
|
# Defaults to 100m (a 10th of a core)
|
||||||
|
cpu: 250m
|
||||||
|
# Defaults to 'Isolated' - an isolated network namespace with no network
|
||||||
|
# access. Use 'Runner' to allow a Function access to the runner's (the xfn
|
||||||
|
# container's) network namespace.
|
||||||
|
network:
|
||||||
|
policy: Runner
|
||||||
|
# How long the Function may run before it's killed. Defaults to 20s.
|
||||||
|
# Keep in mind the Function pipeline is typically invoked once every
|
||||||
|
# 30 to 60 seconds - sometimes more frequently during error conditions.
|
||||||
|
timeout: 30s
|
||||||
|
# An arbitrary Kubernetes resource. Passed to the Function as the config
|
||||||
|
# block of its FunctionIO. Doesn't need to exist as a Custom Resource (CR),
|
||||||
|
# since this resource doesn't exist by itself in the API server but must be
|
||||||
|
# a valid Kubernetes resource (have an apiVersion and kind).
|
||||||
|
config:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: Config
|
||||||
|
metadata:
|
||||||
|
name: cloudsql
|
||||||
|
spec:
|
||||||
|
version: POSTGRES_9_6
|
||||||
|
```
|
||||||
|
{{< /expand >}}
|
||||||
|
|
||||||
|
Use `kubectl describe <xr-kind> <xr-name>` to debug Composition Functions. Look
|
||||||
|
for status conditions and events. Most Functions will emit events associated
|
||||||
|
with the XR if they experience issues.
|
||||||
|
|
||||||
|
## Building a function
|
||||||
|
|
||||||
|
Crossplane doesn't have opinions about how a Composition Function is
|
||||||
|
implemented. Functions must:
|
||||||
|
|
||||||
|
* Be packaged as an OCI image, where the `ENTRYPOINT` is the Function.
|
||||||
|
* Accept input in the form of a `FunctionIO` document on stdin.
|
||||||
|
* Return the `FunctionIO` they were passed, optionally mutated, on stdout.
|
||||||
|
* Run within the constraints specified by the Composition that includes them,
|
||||||
|
such as timeouts, compute, network access.
|
||||||
|
|
||||||
|
This means Functions may be written using a general purpose programming language
|
||||||
|
like Python, Go, or TypeScript. They may also be implemented using a shell
|
||||||
|
script, or an existing tool like Helm or Kustomize.
|
||||||
|
|
||||||
|
### FunctionIO
|
||||||
|
|
||||||
|
When a Composition Function runner like `xfn` runs your Function it will write
|
||||||
|
`FunctionIO` to its stdin. A `FunctionIO` is a Kubernetes style YAML manifest.
|
||||||
|
It's not a custom resource (it never gets created in the API server) but it
|
||||||
|
follows Kubernetes conventions.
|
||||||
|
|
||||||
|
A `FunctionIO` consists of:
|
||||||
|
|
||||||
|
* An optional, arbitrary `config` object.
|
||||||
|
* The `observed` state of the XR and any existing composed resources.
|
||||||
|
* The `desired` state of the XR and any composed resources.
|
||||||
|
* Optional `results` of the Function pipeline.
|
||||||
|
|
||||||
|
Here's a brief example of a `FunctionIO`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apiextensions.crossplane.io/v1alpha1
|
||||||
|
kind: FunctionIO
|
||||||
|
config:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: Config
|
||||||
|
metadata:
|
||||||
|
name: cloudsql
|
||||||
|
spec:
|
||||||
|
version: POSTGRES_9_6
|
||||||
|
observed:
|
||||||
|
composite:
|
||||||
|
resource:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
metadata:
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
connectionDetails:
|
||||||
|
- name: privateIP
|
||||||
|
value: 10.135.0.3
|
||||||
|
resources:
|
||||||
|
- name: db-instance
|
||||||
|
resource:
|
||||||
|
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: DatabaseInstance
|
||||||
|
metadata:
|
||||||
|
name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
connectionDetails:
|
||||||
|
- name: privateIP
|
||||||
|
value: 10.135.0.3
|
||||||
|
desired:
|
||||||
|
composite:
|
||||||
|
resource:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
metadata:
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
connectionDetails:
|
||||||
|
- name: privateIP
|
||||||
|
value: 10.135.0.3
|
||||||
|
resources:
|
||||||
|
- name: db-instance
|
||||||
|
resource:
|
||||||
|
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: DatabaseInstance
|
||||||
|
metadata:
|
||||||
|
name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
- name: db-user
|
||||||
|
resource:
|
||||||
|
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: User
|
||||||
|
metadata:
|
||||||
|
name: platform-ref-gcp-db-p9wrj-z8lpz
|
||||||
|
connectionDetails:
|
||||||
|
- name: password
|
||||||
|
type: FromValue
|
||||||
|
value: very-secret
|
||||||
|
readinessChecks:
|
||||||
|
- type: None
|
||||||
|
results:
|
||||||
|
- severity: Normal
|
||||||
|
message: "Successfully composed GCP SQL user"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `config` object is copied from the `Composition`. It will match what's
|
||||||
|
passed as your Function's `config` in the `Functions` array. It must be a valid
|
||||||
|
Kubernetes object - have an `apiVersion` and `kind`.
|
||||||
|
|
||||||
|
The `observed` state of the XR and any existing composed resources reflects the
|
||||||
|
observed state at the beginning of a reconcile, before any Composition happens.
|
||||||
|
Your Function will only see composite and composed resources that _actually
|
||||||
|
exist_ in the API server in the `observed` state. The `observed` state also
|
||||||
|
includes any observed connection details.
|
||||||
|
|
||||||
|
The `desired` state of the XR and composed resources is how your Function tells
|
||||||
|
Crossplane what it should do. Crossplane 'bootstraps' the initial desired state
|
||||||
|
passed to a Function pipeline with:
|
||||||
|
|
||||||
|
* A copy of the observed state of the XR.
|
||||||
|
* A copy of the observed state of any existing composed resources.
|
||||||
|
* Any new composed resources or modifications to observed resources produced
|
||||||
|
from the `resources` array.
|
||||||
|
|
||||||
|
When adding a new desired resource to the `desired.resources` array you don't
|
||||||
|
need to:
|
||||||
|
|
||||||
|
* Update the XR's resource references.
|
||||||
|
* Add any composition annotations like `crossplane.io/composite-resource-name`.
|
||||||
|
* Set the XR as a controller/owner reference of the desired resource.
|
||||||
|
|
||||||
|
Crossplane will take care of all of these for you. It won't do anything else,
|
||||||
|
including setting a sensible `metadata.name` for the new composed resource -
|
||||||
|
this is up to your Function.
|
||||||
|
|
||||||
|
Finally, the `results` array allows your Function to surface events and debug
|
||||||
|
logs on the XR. Results support the following severities:
|
||||||
|
|
||||||
|
* `Normal` emits a debug log and a `Normal` event associated with the XR.
|
||||||
|
* `Warning` emits a debug log and a `Warning` event associated with the XR.
|
||||||
|
* `Fatal` stops the Composition process before applying any changes.
|
||||||
|
|
||||||
|
When Crossplane encounters a `Fatal` result it will finish running the
|
||||||
|
Composition Function pipeline. Crossplane will then return an error without
|
||||||
|
applying any changes to the API server. Crossplane surfaces this error as a
|
||||||
|
`Warning` event, a debug log, and by setting the `Synced` status condition of
|
||||||
|
the XR to "False".
|
||||||
|
|
||||||
|
The preceding example is heavily edited for brevity. Expand the following
|
||||||
|
example for a more detailed, realistic, and commented example of a `FunctionIO`.
|
||||||
|
|
||||||
|
{{< expand "A more detailed example" >}}
|
||||||
|
In this example a `XPostgreSQLInstance` XR has one existing composed resource -
|
||||||
|
`db-instance`. The composition Function returns a `desired` object with one new
|
||||||
|
composed resource, a `db-user`, to tell Crossplane it should also create a
|
||||||
|
database user.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apiextensions.crossplane.io/v1alpha1
|
||||||
|
kind: FunctionIO
|
||||||
|
config:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: Config
|
||||||
|
metadata:
|
||||||
|
name: cloudsql
|
||||||
|
spec:
|
||||||
|
version: POSTGRES_9_6
|
||||||
|
observed:
|
||||||
|
# The observed state of the Composite Resource.
|
||||||
|
composite:
|
||||||
|
resource:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: "2023-01-27T23:47:12Z"
|
||||||
|
finalizers:
|
||||||
|
- composite.apiextensions.crossplane.io
|
||||||
|
generateName: platform-ref-gcp-db-
|
||||||
|
generation: 5
|
||||||
|
labels:
|
||||||
|
crossplane.io/claim-name: platform-ref-gcp-db
|
||||||
|
crossplane.io/claim-namespace: default
|
||||||
|
crossplane.io/composite: platform-ref-gcp-db-p9wrj
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
resourceVersion: "6817"
|
||||||
|
uid: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
spec:
|
||||||
|
claimRef:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: PostgreSQLInstance
|
||||||
|
name: platform-ref-gcp-db
|
||||||
|
namespace: default
|
||||||
|
compositionRef:
|
||||||
|
name: xpostgresqlinstances.database.example.org
|
||||||
|
compositionRevisionRef:
|
||||||
|
name: xpostgresqlinstances.database.example.org-eb6c684
|
||||||
|
compositionUpdatePolicy: Automatic
|
||||||
|
parameters:
|
||||||
|
storageGB: 10
|
||||||
|
resourceRefs:
|
||||||
|
- apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: DatabaseInstance
|
||||||
|
name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
writeConnectionSecretToRef:
|
||||||
|
name: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
namespace: upbound-system
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- lastTransitionTime: "2023-01-27T23:47:12Z"
|
||||||
|
reason: ReconcileSuccess
|
||||||
|
status: "True"
|
||||||
|
type: Synced
|
||||||
|
- lastTransitionTime: "2023-01-28T00:09:12Z"
|
||||||
|
reason: Creating
|
||||||
|
status: "False"
|
||||||
|
type: Ready
|
||||||
|
connectionDetails:
|
||||||
|
lastPublishedTime: "2023-01-28T00:08:12Z"
|
||||||
|
# Any observed Composite Resource connection details.
|
||||||
|
connectionDetails:
|
||||||
|
- name: privateIP
|
||||||
|
value: 10.135.0.3
|
||||||
|
# The observed state of any existing Composed Resources.
|
||||||
|
resources:
|
||||||
|
- name: db-instance
|
||||||
|
resource:
|
||||||
|
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: DatabaseInstance
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
crossplane.io/composition-resource-name: db-instance
|
||||||
|
crossplane.io/external-name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
creationTimestamp: "2023-01-27T23:47:12Z"
|
||||||
|
finalizers:
|
||||||
|
- finalizer.managedresource.crossplane.io
|
||||||
|
generateName: platform-ref-gcp-db-p9wrj-
|
||||||
|
generation: 80
|
||||||
|
labels:
|
||||||
|
crossplane.io/claim-name: platform-ref-gcp-db
|
||||||
|
crossplane.io/claim-namespace: default
|
||||||
|
crossplane.io/composite: platform-ref-gcp-db-p9wrj
|
||||||
|
name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: database.example.org/v1alpha1
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: true
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
uid: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
resourceVersion: "7992"
|
||||||
|
uid: 43919834-fdce-427e-85d9-d03eab9501f1
|
||||||
|
spec:
|
||||||
|
forProvider:
|
||||||
|
databaseVersion: POSTGRES_13
|
||||||
|
deletionProtection: false
|
||||||
|
project: example
|
||||||
|
region: us-west2
|
||||||
|
settings:
|
||||||
|
- diskSize: 10
|
||||||
|
ipConfiguration:
|
||||||
|
- privateNetwork: projects/example/global/networks/platform-ref-gcp-cluster
|
||||||
|
privateNetworkRef:
|
||||||
|
name: platform-ref-gcp-cluster
|
||||||
|
tier: db-f1-micro
|
||||||
|
providerConfigRef:
|
||||||
|
name: default
|
||||||
|
writeConnectionSecretToRef:
|
||||||
|
name: 96623f41-be2e-4eda-84d4-9668b48e284d-gcp-postgresql
|
||||||
|
namespace: upbound-system
|
||||||
|
status:
|
||||||
|
atProvider:
|
||||||
|
connectionName: example:us-west2:platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
firstIpAddress: 34.102.103.85
|
||||||
|
id: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
privateIpAddress: 10.135.0.3
|
||||||
|
publicIpAddress: 34.102.103.85
|
||||||
|
settings:
|
||||||
|
- version: 1
|
||||||
|
conditions:
|
||||||
|
- lastTransitionTime: "2023-01-28T00:07:30Z"
|
||||||
|
reason: Available
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
||||||
|
- lastTransitionTime: "2023-01-27T23:47:14Z"
|
||||||
|
reason: ReconcileSuccess
|
||||||
|
status: "True"
|
||||||
|
type: Synced
|
||||||
|
# Any observed composed resource connection details.
|
||||||
|
connectionDetails:
|
||||||
|
- name: privateIP
|
||||||
|
value: 10.135.0.3
|
||||||
|
desired:
|
||||||
|
# The observed state of the Composite Resource.
|
||||||
|
composite:
|
||||||
|
resource:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: "2023-01-27T23:47:12Z"
|
||||||
|
finalizers:
|
||||||
|
- composite.apiextensions.crossplane.io
|
||||||
|
generateName: platform-ref-gcp-db-
|
||||||
|
generation: 5
|
||||||
|
labels:
|
||||||
|
crossplane.io/claim-name: platform-ref-gcp-db
|
||||||
|
crossplane.io/claim-namespace: default
|
||||||
|
crossplane.io/composite: platform-ref-gcp-db-p9wrj
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
resourceVersion: "6817"
|
||||||
|
uid: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
spec:
|
||||||
|
claimRef:
|
||||||
|
e apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: PostgreSQLInstance
|
||||||
|
name: platform-ref-gcp-db
|
||||||
|
namespace: default
|
||||||
|
compositionRef:
|
||||||
|
name: xpostgresqlinstances.database.example.org
|
||||||
|
compositionRevisionRef:
|
||||||
|
name: xpostgresqlinstances.database.example.org-eb6c684
|
||||||
|
compositionUpdatePolicy: Automatic
|
||||||
|
parameters:
|
||||||
|
storageGB: 10
|
||||||
|
resourceRefs:
|
||||||
|
- apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: DatabaseInstance
|
||||||
|
name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
writeConnectionSecretToRef:
|
||||||
|
name: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
namespace: upbound-system
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- lastTransitionTime: "2023-01-27T23:47:12Z"
|
||||||
|
reason: ReconcileSuccess
|
||||||
|
status: "True"
|
||||||
|
type: Synced
|
||||||
|
- lastTransitionTime: "2023-01-28T00:09:12Z"
|
||||||
|
reason: Creating
|
||||||
|
status: "False"
|
||||||
|
type: Ready
|
||||||
|
connectionDetails:
|
||||||
|
lastPublishedTime: "2023-01-28T00:08:12Z"
|
||||||
|
# Any desired Composite Resource connection details. Your Composition
|
||||||
|
# Function can add new entries to this array and Crossplane will record them
|
||||||
|
# as the XR's connection details.
|
||||||
|
connectionDetails:
|
||||||
|
- name: privateIP
|
||||||
|
value: 10.135.0.3
|
||||||
|
# The desired composed resources.
|
||||||
|
resources:
|
||||||
|
# This db-instance matches the entry in observed. Functions must include any
|
||||||
|
# observed resources in their desired resources array. If you omit an observed
|
||||||
|
# resource from the desired resources array Crossplane will delete it.
|
||||||
|
# Crossplane will 'bootstrap' the desired state passed to the Function
|
||||||
|
# pipeline by copying all observed resources into the desired resources array.
|
||||||
|
- name: db-instance
|
||||||
|
resource:
|
||||||
|
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: DatabaseInstance
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
crossplane.io/composition-resource-name: DBInstance
|
||||||
|
crossplane.io/external-name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
creationTimestamp: "2023-01-27T23:47:12Z"
|
||||||
|
finalizers:
|
||||||
|
- finalizer.managedresource.crossplane.io
|
||||||
|
generateName: platform-ref-gcp-db-p9wrj-
|
||||||
|
generation: 80
|
||||||
|
labels:
|
||||||
|
crossplane.io/claim-name: platform-ref-gcp-db
|
||||||
|
crossplane.io/claim-namespace: default
|
||||||
|
crossplane.io/composite: platform-ref-gcp-db-p9wrj
|
||||||
|
name: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: database.example.org/v1alpha1
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: true
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
uid: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
resourceVersion: "7992"
|
||||||
|
uid: 43919834-fdce-427e-85d9-d03eab9501f1
|
||||||
|
spec:
|
||||||
|
forProvider:
|
||||||
|
databaseVersion: POSTGRES_13
|
||||||
|
deletionProtection: false
|
||||||
|
project: example
|
||||||
|
region: us-west2
|
||||||
|
settings:
|
||||||
|
- diskSize: 10
|
||||||
|
ipConfiguration:
|
||||||
|
- privateNetwork: projects/example/global/networks/platform-ref-gcp-cluster
|
||||||
|
privateNetworkRef:
|
||||||
|
name: platform-ref-gcp-cluster
|
||||||
|
tier: db-f1-micro
|
||||||
|
providerConfigRef:
|
||||||
|
name: default
|
||||||
|
writeConnectionSecretToRef:
|
||||||
|
name: 96623f41-be2e-4eda-84d4-9668b48e284d-gcp-postgresql
|
||||||
|
namespace: upbound-system
|
||||||
|
status:
|
||||||
|
atProvider:
|
||||||
|
connectionName: example:us-west2:platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
firstIpAddress: 34.102.103.85
|
||||||
|
id: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
privateIpAddress: 10.135.0.3
|
||||||
|
publicIpAddress: 34.102.103.85
|
||||||
|
settings:
|
||||||
|
- version: 1
|
||||||
|
conditions:
|
||||||
|
- lastTransitionTime: "2023-01-28T00:07:30Z"
|
||||||
|
reason: Available
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
||||||
|
- lastTransitionTime: "2023-01-27T23:47:14Z"
|
||||||
|
reason: ReconcileSuccess
|
||||||
|
status: "True"
|
||||||
|
type: Synced
|
||||||
|
# This db-user is a desired composed resource that doesn't yet exist. This
|
||||||
|
# Composition Function is requesting it be created.
|
||||||
|
- name: db-user
|
||||||
|
resource:
|
||||||
|
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||||
|
kind: User
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
crossplane.io/composition-resource-name: db-user
|
||||||
|
crossplane.io/external-name: platform-ref-gcp-db-p9wrj-z8lpz
|
||||||
|
creationTimestamp: "2023-01-27T23:47:12Z"
|
||||||
|
finalizers:
|
||||||
|
- finalizer.managedresource.crossplane.io
|
||||||
|
generateName: platform-ref-gcp-db-p9wrj-
|
||||||
|
generation: 115
|
||||||
|
labels:
|
||||||
|
crossplane.io/claim-name: platform-ref-gcp-db
|
||||||
|
crossplane.io/claim-namespace: default
|
||||||
|
crossplane.io/composite: platform-ref-gcp-db-p9wrj
|
||||||
|
name: platform-ref-gcp-db-p9wrj-z8lpz
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: database.example.org/v1alpha1
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: true
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
name: platform-ref-gcp-db-p9wrj
|
||||||
|
uid: 96623f41-be2e-4eda-84d4-9668b48e284d
|
||||||
|
resourceVersion: "9951"
|
||||||
|
uid: ab5dafbe-2bc8-47ea-8b5b-9bcb40183e45
|
||||||
|
spec:
|
||||||
|
forProvider:
|
||||||
|
instance: platform-ref-gcp-db-p9wrj-tvvtg
|
||||||
|
project: example
|
||||||
|
providerConfigRef:
|
||||||
|
name: default
|
||||||
|
# Any desired connection details for the new db-user composed resource.
|
||||||
|
# Desired connection details can be FromValue, FromFieldPath, or
|
||||||
|
# FromConnectionSecretKey, just like their P&T Composition equivalents.
|
||||||
|
connectionDetails:
|
||||||
|
- name: password
|
||||||
|
type: FromValue
|
||||||
|
value: very-secret
|
||||||
|
# Any desired readiness checks for the new db-user composed resource.
|
||||||
|
# Desired readiness checks can be NonEmpty, MatchString, MatchInteger, or
|
||||||
|
# None, just like their P&T Composition equivalents.
|
||||||
|
readinessChecks:
|
||||||
|
- type: None
|
||||||
|
# An optional array of results.
|
||||||
|
results:
|
||||||
|
- severity: Normal
|
||||||
|
message: "Successfully composed GCP SQL user"
|
||||||
|
```
|
||||||
|
{{< /expand >}}
|
||||||
|
|
||||||
|
### An example Function
|
||||||
|
|
||||||
|
You can write a Composition Function using any programming language that can be
|
||||||
|
containerized, or existing tools like Helm or Kustomize.
|
||||||
|
|
||||||
|
Here's a Python Composition Function that doesn't create any new desired
|
||||||
|
resources, but instead annotates any existing desired resources with a quote.
|
||||||
|
Because this function accesses the internet it needs to be run with the `Runner`
|
||||||
|
network policy.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ANNOTATION_KEY_AUTHOR = "quotable.io/author"
|
||||||
|
ANNOTATION_KEY_QUOTE = "quotable.io/quote"
|
||||||
|
|
||||||
|
|
||||||
|
def get_quote() -> tuple[str, str]:
|
||||||
|
"""Get a quote from quotable.io"""
|
||||||
|
rsp = requests.get("https://api.quotable.io/random")
|
||||||
|
rsp.raise_for_status()
|
||||||
|
j = rsp.json()
|
||||||
|
return (j["author"], j["content"])
|
||||||
|
|
||||||
|
|
||||||
|
def read_Functionio() -> dict:
|
||||||
|
"""Read the FunctionIO from stdin."""
|
||||||
|
return yaml.load(sys.stdin.read(), yaml.Loader)
|
||||||
|
|
||||||
|
|
||||||
|
def write_Functionio(Functionio: dict):
|
||||||
|
"""Write the FunctionIO to stdout and exit."""
|
||||||
|
sys.stdout.write(yaml.dump(Functionio))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def result_warning(Functionio: dict, message: str):
|
||||||
|
"""Add a warning result to the supplied FunctionIO."""
|
||||||
|
if "results" not in Functionio:
|
||||||
|
Functionio["results"] = []
|
||||||
|
Functionio["results"].append({"severity": "Warning", "message": message})
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Annotate all desired composed resources with a quote from quotable.io"""
|
||||||
|
try:
|
||||||
|
Functionio = read_Functionio()
|
||||||
|
except yaml.parser.ParserError as err:
|
||||||
|
sys.stdout.write("cannot parse FunctionIO: {}\n".format(err))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Return early if there are no desired resources to annotate.
|
||||||
|
if "desired" not in Functionio or "resources" not in Functionio["desired"]:
|
||||||
|
write_Functionio(Functionio)
|
||||||
|
|
||||||
|
# If we can't get our quote, add a warning and return early.
|
||||||
|
try:
|
||||||
|
quote, author = get_quote()
|
||||||
|
except requests.exceptions.RequestException as err:
|
||||||
|
result_warning(Functionio, "Cannot get quote: {}".format(err))
|
||||||
|
write_Functionio(Functionio)
|
||||||
|
|
||||||
|
# Annotate all desired resources with our quote.
|
||||||
|
for r in Functionio["desired"]["resources"]:
|
||||||
|
if "resource" not in r:
|
||||||
|
# This shouldn't happen - add a warning and continue.
|
||||||
|
result_warning(
|
||||||
|
Functionio,
|
||||||
|
"Desired resource {name} missing resource body".format(
|
||||||
|
name=r.get("name", "unknown")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "metadata" not in r["resource"]:
|
||||||
|
r["resource"]["metadata"] = {}
|
||||||
|
|
||||||
|
if "annotations" not in r["resource"]["metadata"]:
|
||||||
|
r["resource"]["metadata"]["annotations"] = {}
|
||||||
|
|
||||||
|
if ANNOTATION_KEY_QUOTE in r["resource"]["metadata"]["annotations"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
r["resource"]["metadata"]["annotations"][ANNOTATION_KEY_AUTHOR] = author
|
||||||
|
r["resource"]["metadata"]["annotations"][ANNOTATION_KEY_QUOTE] = quote
|
||||||
|
|
||||||
|
write_Functionio(Functionio)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
Building this function requires its `requirements.txt` and a `Dockerfile`:
|
||||||
|
|
||||||
|
{{< expand "The Function's requirements" >}}
|
||||||
|
```python
|
||||||
|
certifi==2022.12.7
|
||||||
|
charset-normalizer==3.0.1
|
||||||
|
click==8.1.3
|
||||||
|
idna==3.4
|
||||||
|
pathspec==0.10.3
|
||||||
|
platformdirs==2.6.2
|
||||||
|
PyYAML==6.0
|
||||||
|
requests==2.28.2
|
||||||
|
tomli==2.0.1
|
||||||
|
urllib3==1.26.14
|
||||||
|
```
|
||||||
|
{{< /expand >}}
|
||||||
|
|
||||||
|
{{< expand "The Function's Dockerfile" >}}
|
||||||
|
```Dockerfile
|
||||||
|
FROM debian:11-slim AS build
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install --no-install-suggests --no-install-recommends --yes python3-venv && \
|
||||||
|
python3 -m venv /venv && \
|
||||||
|
/venv/bin/pip install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
|
FROM build AS build-venv
|
||||||
|
COPY requirements.txt /requirements.txt
|
||||||
|
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/python3-debian11
|
||||||
|
COPY --from=build-venv /venv /venv
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
ENTRYPOINT ["/venv/bin/python3", "main.py"]
|
||||||
|
```
|
||||||
|
{{< /expand >}}
|
||||||
|
|
||||||
|
Create and push the Function just like you would any Docker image:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Build the Function.
|
||||||
|
$ docker build .
|
||||||
|
Sending build context to Docker daemon 38.99MB
|
||||||
|
Step 1/10 : FROM debian:11-slim AS build
|
||||||
|
---> 4810399f6c13
|
||||||
|
Step 2/10 : RUN apt-get update && apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc && python3 -m venv /venv && /venv/bin/pip install --upgrade pip setuptools wheel
|
||||||
|
---> Using cache
|
||||||
|
---> 9b34960c88d7
|
||||||
|
Step 3/10 : FROM build AS build-venv
|
||||||
|
---> 9b34960c88d7
|
||||||
|
Step 4/10 : COPY requirements.txt /requirements.txt
|
||||||
|
---> Using cache
|
||||||
|
---> fae19dad52af
|
||||||
|
Step 5/10 : RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt
|
||||||
|
---> Using cache
|
||||||
|
---> f4b811c75812
|
||||||
|
Step 6/10 : FROM gcr.io/distroless/python3-debian11
|
||||||
|
---> 2a0e74a2b005
|
||||||
|
Step 7/10 : COPY --from=build-venv /venv /venv
|
||||||
|
---> Using cache
|
||||||
|
---> cf727d3f20d3
|
||||||
|
Step 8/10 : COPY . /app
|
||||||
|
---> a044aef45e32
|
||||||
|
Step 9/10 : WORKDIR /app
|
||||||
|
---> Running in d08a6144815b
|
||||||
|
Removing intermediate container d08a6144815b
|
||||||
|
---> 7250f5aa653e
|
||||||
|
Step 10/10 : ENTRYPOINT ["/venv/bin/python3", "main.py"]
|
||||||
|
---> Running in 3f4d9dc55bad
|
||||||
|
Removing intermediate container 3f4d9dc55bad
|
||||||
|
---> bfd2f920c591
|
||||||
|
Successfully built bfd2f920c591
|
||||||
|
|
||||||
|
# Tag the Function.
|
||||||
|
$ docker tag bfd2f920c591 example-org/xfn-quotable-simple:v0.1.0
|
||||||
|
|
||||||
|
# Push the Function.
|
||||||
|
$ docker push xpkg.upbound.io/example-org/xfn-quotable-simple:v0.1.0
|
||||||
|
The push refers to repository [xpkg.upbound.io/example-org/xfn-quotable-simple]
|
||||||
|
cf6d94b88843: Pushed
|
||||||
|
77646fd315d2: Mounted from example-org/xfn-quotable
|
||||||
|
50630ee42b6e: Mounted from example-org/xfn-quotable
|
||||||
|
7e2cf97ed8c4: Mounted from example-org/xfn-quotable
|
||||||
|
96e320b34b54: Mounted from example-org/xfn-quotable
|
||||||
|
fba4381f2bb7: Mounted from example-org/xfn-quotable
|
||||||
|
v0.1.0: digest: sha256:d8a6404e5fe38936aa8dadd861fea35ede0aded6168d501052f91cdabab0135e size: 1584
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now use this Function in your Composition. The following example will
|
||||||
|
create an `RDSInstance` using P&T Composition, then run the Function to annotate
|
||||||
|
it with a quote.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apiextensions.crossplane.io/v1
|
||||||
|
kind: Composition
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
compositeTypeRef:
|
||||||
|
apiVersion: database.example.org/v1alpha1
|
||||||
|
kind: XPostgreSQLInstance
|
||||||
|
resources:
|
||||||
|
- name: rds-instance
|
||||||
|
base:
|
||||||
|
apiVersion: rds.aws.upbound.io/v1beta1
|
||||||
|
kind: Instance
|
||||||
|
spec:
|
||||||
|
forProvider:
|
||||||
|
dbName: example
|
||||||
|
instanceClass: db.t3.micro
|
||||||
|
region: us-west-2
|
||||||
|
skipFinalSnapshot: true
|
||||||
|
username: exampleuser
|
||||||
|
engine: postgres
|
||||||
|
engineVersion: "12"
|
||||||
|
patches:
|
||||||
|
- fromFieldPath: spec.parameters.storageGB
|
||||||
|
toFieldPath: spec.forProvider.allocatedStorage
|
||||||
|
connectionDetails:
|
||||||
|
- type: FromFieldPath
|
||||||
|
name: username
|
||||||
|
fromFieldPath: spec.forProvider.username
|
||||||
|
- type: FromConnectionSecretKey
|
||||||
|
name: password
|
||||||
|
fromConnectionSecretKey: attribute.password
|
||||||
|
functions:
|
||||||
|
- name: quotable
|
||||||
|
type: Container
|
||||||
|
container:
|
||||||
|
image: xpkg.upbound.io/example-org/xfn-quotable-simple:v0.1.0
|
||||||
|
network:
|
||||||
|
policy: Runner
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tips for new functions
|
||||||
|
|
||||||
|
Here are some things to keep in mind when building a Composition Function:
|
||||||
|
|
||||||
|
* Your Function may be running as part of a pipeline. This means your Function
|
||||||
|
_must_ pass through any desired state that it's unconcerned with. If your
|
||||||
|
Function is passed a desired composed resource and doesn't return that
|
||||||
|
composed resource in its output, it will be deleted. Crossplane considers the
|
||||||
|
desired state of the XR and any composed resources to be whatever `FunctionIO`
|
||||||
|
is returned by the last Function in the pipeline.
|
||||||
|
* Crossplane won't set a `metadata.name` for your desired resources resources.
|
||||||
|
It's a good practice to match P&T Composition's behavior by setting
|
||||||
|
`metadata.generateName: "name-of-the-xr-"` for any new desired resources.
|
||||||
|
* Don't add new entries to the desired resources array every time your function
|
||||||
|
is invoked. Remember to check whether your desired resource is already in the
|
||||||
|
`observed` and/or `desired` objects. You may need to update it rather than
|
||||||
|
create it.
|
||||||
|
* Don't bypass providers. Composition Functions are designed to tell Crossplane
|
||||||
|
how to orchestrate managed resources - not to directly orchestrate external
|
||||||
|
systems.
|
||||||
|
* Include your function name and version in any results you return to aid in
|
||||||
|
debugging.
|
||||||
|
* Write tests for your function. Pass it a `FunctionIO` on stdin in and ensure
|
||||||
|
it returns the expected `FunctionIO` on stdout.
|
||||||
|
* Keep your Functions fast and lightweight. Remember that Crossplane runs them
|
||||||
|
approximately once every 30-60 seconds.
|
||||||
|
|
||||||
|
## The xfn runner
|
||||||
|
|
||||||
|
Composition Function runners are designed to be pluggable. Each time Crossplane
|
||||||
|
needs to invoke a Composition Function it makes a gRPC call to a configurable
|
||||||
|
endpoint. The default, reference Composition Function runner is named `xfn`.
|
||||||
|
|
||||||
|
{{< hint "note" >}}
|
||||||
|
The default runner endpoint is `unix-abstract:crossplane/fn/default.sock`. It's
|
||||||
|
possible to run Functions using a different endpoint, for example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
functions:
|
||||||
|
- name: my-cool-Function
|
||||||
|
type: Container
|
||||||
|
container:
|
||||||
|
image: xkpg.io/my-cool-Function:0.1.0
|
||||||
|
runner:
|
||||||
|
endpoint: unix-abstract:/your/custom/runner.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently Crossplane uses unauthenticated, unencrypted gRPC requests to run
|
||||||
|
Functions, so requests shouldn't be sent over the network. Encryption and
|
||||||
|
authentication will be added in a future release.
|
||||||
|
{{< /hint >}}
|
||||||
|
|
||||||
|
`xfn` runs as a sidecar container within the Crossplane pod. It runs each
|
||||||
|
Composition Function as a nested [rootless container][rootless-containers].
|
||||||
|
|
||||||
|
{{< img src="master/guides/composition-functions-xfn-runner.png" alt="Crossplane running Functions using xfn via gRPC" size="tiny" >}}
|
||||||
|
|
||||||
|
The Crossplane Helm chart deploys `xfn` with:
|
||||||
|
|
||||||
|
* The [`Unconfined` seccomp profile][kubernetes-seccomp].
|
||||||
|
* The `CAP_SETUID` and `CAP_SETGID` capabilities.
|
||||||
|
|
||||||
|
The `Unconfined` seccomp profile allows Crossplane to make required syscalls
|
||||||
|
such as `unshare` and `mount` that are not allowed by most `RuntimeDefault`
|
||||||
|
profiles. It's possible to run `xfn` with nearly the same restrictions as most
|
||||||
|
`RuntimeDefault` profiles by authoring a custom `Localhost` profile. Refer to
|
||||||
|
the [seccomp documentation][kubernetes-seccomp] for information on how to do so.
|
||||||
|
|
||||||
|
Granting `CAP_SETUID` and `CAP_SETGID` allows `xfn` to create Function
|
||||||
|
containers that support up to 65,536 UIDs and GIDs. If `xfn` is run without
|
||||||
|
these capabilities it will be restricted to creating Function containers that
|
||||||
|
support only UID and GID 0.
|
||||||
|
|
||||||
|
Regardless of capabilities `xfn` always runs each Composition Function as an
|
||||||
|
unprivileged user. That user will appear to be root inside the Composition
|
||||||
|
Function container thanks to [`user_namespaces(7)`].
|
||||||
|
|
||||||
|
[rootless-containers]: https://rootlesscontaine.rs
|
||||||
|
[kubernetes-seccomp]: https://kubernetes.io/docs/tutorials/security/seccomp/
|
||||||
|
[`user_namespaces(7)`]: https://man7.org/linux/man-pages/man7/user_namespaces.7.html
|
Loading…
Reference in New Issue