Skip to content

Commit ef6c2e3

Browse files
committed
docs: add scalable composition guide and XRD scale subresource section
Signed-off-by: Jonasz Łasut-Balcerzak <jonasz.lasut@gmail.com>
1 parent d58066f commit ef6c2e3

File tree

5 files changed

+309
-0
lines changed

5 files changed

+309
-0
lines changed

content/master/composition/composite-resource-definitions.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,13 @@ isolation and follows standard Kubernetes patterns. Use `Cluster` scope only
531531
for platform level resources like RBAC or cluster configuration.
532532
{{< /hint >}}
533533

534+
### Scale subresource
535+
536+
XRDs can expose the Kubernetes `scale` subresource on a composite resource,
537+
enabling `kubectl scale`, the Horizontal Pod Autoscaler, and KEDA to control
538+
the composite resource. For a full walkthrough see
539+
[Scalable Composition]({{<ref "../guides/scalable-composition">}}).
540+
534541
### Set composite resource defaults
535542
XRDs can set default parameters for composite resources.
536543

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
---
2+
title: Scalable Composition
3+
weight: 84
4+
description: "Expose the Kubernetes scale subresource on a composite resource to
5+
enable kubectl scale, HPA, and KEDA"
6+
---
7+
8+
An XRD can expose the Kubernetes
9+
[`scale` subresource](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource)
10+
on a composite resource. Exposing the `scale` subresource enables standard
11+
Kubernetes scaling tools, including `kubectl scale`, the
12+
[Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/),
13+
and [KEDA](https://keda.sh/), to control the composite resource without knowing
14+
its full schema.
15+
16+
## Example overview
17+
18+
This guide shows how to expose the `scale` subresource by creating a `MyApp`
19+
composite resource that wraps a Kubernetes `Deployment`.
20+
21+
When a user creates a `MyApp`, Crossplane provisions a `Deployment` and wires
22+
the replica count between the composite resource and the `Deployment`. Standard
23+
Kubernetes scaling tools can then drive the replica count without knowing the
24+
full schema of `MyApp`.
25+
26+
An example `MyApp` XR looks like this:
27+
28+
```yaml {copy-lines="none"}
29+
apiVersion: example.org/v1alpha1
30+
kind: MyApp
31+
metadata:
32+
name: my-app
33+
spec:
34+
replicas: 1
35+
```
36+
37+
**Behind the scenes, Crossplane:**
38+
39+
1. Creates a `Deployment` (the composed resource) with the requested replica
40+
count
41+
2. Writes back the observed replica count from the `Deployment` to
42+
`status.replicas` on the `MyApp`
43+
3. Marks `MyApp` as ready when the `Deployment` is healthy
44+
45+
Because `MyApp` exposes the `scale` subresource, you can scale it without
46+
knowing its full schema:
47+
48+
```shell {copy-lines="none"}
49+
kubectl scale myapp/my-app --replicas=3
50+
```
51+
52+
The Horizontal Pod Autoscaler and KEDA can also target `MyApp` directly using
53+
the same `scale` subresource.
54+
55+
## Prerequisites
56+
57+
This guide requires:
58+
59+
* A Kubernetes cluster
60+
* Crossplane [installed on the Kubernetes cluster]({{<ref "../get-started/install">}})
61+
62+
## Install the functions
63+
64+
Install `function-patch-and-transform` to compose resources and patch
65+
fields between the composite resource and its composed resources:
66+
67+
```yaml
68+
apiVersion: pkg.crossplane.io/v1
69+
kind: Function
70+
metadata:
71+
name: function-patch-and-transform
72+
spec:
73+
package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.3
74+
```
75+
76+
Save the function as `fn-pat.yaml` and apply it:
77+
78+
```shell
79+
kubectl apply -f fn-pat.yaml
80+
```
81+
82+
This guide also uses `function-auto-ready`. This function automatically
83+
marks composed resources as ready when they're healthy:
84+
85+
```yaml
86+
apiVersion: pkg.crossplane.io/v1
87+
kind: Function
88+
metadata:
89+
name: function-auto-ready
90+
spec:
91+
package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.3
92+
```
93+
94+
Save this as `fn-auto-ready.yaml` and apply it:
95+
96+
```shell
97+
kubectl apply -f fn-auto-ready.yaml
98+
```
99+
100+
Check that Crossplane installed the functions:
101+
102+
```shell {copy-lines="1"}
103+
kubectl get pkg
104+
NAME INSTALLED HEALTHY PACKAGE AGE
105+
function.pkg.crossplane.io/function-auto-ready True False xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.3 2s
106+
function.pkg.crossplane.io/function-patch-and-transform True False xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.3 16s
107+
```
108+
109+
## Configure the XRD
110+
111+
Configure the `scale` subresource per version in the XRD's
112+
{{<hover label="xrdscale" line="22">}}subresources{{</hover>}} field.
113+
114+
```yaml {label="xrdscale"}
115+
apiVersion: apiextensions.crossplane.io/v2
116+
kind: CompositeResourceDefinition
117+
metadata:
118+
name: myapps.example.org
119+
spec:
120+
group: example.org
121+
names:
122+
kind: MyApp
123+
plural: myapps
124+
scope: Namespaced
125+
versions:
126+
- additionalPrinterColumns:
127+
- jsonPath: .spec.replicas
128+
name: DESIRED
129+
type: string
130+
- jsonPath: .status.replicas
131+
name: CURRENT
132+
type: string
133+
name: v1alpha1
134+
served: true
135+
referenceable: true
136+
subresources:
137+
scale:
138+
specReplicasPath: .spec.replicas
139+
statusReplicasPath: .status.replicas
140+
labelSelectorPath: .status.labelSelector
141+
schema:
142+
openAPIV3Schema:
143+
properties:
144+
spec:
145+
properties:
146+
replicas:
147+
type: integer
148+
status:
149+
properties:
150+
replicas:
151+
type: integer
152+
labelSelector:
153+
type: string
154+
```
155+
156+
Save this as `xrd-scale.yaml` and apply it:
157+
158+
```shell
159+
kubectl apply -f xrd-scale.yaml
160+
```
161+
162+
Verify the XRD exists:
163+
164+
```shell {copy-lines="1"}
165+
kubectl get xrd
166+
NAME ESTABLISHED OFFERED AGE
167+
myapps.example.org True 6s
168+
```
169+
170+
The `scale` block has three fields:
171+
172+
* {{<hover label="xrdscale" line="24">}}specReplicasPath{{</hover>}}: the
173+
JSON path to the field in the composite resource's `spec` that holds the
174+
desired replica count. This field must exist in the XRD schema.
175+
* {{<hover label="xrdscale" line="25">}}statusReplicasPath{{</hover>}}: the
176+
JSON path to the field in the composite resource's `status` that holds the
177+
observed replica count. This field must exist in the XRD schema.
178+
* {{<hover label="xrdscale" line="26">}}labelSelectorPath{{</hover>}}: an
179+
optional JSON path to a `string` field in `status` that holds a serialized
180+
label selector. Required by the Horizontal Pod Autoscaler.
181+
182+
{{<hint "important">}}
183+
Crossplane propagates the `scale` configuration to the generated CRD.
184+
The composition author must implement the scaling logic, for example
185+
by patching `spec.replicas` from the composite resource into a
186+
composed `Deployment`.
187+
{{</hint>}}
188+
189+
## Implement scaling in a Composition
190+
191+
After enabling the `scale` subresource on the XRD, wire the replica count into
192+
the composed resources in the Composition. The following example uses
193+
`function-patch-and-transform` to forward `spec.replicas` from the composite
194+
resource to a `Deployment`:
195+
196+
```yaml
197+
apiVersion: apiextensions.crossplane.io/v1
198+
kind: Composition
199+
metadata:
200+
name: myapp
201+
spec:
202+
compositeTypeRef:
203+
apiVersion: example.org/v1alpha1
204+
kind: MyApp
205+
mode: Pipeline
206+
pipeline:
207+
- step: patch-and-transform
208+
functionRef:
209+
name: function-patch-and-transform
210+
input:
211+
apiVersion: pt.fn.crossplane.io/v1beta1
212+
kind: Resources
213+
resources:
214+
- name: deployment
215+
base:
216+
apiVersion: apps/v1
217+
kind: Deployment
218+
spec:
219+
replicas: 1
220+
selector:
221+
matchLabels:
222+
app: nginx
223+
template:
224+
metadata:
225+
labels:
226+
app: nginx
227+
spec:
228+
containers:
229+
- name: nginx
230+
image: nginx:1.29.7-alpine
231+
ports:
232+
- containerPort: 80
233+
patches:
234+
- type: FromCompositeFieldPath
235+
fromFieldPath: spec.replicas
236+
toFieldPath: spec.replicas
237+
- type: ToCompositeFieldPath
238+
fromFieldPath: status.readyReplicas
239+
toFieldPath: status.replicas
240+
- step: automatically-detect-readiness
241+
functionRef:
242+
name: function-auto-ready
243+
```
244+
245+
Save this as `composition-scale.yaml` and apply it:
246+
247+
```shell
248+
kubectl apply -f composition-scale.yaml
249+
```
250+
251+
Verify the composition exists:
252+
253+
```shell {copy-lines="1"}
254+
kubectl get compositions
255+
NAME XR-KIND XR-APIVERSION AGE
256+
myapp MyApp example.org/v1alpha1 3s
257+
```
258+
259+
The composition must also write back the current replica count to
260+
`status.replicas` (and `status.labelSelector` if used) so that autoscalers and
261+
`kubectl scale --current-replicas` read the correct replica count.
262+
263+
## Create a `MyApp` composite resource
264+
265+
With the XRD and Composition in place, create a `MyApp` composite resource:
266+
267+
```yaml
268+
apiVersion: example.org/v1alpha1
269+
kind: MyApp
270+
metadata:
271+
name: my-app
272+
spec:
273+
replicas: 1
274+
```
275+
276+
Verify the current replica count:
277+
278+
```shell {copy-lines="1"}
279+
kubectl get myapp/my-app
280+
NAME DESIRED CURRENT SYNCED READY COMPOSITION AGE
281+
my-app 1 1 True True myapp 108s
282+
```
283+
284+
## Use the scale subresource
285+
286+
After applying the XRD and Composition, scale a composite resource with
287+
`kubectl scale`:
288+
289+
```shell
290+
kubectl scale myapp/my-app --replicas=3
291+
```
292+
293+
Verify the current replica count:
294+
295+
```shell {copy-lines="1"}
296+
kubectl get myapp/my-app
297+
NAME DESIRED CURRENT SYNCED READY COMPOSITION AGE
298+
my-app 3 3 True True myapp 3m26s
299+
```

utils/vale/styles/Crossplane/allowed-jargon.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ API's
44
APIs
55
ARM64
66
autoscaler
7+
autoscalers
78
backoff
89
backported
910
base64

utils/vale/styles/Crossplane/brands.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CC-BY
55
CloudNativePG
66
CloudSQL
77
CNCF
8+
KEDA
89
Commonmark
910
DockerHub
1011
DocSearch

utils/vale/styles/Crossplane/spelling-exceptions.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Service-specific
7777
space-delimited
7878
status-checking
7979
step-by-step
80+
subresource
8081
subresources
8182
System-level
8283
/tab

0 commit comments

Comments
 (0)