Get Started With Composition

This guide shows how to create a new kind of custom resource named App. When a user calls the custom resource API to create an App, Crossplane creates a Deployment and a Service.

Crossplane calls this composition. The App is composed of the Deployment and the Service.

Tip
The guide shows how to configure composition using YAML, YAML+CEL, templated YAML, Python, and KCL. You can pick your preferred language.

An App custom resource looks like this:

 1apiVersion: example.crossplane.io/v1
 2kind: App
 3metadata:
 4  namespace: default
 5  name: my-app
 6spec:
 7  image: nginx
 8status:
 9  replicas: 2  # Copied from the Deployment's status
10  address: 10.0.0.1  # Copied from the Service's status

The App is the custom API Crossplane users use to configure an app.

When users create an App Crossplane creates this Deployment and Service:

 1---
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  namespace: default
 6  name: my-app-dhj3a
 7  labels:
 8    example.crossplane.io/app: my-app  # Copied from the App's name
 9spec:
10  replicas: 2
11  selector:
12    matchLabels:
13      example.crossplane.io/app: my-app  # Copied from the App's name
14  template:
15    metadata:
16      labels:
17        example.crossplane.io/app: my-app  # Copied from the App's name
18    spec:
19      containers:
20      - name: app
21        image: nginx  # Copied from the App's spec
22        ports:
23        - containerPort: 80
24---
25apiVersion: v1
26kind: Service
27metadata:
28  namespace: default
29  name: my-app-03mda
30  labels:
31    example.crossplane.io/app: my-app  # Copied from the App's name
32spec:
33  selector:
34    example.crossplane.io/app: my-app  # Copied from the App's name
35  ports:
36  - protocol: TCP
37    port: 8080
38    targetPort: 80

Crossplane builds on Kubernetes, so users can use kubectl or any other tool from the Kubernetes ecosystem to work with apps.

Tip
Kubernetes custom resources are just JSON REST APIs, so users can use any tool that supports REST APIs to work with apps.

Prerequisites

This guide requires:

Create the custom resource

Follow these steps to create a new kind of custom resource using Crossplane:

  1. Define the schema of the App custom resource
  2. Install the function you want to use to configure how Crossplane composes apps
  3. Configure how Crossplane composes apps

After you complete these steps you can use the new App custom resource.

Define the schema

Crossplane calls a custom resource that’s powered by composition a composite resource, or XR.

Note

Kubernetes calls user-defined API resources custom resources.

Crossplane calls user-defined API resources that use composition composite resources.

A composite resource is a kind of custom resource.

Create this composite resource definition (XRD) to define the schema of the new App composite resource (XR).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
  name: apps.example.crossplane.io
spec:
  scope: Namespaced
  group: example.crossplane.io
  names:
    kind: App
    plural: apps
  versions:
  - name: v1
    served: true
    referenceable: true
    schema:
     openAPIV3Schema:
       type: object
       properties:
        spec:
          type: object
          properties:
            image:
              description: The app's OCI container image.
              type: string
          required:
          - image
        status:
          type: object
          properties:
            replicas:
              description: The number of available app replicas.
              type: integer
            address:
              description: The app's IP address.
              type: string
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/xrd.yaml

Check that Crossplane has established the XRD:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/xrd.yaml
2NAME                         ESTABLISHED   OFFERED   AGE
3apps.example.crossplane.io   True                    21s

Now that Crossplane has established the XRD, Kubernetes is serving API requests for the new App XR.

Crossplane now knows it’s responsible for the new App XR, but it doesn’t know what to do when you create or update one. You tell Crossplane what to do by installing a function and configuring a composition.

Install the function

You can use different composition functions to configure what Crossplane does when someone creates or updates a composite resource (XR). Composition functions are like configuration language plugins.

Pick what language to use to configure how Crossplane turns an App XR into a Deployment and a Service.

Templated YAML is a good choice if you’re used to writing Helm charts.

Create this composition function to install templated YAML support:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-go-templating
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-go-templating.yaml

Check that Crossplane installed the function:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-go-templating.yaml
2NAME                                        INSTALLED   HEALTHY   PACKAGE                                                               AGE
3crossplane-contrib-function-go-templating   True        True      xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2   9s

YAML is a good choice for small, static compositions. It doesn’t support loops or conditionals.

Create this composition function to install YAML support:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-patch-and-transform.yaml

Check that Crossplane installed the function:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-patch-and-transform.yaml
2NAME                                              INSTALLED   HEALTHY   PACKAGE                                                                     AGE
3crossplane-contrib-function-patch-and-transform   True        True      xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2   10s

YAML+CEL is a good choice for defining resources in plain YAML and wiring them together with CEL expressions. The function resolves dependencies between resources automatically.

Create this composition function to install YAML+CEL support:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-kro
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-kro.yaml

Check that Crossplane installed the function:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-kro.yaml
2NAME                              INSTALLED   HEALTHY   PACKAGE                                                     AGE
3crossplane-contrib-function-kro   True        True      xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0   6s

Python is a good choice for compositions with dynamic logic. You can use the full Python standard library.

Create this composition function to install Python support:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-python
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-python.yaml

Check that Crossplane installed the function:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-python.yaml
2NAME                                 INSTALLED   HEALTHY   PACKAGE                                                        AGE
3crossplane-contrib-function-python   True        True      xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0   12s

KCL is a good choice for compositions with dynamic logic. It’s fast and sandboxed.

Create this composition function to install KCL support:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-kcl
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-kcl.yaml

Check that Crossplane installed the function:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-kcl.yaml
2NAME                              INSTALLED   HEALTHY   PACKAGE                                                      AGE
3crossplane-contrib-function-kcl   True        True      xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2   6s

Pythonic is an excellent choice for compositions with dynamic logic. The full flexibility and power of python is available using a set of python classes with an elegant and terse syntax that hides the details of the low level Crossplane function APIs.

Create this composition function to install Pythonic support:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-pythonic
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-pythonic.yaml

Check that Crossplane installed the function:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/fn-pythonic.yaml
2NAME               INSTALLED  HEALTHY  PACKAGE                                                         AGE
3function-pythonic  True       True     xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0  1m

Configure the composition

A composition tells Crossplane what functions to call when you create or update a composite resource (XR).

Create a composition to tell Crossplane what to do when you create or update an App XR.

Create this composition to use templated YAML to configure Crossplane:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-templated-yaml
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-go-templating
    input:
      apiVersion: gotemplating.fn.crossplane.io/v1beta1
      kind: GoTemplate
      source: Inline
      inline:
        template: |
          ---
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            annotations:
              gotemplating.fn.crossplane.io/composition-resource-name: deployment
              {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }}
              gotemplating.fn.crossplane.io/ready: "True"
              {{ end }}
            labels:
              example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
          spec:
            replicas: 2
            selector:
              matchLabels:
                example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
            template:
              metadata:
                labels:
                  example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
              spec:
                containers:
                - name: app
                  image: {{ .observed.composite.resource.spec.image }}
                  ports:
                  - containerPort: 80
          ---
          apiVersion: v1
          kind: Service
          metadata:
            annotations:
              gotemplating.fn.crossplane.io/composition-resource-name: service
              {{ if (get (getComposedResource . "service").spec "clusterIP") }}
              gotemplating.fn.crossplane.io/ready: "True"
              {{ end }}
            labels:
              example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
          spec:
            selector:
              example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
            ports:
            - protocol: TCP
              port: 8080
              targetPort: 80
          ---
          apiVersion: example.crossplane.io/v1
          kind: App
          status:
            replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }}
            address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }}
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/composition-templated-yaml.yaml

Create this composition to use YAML to configure Crossplane:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-yaml
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: deployment
        base:
          apiVersion: apps/v1
          kind: Deployment
          spec:
            replicas: 2
            template:
              spec:
                containers:
                - name: app
                  ports:
                  - containerPort: 80
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: metadata.labels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.selector.matchLabels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.template.metadata.labels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: spec.image
          toFieldPath: spec.template.spec.containers[0].image
        - type: ToCompositeFieldPath
          fromFieldPath: status.availableReplicas
          toFieldPath: status.replicas
        readinessChecks:
        - type: MatchCondition
          matchCondition:
            type: Available
            status: "True"
      - name: service
        base:
          apiVersion: v1
          kind: Service
          spec:
            ports:
            - protocol: TCP
              port: 8080
              targetPort: 80
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: metadata.labels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.selector[example.crossplane.io/app]
        - type: ToCompositeFieldPath
          fromFieldPath: spec.clusterIP
          toFieldPath: status.address
        readinessChecks:
        - type: NonEmpty
          fieldPath: spec.clusterIP
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/composition-yaml.yaml

Create this composition to use YAML and CEL to configure Crossplane. Define resources in YAML, wire them with CEL expressions, and let Crossplane handle the rest.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-yaml-cel
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-kro
    input:
      apiVersion: kro.fn.crossplane.io/v1beta1
      kind: ResourceGraph
      status:
        replicas: ${deployment.status.?availableReplicas.orValue(0)}
        address: ${service.spec.?clusterIP.orValue("")}
      resources:
      - id: deployment
        template:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            labels:
              example.crossplane.io/app: ${schema.metadata.name}
          spec:
            replicas: 2
            selector:
              matchLabels:
                example.crossplane.io/app: ${schema.metadata.name}
            template:
              metadata:
                labels:
                  example.crossplane.io/app: ${schema.metadata.name}
              spec:
                containers:
                - name: app
                  image: ${schema.spec.image}
                  ports:
                  - containerPort: 80
        readyWhen:
        - ${deployment.status.?conditions.orValue([]).exists(c, c.type == "Available" && c.status == "True")}
      - id: service
        template:
          apiVersion: v1
          kind: Service
          metadata:
            labels:
              example.crossplane.io/app: ${schema.metadata.name}
          spec:
            selector:
              example.crossplane.io/app: ${schema.metadata.name}
            ports:
            - protocol: TCP
              port: 8080
              targetPort: 80
        readyWhen:
        - ${service.spec.?clusterIP.hasValue()}
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/composition-yaml-cel.yaml
Tip
This function uses the same resource graph syntax as kro, so existing kro resource definitions work without changes.

Create this composition to use Python to configure Crossplane:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-python
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-python
    input:
      apiVersion: python.fn.crossplane.io/v1beta1
      kind: Script
      script: |
        def compose(req, rsp):
            observed_xr = req.observed.composite.resource

            rsp.desired.resources["deployment"].resource.update({
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {
                  "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                },
                "spec": {
                    "replicas": 2,
                    "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}},
                    "template": {
                      "metadata": {
                        "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                      },
                      "spec": {
                        "containers": [{
                          "name": "app",
                          "image": observed_xr["spec"]["image"],
                          "ports": [{"containerPort": 80}]
                        }],
                      },
                    },
                },
            })

            observed_deployment = req.observed.resources["deployment"].resource
            if "status" in observed_deployment:
              if "availableReplicas" in observed_deployment["status"]:
                rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"]
              if "conditions" in observed_deployment["status"]:
                for condition in observed_deployment["status"]["conditions"]:
                  if condition["type"] == "Available" and condition["status"] == "True":
                    rsp.desired.resources["deployment"].ready = True

            rsp.desired.resources["service"].resource.update({
                "apiVersion": "v1",
                "kind": "Service",
                "metadata": {
                  "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                },
                "spec": {
                  "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                  "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}],
                },
            })

            observed_service = req.observed.resources["service"].resource
            if "spec" in observed_service and "clusterIP" in observed_service["spec"]:
              rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"]
              rsp.desired.resources["service"].ready = True
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/composition-python.yaml
Tip

You can write your own function in Python.

It’s a good idea to write your own function for larger configurations. When you write your own function you can write multiple files of Python. You don’t embed the Python in YAML, so it’s easier to use a Python IDE.

Read the guide to writing a composition function in Python.

Create this composition to use KCL to configure Crossplane:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-kcl
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-kcl
    input:
      apiVersion: krm.kcl.dev/v1alpha1
      kind: KCLInput
      spec:
        source: |
          observed_xr = option("params").oxr

          _desired_deployment = {
            apiVersion = "apps/v1"
            kind = "Deployment"
            metadata = {
              annotations = {
                "krm.kcl.dev/composition-resource-name" = "deployment"
              }
              labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
            }
            spec = {
              replicas = 2
              selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name}
              template = {
                metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
                spec.containers = [{
                  name = "app"
                  image = observed_xr.spec.image
                  ports = [{containerPort = 80}]
                }]
              }
            }
          }

          observed_deployment = option("params").ocds["deployment"]?.Resource
          if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]):
            _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True"

          _desired_service = {
            apiVersion = "v1"
            kind = "Service"
            metadata = {
              annotations = {
                "krm.kcl.dev/composition-resource-name" = "service"
              }
              labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
            }
            spec = {
              selector = {"example.crossplane.io/app" = observed_xr.metadata.name}
              ports = [{protocol = "TCP", port = 8080, targetPort = 80}]
            }
          }

          observed_service = option("params").ocds["service"]?.Resource
          if observed_service?.spec?.clusterIP:
            _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True"

          _desired_xr = {
            **option("params").dxr

            status.address = observed_service?.spec?.clusterIP or ""
            status.replicas = observed_deployment?.status?.availableReplicas or 0
          }

          items = [_desired_deployment, _desired_service, _desired_xr]
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/composition-kcl.yaml

Create this composition to use Pythonic to configure Crossplane:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-pythonic
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: function-pythonic
    input:
      apiVersion: pythonic.fn.crossplane.io/v1alpha1
      kind: Composite
      composite: |
        class Composite(BaseComposite):
          def compose(self):
            labels = {'example.crossplane.io/app': self.metadata.name}

            d = self.resources.deployment('apps/v1', 'Deployment')
            d.metadata.labels = labels
            d.spec.replicas = 2
            d.spec.selector.matchLabels = labels
            d.spec.template.metadata.labels = labels
            d.spec.template.spec.containers[0].name = 'app'
            d.spec.template.spec.containers[0].image = self.spec.image
            d.spec.template.spec.containers[0].ports[0].containerPort = 80

            s = self.resources.service('v1', 'Service')
            s.metadata.labels = labels
            s.spec.selector = labels
            s.spec.ports[0].protocol = 'TCP'
            s.spec.ports[0].port = 8080
            s.spec.ports[0].targetPort = 80

            self.status.replicas = d.status.availableReplicas
            self.status.address = s.observed.spec.clusterIP
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/composition-pythonic.yaml
Note

A composition can include multiple functions.

Functions can change the results of earlier functions in the pipeline. Crossplane uses the result returned by the last function.

Tip
If you edit this composition to include a different kind of resource you might need to grant Crossplane access to compose it. Read the composition documentation to learn how to grant Crossplane access.

Use the custom resource

Crossplane now understands App custom resources.

Create an App:

1
2
3
4
5
6
7
apiVersion: example.crossplane.io/v1
kind: App
metadata:
  namespace: default
  name: my-app
spec:
  image: nginx
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/app.yaml

Check that the App is ready:

1kubectl get -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/app.yaml
2NAME     SYNCED   READY   COMPOSITION   AGE
3my-app   True     True    app-yaml      56s
Note

The COMPOSITION column shows what composition the App is using.

You can create multiple compositions for each kind of XR. Read the XR page to learn how to select which composition Crossplane uses.

Check that Crossplane created a Deployment and a Service:

1kubectl get deploy,service -l example.crossplane.io/app=my-app
2NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
3deployment.apps/my-app-2r2rk   2/2     2            2           11m
4
5NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
6service/my-app-xfkzg   ClusterIP   10.96.148.56   <none>        8080/TCP   11m
Tip

Edit the App’s image:

1kubectl edit -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/app.yaml

Crossplane updates the Deployment’s image to match.

Delete the App.

1kubectl delete -f https://deploy-preview-1086--crossplane.netlify.app/v2.2/manifests/get-started/composition/app.yaml

When you delete the App, Crossplane deletes the Deployment and Service.

Next steps

Managed resources (MRs) are ready-made Kubernetes custom resources.

Crossplane has an extensive library of managed resources you can use to manage almost any cloud provider, or cloud native software.

Get started with managed resources to learn more about them.

You can use MRs with composition. Try updating your App composition to include an MR.