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:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-patch-and-transform
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0

Save the function as fn-pat.yaml and apply it:

1kubectl apply -f fn-pat.yaml

Check that Crossplane installed the function:

1kubectl get -f fn-pat.yaml
2NAME                          INSTALLED   HEALTHY   PACKAGE                                                                      AGE
3function-patch-and-transform  True        True      xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0   8s

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

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-auto-ready
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0

Save this as fn-auto-ready.yaml and apply it:

1kubectl apply -f fn-auto-ready.yaml

Configure the XRD

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

 1apiVersion: apiextensions.crossplane.io/v2
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: myapps.example.org
 5spec:
 6  group: example.org
 7  names:
 8    kind: MyApp
 9    plural: myapps
10  scope: Namespaced
11  versions:
12  - additionalPrinterColumns:
13    - jsonPath: .spec.replicas
14      name: DESIRED
15      type: string
16    - jsonPath: .status.replicas
17      name: CURRENT
18      type: string
19    name: v1alpha1
20    served: true
21    referenceable: true
22    subresources:
23      scale:
24        specReplicasPath: .spec.replicas
25        statusReplicasPath: .status.replicas
26        labelSelectorPath: .status.labelSelector
27    schema:
28      openAPIV3Schema:
29        properties:
30          spec:
31            properties:
32              replicas:
33                type: integer
34          status:
35            properties:
36              replicas:
37                type: integer
38              labelSelector:
39                type: string

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:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: myapp
 5spec:
 6  compositeTypeRef:
 7    apiVersion: example.org/v1alpha1
 8    kind: MyApp
 9  mode: Pipeline
10  pipeline:
11  - step: patch-and-transform
12    functionRef:
13      name: function-patch-and-transform
14    input:
15      apiVersion: pt.fn.crossplane.io/v1beta1
16      kind: Resources
17      resources:
18      - name: deployment
19        base:
20          apiVersion: apps/v1
21          kind: Deployment
22          spec:
23            replicas: 1
24            selector:
25              matchLabels:
26                app: nginx
27            template:
28              metadata:
29                labels:
30                  app: nginx
31              spec:
32                containers:
33                - name: nginx
34                  image: nginx:1.29.7-alpine
35                  ports:
36                  - containerPort: 80
37        patches:
38        - type: FromCompositeFieldPath
39          fromFieldPath: spec.replicas
40          toFieldPath: spec.replicas
41        - type: ToCompositeFieldPath
42          fromFieldPath: status.readyReplicas
43          toFieldPath: status.replicas
44  - step: automatically-detect-readiness
45    functionRef:
46      name: function-auto-ready

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

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:

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