This guide discusses the use of “Composition Revisions” to safely make and roll back changes to a Crossplane Composition. It assumes familiarity with Crossplane, and particularly with Compositions.

A Composition configures how Crossplane should reconcile a Composite Resource (XR). Put otherwise, when you create an XR the selected Composition determines what managed resources Crossplane will create in response. Let’s say for example that you define a PlatformDB XR, which represents your organisation’s common database configuration of an Azure MySQL Server and a few firewall rules. The Composition contains the ‘base’ configuration for the MySQL server and the firewall rules that are extended by the configuration for the PlatformDB.

A Composition is associated with multiple XRs that make use of it. You might define a Composition named big-platform-db that’s used by ten different PlatformDB XRs. Usually, in the interest of self-service, the Composition is managed by a different team from the actual PlatformDB XRs. For example the Composition may be written and maintained by a platform team member, while individual application teams create PlatformDB XRs that use said Composition.

Each Composition is mutable - you can update it as your organisation’s needs change. However, updating a Composition without Composition Revisions can be a risky process. Crossplane constantly uses the Composition to ensure that your actual infrastructure - your MySQL Servers and firewall rules - match your desired state. If you have 10 PlatformDB XRs all using the big-platform-db Composition, all 10 of those XRs will be instantly updated in accordance with any updates you make to the big-platform-db Composition.

Composition Revisions allow XRs to opt out of automatic updates. Instead you can update your XRs to use the latest Composition settings at your own pace. This enables you to canary changes to your infrastructure, or to roll back some XRs to previous Composition settings without rolling back all XRs.

Using Composition Revisions

When Composition Revisions are enabled three things happen:

  1. Crossplane creates a CompositionRevision for each Composition update.
  2. Composite Resources gain a spec.compositionRevisionRef field that specifies which CompositionRevision they use.
  3. Composite Resources gain a spec.compositionUpdatePolicy field that specifies how they should be updated to new Composition Revisions.

Each time you edit a Composition Crossplane will automatically create a CompositionRevision that represents that ‘revision’ of the Composition - that unique state. Each revision is allocated an increasing revision number. This gives CompositionRevision consumers an idea about which revision is ’newest’.

You can discover which revisions exist using kubectl:

1# Find all revisions of the Composition named 'example'
2kubectl get compositionrevision -l crossplane.io/composition-name=example

This should produce output something like:

1NAME              REVISION   AGE
2example-18pdgs2   1          4m36s
3example-2bgdr31   2          73s
4example-xjrdmzz   3          61s

A Composition is a mutable resource that you can update as your needs change over time. Each CompositionRevision is an immutable snapshot of those needs at a particular point in time.

Crossplane behaves the same way by default whether Composition Revisions are enabled or not. This is because when you enable Composition Revisions all XRs default to the Automatic compositionUpdatePolicy. XRs support two update policies:

  • Automatic: Automatically use the latest CompositionRevision. (Default)
  • Manual: Require manual intervention to change CompositionRevision.

The below XR uses the Manual policy. When this policy is used the XR will select the latest CompositionRevision when it’s first created, but must manually be updated when you wish it to use another CompositionRevision.

 1apiVersion: example.org/v1alpha1
 2kind: PlatformDB
 3metadata:
 4  name: example
 5spec:
 6  parameters:
 7    storageGB: 20
 8  # The Manual policy specifies that you don't want this XR to update to the
 9  # latest CompositionRevision automatically.
10  compositionUpdatePolicy: Manual
11  compositionRef:
12    name: example
13  writeConnectionSecretToRef:
14    name: db-conn

Crossplane sets an XR’s compositionRevisionRef automatically at creation time regardless of your chosen compositionUpdatePolicy. If you choose the Manual policy you must edit the compositionRevisionRef field when you want your XR to use a different CompositionRevision.

 1apiVersion: example.org/v1alpha1
 2kind: PlatformDB
 3metadata:
 4  name: example
 5spec:
 6  parameters:
 7    storageGB: 20
 8  compositionUpdatePolicy: Manual
 9  compositionRef:
10    name: example
11  # Update the referenced CompositionRevision if and when you are ready.
12  compositionRevisionRef:
13    name: example-18pdg
14  writeConnectionSecretToRef:
15    name: db-conn

Complete example

This tutorial discusses how CompositionRevisions work and how they manage Composite Resource (XR) updates. This starts with a Composition and CompositeResourceDefinition (XRD) that defines a MyVPC resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will assign different CompositionRevisions to the created composite resources each time the composition is updated.

Preparation

Install Crossplane

Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.

1kubectl create namespace crossplane-system
2helm repo add crossplane-master https://charts.crossplane.io/master/
3helm repo update
4helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
5kubectl get pods -n crossplane-system

Expected Output:

1NAME                                       READY   STATUS    RESTARTS   AGE
2crossplane-7f75ddcc46-f4d2z                1/1     Running   0          9s
3crossplane-rbac-manager-78bd597746-sdv6w   1/1     Running   0          9s

Deploy Composition and XRD Examples

Apply the example Composition.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  labels:
 5    channel: dev
 6  name: myvpcs.aws.example.upbound.io
 7spec:
 8  writeConnectionSecretsToNamespace: crossplane-system
 9  compositeTypeRef:
10    apiVersion: aws.example.upbound.io/v1alpha1
11    kind: MyVPC
12  mode: Pipeline
13  pipeline:
14  - step: patch-and-transform
15    functionRef:
16      name: function-patch-and-transform
17    input:
18      apiVersion: pt.fn.crossplane.io/v1beta1
19      kind: Resources
20      resources:
21      - name: my-vpc
22        base:
23          apiVersion: ec2.aws.upbound.io/v1beta1
24          kind: VPC
25          spec:
26            forProvider:
27              region: us-west-1
28              cidrBlock: 192.168.0.0/16
29              enableDnsSupport: true
30              enableDnsHostnames: true

Apply the example XRD.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: myvpcs.aws.example.upbound.io
 5spec:
 6  group: aws.example.upbound.io
 7  names:
 8    kind: MyVPC
 9    plural: myvpcs
10  versions:
11  - name: v1alpha1
12    served: true 
13    referenceable: true 
14    schema:
15      openAPIV3Schema:
16        type: object 
17        properties:
18          spec:
19            type: object 
20            properties:
21              id:
22                type: string 
23                description: ID of this VPC that other objects will use to refer to it. 
24            required:
25            - id

Verify that Crossplane created the Composition revision

1kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"

Expected Output:

1NAME                                    REVISION   CHANNEL
2myvpcs.aws.example.upbound.io-ad265bc   1          dev
Note
The label dev is automatically created from the Composition.

Create Composite Resources

This tutorial has four composite resources to cover different update policies and composition selection options. The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting compositionUpdatePolicy: Manual in the XR. It’s also possible to select the latest revision with a specific label with compositionRevisionSelector.matchLabels together with compositionUpdatePolicy: Automatic.

Default update policy

Create an XR without a compositionUpdatePolicy defined. The update policy is Automatic by default:

1apiVersion: aws.example.upbound.io/v1alpha1
2kind: MyVPC
3metadata:
4  name: vpc-auto
5spec:
6  id: vpc-auto

Expected Output:

1myvpc.aws.example.upbound.io/vpc-auto created

Manual update policy

Create a Composite Resource with compositionUpdatePolicy: Manual and compositionRevisionRef.

1apiVersion: aws.example.upbound.io/v1alpha1
2kind: MyVPC
3metadata:
4  name: vpc-man
5spec:
6  id: vpc-man
7  compositionUpdatePolicy: Manual
8  compositionRevisionRef:
9    name: myvpcs.aws.example.upbound.io-ad265bc

Expected Output:

1myvpc.aws.example.upbound.io/vpc-man created

Using a selector

Create an XR with a compositionRevisionSelector of channel: dev:

1apiVersion: aws.example.upbound.io/v1alpha1
2kind:  MyVPC
3metadata:
4  name: vpc-dev
5spec:
6  id: vpc-dev
7  compositionRevisionSelector:
8    matchLabels:
9      channel: dev

Expected Output:

1myvpc.aws.example.upbound.io/vpc-dev created

Create an XR with a compositionRevisionSelector of channel: staging:

1apiVersion: aws.example.upbound.io/v1alpha1
2kind: MyVPC
3metadata:
4  name: vpc-staging
5spec:
6  id: vpc-staging
7  compositionRevisionSelector:
8    matchLabels:
9      channel: staging

Expected Output:

1myvpc.aws.example.upbound.io/vpc-staging created

Verify the Composite Resource with the label channel: staging doesn’t have a REVISION.
All other XRs have a REVISION matching the created Composition Revision.

1kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"

Expected Output:

1NAME          SYNCED   REVISION                                POLICY      MATCHLABEL
2vpc-auto      True     myvpcs.aws.example.upbound.io-ad265bc   Automatic   <none>
3vpc-dev       True     myvpcs.aws.example.upbound.io-ad265bc   Automatic   map[channel:dev]
4vpc-man       True     myvpcs.aws.example.upbound.io-ad265bc   Manual      <none>
5vpc-staging   False    <none>                                  Automatic   map[channel:staging]
Note
The vpc-staging XR label doesn’t match any existing Composition Revisions.

Create new Composition revisions

Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will also trigger a new CompositionRevision.

Update the Composition label

Update the Composition label to channel: staging:

1kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite

Expected Output:

1composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled

Verify that Crossplane creates a new Composition revision:

1kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"

Expected Output:

1NAME                                    REVISION   CHANNEL
2myvpcs.aws.example.upbound.io-727b3c8   2          staging
3myvpcs.aws.example.upbound.io-ad265bc   1          dev

Verify that Crossplane assigns the Composite Resources vpc-auto and vpc-staging to Composite revision:2.
XRs vpc-man and vpc-dev are still assigned to the original revision:1:

1kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"

Expected Output:

1NAME          SYNCED   REVISION                                POLICY      MATCHLABEL
2vpc-auto      True     myvpcs.aws.example.upbound.io-727b3c8   Automatic   <none>
3vpc-dev       True     myvpcs.aws.example.upbound.io-ad265bc   Automatic   map[channel:dev]
4vpc-man       True     myvpcs.aws.example.upbound.io-ad265bc   Manual      <none>
5vpc-staging   True     myvpcs.aws.example.upbound.io-727b3c8   Automatic   map[channel:staging]
Note
vpc-auto always use the latest Revision.
vpc-staging now matches the label applied to Revision revision:2.

Update Composition Spec and Label

Update the Composition to disable DNS support in the VPC and change the label from staging back to dev.

Apply the following changes to update the Composition spec and label:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  labels:
 5    channel: dev
 6  name: myvpcs.aws.example.upbound.io
 7spec:
 8  writeConnectionSecretsToNamespace: crossplane-system
 9  compositeTypeRef:
10    apiVersion: aws.example.upbound.io/v1alpha1
11    kind: MyVPC
12  mode: Pipeline
13  pipeline:
14  - step: patch-and-transform
15    functionRef:
16      name: function-patch-and-transform
17    input:
18      apiVersion: pt.fn.crossplane.io/v1beta1
19      kind: Resources
20      resources:
21      - name: my-vpc
22        base:
23          apiVersion: ec2.aws.upbound.io/v1beta1
24          kind: VPC
25          spec:
26            forProvider:
27              region: us-west-1
28              cidrBlock: 192.168.0.0/16
29              enableDnsSupport: false
30              enableDnsHostnames: true

Expected Output:

1composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured

Verify that Crossplane creates a new Composition revision:

1kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"

Expected Output:

1NAME                                    REVISION   CHANNEL
2myvpcs.aws.example.upbound.io-727b3c8   2          staging
3myvpcs.aws.example.upbound.io-ad265bc   1          dev
4myvpcs.aws.example.upbound.io-f81c553   3          dev
Note
Changing the label and the spec values simultaneously is critical for deploying new changes to the dev channel.

Verify Crossplane assigns the Composite Resources vpc-auto and vpc-dev to Composite revision:3.
vpc-staging is assigned to revision:2, and vpc-man is still assigned to the original revision:1:

1kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"

Expected Output:

1NAME          SYNCED   REVISION                                POLICY      MATCHLABEL
2vpc-auto      True     myvpcs.aws.example.upbound.io-f81c553   Automatic   <none>
3vpc-dev       True     myvpcs.aws.example.upbound.io-f81c553   Automatic   map[channel:dev]
4vpc-man       True     myvpcs.aws.example.upbound.io-ad265bc   Manual      <none>
5vpc-staging   True     myvpcs.aws.example.upbound.io-727b3c8   Automatic   map[channel:staging]
Note
vpc-dev matches the updated label applied to Revision revision:3. vpc-staging matches the label applied to Revision revision:2.