This document is for an unreleased version of Crossplane.

This document applies to the Crossplane master branch and not to the latest release v2.2.

An XRD can expose the Kubernetes scale subresource on a composite resource. Exposing the scale subresource enables standard Kubernetes scaling tools, including kubectl scale, the Horizontal Pod Autoscaler, and KEDA, to control the composite resource without knowing its full schema.

Example overview

This guide shows how to expose the scale subresource by creating a MyApp composite resource that wraps a Kubernetes Deployment.

When a user creates a MyApp, Crossplane provisions a Deployment and wires the replica count between the composite resource and the Deployment. Standard Kubernetes scaling tools can then drive the replica count without knowing the full schema of MyApp.

An example MyApp XR looks like this:

1apiVersion: example.org/v1alpha1
2kind: MyApp
3metadata:
4  name: my-app
5spec:
6  replicas: 1

Behind the scenes, Crossplane:

  1. Creates a Deployment (the composed resource) with the requested replica count
  2. Writes back the observed replica count from the Deployment to status.replicas on the MyApp
  3. Marks MyApp as ready when the Deployment is healthy

Because MyApp exposes the scale subresource, you can scale it without knowing its full schema:

1kubectl scale myapp/my-app --replicas=3

The Horizontal Pod Autoscaler and KEDA can also target MyApp directly using the same scale subresource.

Prerequisites

This guide requires:

Install the functions

Install function-patch-and-transform to compose resources and patch fields between the composite resource and its composed resources:

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

This guide also uses function-auto-ready. This function automatically marks composed resources as ready when they’re healthy:

1
2
3
4
5
6
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-auto-ready
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.3
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/master/manifests/guides/scalable-composition/fn-auto-ready.yaml

Check that Crossplane installed the functions:

1kubectl get pkg
2NAME                                                      INSTALLED   HEALTHY   PACKAGE                                                                      AGE
3function.pkg.crossplane.io/function-auto-ready            True        True      xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.3             2s
4function.pkg.crossplane.io/function-patch-and-transform   True        True      xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.3   16s

Configure the XRD

Configure the scale subresource per version in the XRD’s subresources field.

 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/v2
kind: CompositeResourceDefinition
metadata:
  name: myapps.example.org
spec:
  group: example.org
  names:
    kind: MyApp
    plural: myapps
  scope: Namespaced
  versions:
  - additionalPrinterColumns:
    - jsonPath: .spec.replicas
      name: DESIRED
      type: string
    - jsonPath: .status.replicas
      name: CURRENT
      type: string
    name: v1alpha1
    served: true
    referenceable: true
    subresources:
      scale:
        specReplicasPath: .spec.replicas
        statusReplicasPath: .status.replicas
        labelSelectorPath: .status.labelSelector
    schema:
      openAPIV3Schema:
        properties:
          spec:
            properties:
              replicas:
                type: integer
          status:
            properties:
              replicas:
                type: integer
              labelSelector:
                type: string
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/master/manifests/guides/scalable-composition/xrd-scale.yaml

Verify the XRD exists:

1kubectl get xrd
2NAME                 ESTABLISHED   OFFERED   AGE
3myapps.example.org   True                    6s

The scale block has three fields:

  • specReplicasPath: the JSON path to the field in the composite resource’s spec that holds the desired replica count. This field must exist in the XRD schema.
  • statusReplicasPath: the JSON path to the field in the composite resource’s status that holds the observed replica count. This field must exist in the XRD schema.
  • labelSelectorPath: an optional JSON path to a string field in status that holds a serialized label selector. Required by the Horizontal Pod Autoscaler.
Important
Crossplane propagates the scale configuration to the generated CRD. The composition author must implement the scaling logic, for example by patching spec.replicas from the composite resource into a composed Deployment.

Implement scaling in a Composition

After enabling the scale subresource on the XRD, wire the replica count into the composed resources in the Composition. The following example uses function-patch-and-transform to forward spec.replicas from the composite resource to a Deployment:

 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
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: myapp
spec:
  compositeTypeRef:
    apiVersion: example.org/v1alpha1
    kind: MyApp
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: deployment
        base:
          apiVersion: apps/v1
          kind: Deployment
          spec:
            replicas: 1
            selector:
              matchLabels:
                app: nginx
            template:
              metadata:
                labels:
                  app: nginx
              spec:
                containers:
                - name: nginx
                  image: nginx:1.29.7-alpine
                  ports:
                  - containerPort: 80
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.replicas
          toFieldPath: spec.replicas
        - type: ToCompositeFieldPath
          fromFieldPath: status.readyReplicas
          toFieldPath: status.replicas
  - step: automatically-detect-readiness
    functionRef:
      name: function-auto-ready
1kubectl apply -f https://deploy-preview-1086--crossplane.netlify.app/master/manifests/guides/scalable-composition/composition-scale.yaml

Verify the composition exists:

1kubectl get compositions
2NAME    XR-KIND   XR-APIVERSION          AGE
3myapp   MyApp     example.org/v1alpha1   3s

The composition must also write back the current replica count to status.replicas (and status.labelSelector if used) so that autoscalers and kubectl scale --current-replicas read the correct replica count.

Create a MyApp composite resource

With the XRD and Composition in place, create a MyApp composite resource:

1apiVersion: example.org/v1alpha1
2kind: MyApp
3metadata:
4  name: my-app
5spec:
6  replicas: 1

Verify the current replica count:

1kubectl get myapp/my-app
2NAME     DESIRED   CURRENT   SYNCED   READY   COMPOSITION   AGE
3my-app   1         1         True     True    myapp         108s

Use the scale subresource

After applying the XRD and Composition, scale a composite resource with kubectl scale:

1kubectl scale myapp/my-app --replicas=3

Verify the current replica count:

1kubectl get myapp/my-app
2NAME     DESIRED   CURRENT   SYNCED   READY   COMPOSITION   AGE
3my-app   3         3         True     True    myapp         3m26s