Skip to content

Commit 3c27d58

Browse files
authored
Merge pull request crossplane#6398 from adamwg/awg/function-revisions-design
Add a one-pager regarding controlled rollout of composition functions
2 parents 62caa0a + 5681480 commit 3c27d58

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# Controlled Rollout of Composition Functions
2+
3+
* Owner: Adam Wolfe Gordon (@adamwg)
4+
* Reviewers: Crossplane Maintainers
5+
* Status: Accepted
6+
7+
## Background
8+
9+
Crossplane allows multiple revisions of a composition to be available via the
10+
CompositionRevision API. An XR can specify which revision to use by name or
11+
label selector, with the newest revision being used by default. This allows
12+
users to gradually roll out new revisions of compositions by either manually or
13+
automatically (via a controller) updating the composition revision in use for
14+
each XR.
15+
16+
With composition functions, some or all composition logic moves out of the
17+
composition itself and into functions. Crossplane allows only a single revision
18+
of a function to be active at once; the active revision is the only one with a
19+
corresponding deployment in the control plane. While composition changes can
20+
still be gradually rolled out, function changes are all or nothing: the new
21+
version of a function is used by all composition revisions, and therefore all
22+
XRs, immediately.
23+
24+
With "generic" functions such as `function-go-templating` or `function-kcl` that
25+
take source code written inline in the composition as input, this is generally a
26+
tolerable problem. The functions themselves change slowly compared with the
27+
compositions using them, and the code that directly composes resources is
28+
versioned with the compositions as it would be when using patch and
29+
transform. The requirement to inline code into YAML also naturally keeps the
30+
custom code users write for these functions relatively small and easy to
31+
inspect.
32+
33+
With non-generic functions, the code responsible for composing resources lives
34+
entirely outside of the composition and may be arbitrarily complex. This means
35+
composition revisions cannot be used to gradually roll out changes to
36+
composition logic: the logic is in functions, which have only one active
37+
revision.
38+
39+
## Goals
40+
41+
* Allow users to progressively roll out changes to functions that compose
42+
resources. This solves [crossplane#6139].
43+
* Maintain current behavior for users who do not wish to roll out function
44+
changes progressively.
45+
46+
## Proposal
47+
48+
This document proposes two changes in Crossplane to allow for progressive
49+
rollout of composition functions:
50+
51+
1. Allow multiple revisions of a function to be active (i.e., able to serve
52+
requests) at once, and
53+
2. Allow composition pipeline steps to reference a specific function revision so
54+
that different revisions of a composition can use different revisions of a
55+
function.
56+
57+
To limit risk, both of these changes will be introduced behind a feature flag,
58+
which will initially be off by default. Since neither change is useful by
59+
itself, a single feature flag will control both.
60+
61+
### Package Manager Changes
62+
63+
Crossplane already supports multiple revisions of function packages; however,
64+
only one revision at a time can be active. The active revision is the only one
65+
with a running deployment and there is a single endpoint (Kubernetes Service)
66+
for each function. The service is updated by Crossplane to point at the active
67+
revision’s deployment, but the endpoint is recorded in each function revision
68+
resource and the composition controller looks up endpoints by finding the active
69+
revision.
70+
71+
To allow for multiple active revisions, we will add a new field to the
72+
`Function` resource called `activeRevisionLimit`. This setting controls how many
73+
revisions Crossplane will keep active at any given time. Its name mirrors the
74+
`revisionHistoryLimit` setting and its value must be no greater than the
75+
`revisionHistoryLimit`. By default, the `activeRevisionLimit` will be 1,
76+
maintaining today’s behavior. For all package types other than `Function`, 1
77+
will be the only valid value for `activeRevisionLimit`, since multiple active
78+
revisions do not make sense for providers or configurations.
79+
80+
For example, to maintain up to four revisions and up to two active revisions,
81+
the user would create a function resource like the following:
82+
83+
```yaml
84+
apiVersion: pkg.crossplane.io/v1
85+
kind: Function
86+
metadata:
87+
name: function-patch-and-transform
88+
spec:
89+
package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
90+
revisionHistoryLimit: 4
91+
activeRevisionLimit: 2
92+
```
93+
94+
When `revisionActivationPolicy` is `Automatic`, the revisions with the highest
95+
revision numbers (up to the limit) will be active. If a new revision is created
96+
and `activeRevisionLimit` revisions are already active, the active one with the
97+
lowest revision number will be deactivated. When `revisionActivationPolicy` is
98+
`Manual`, `activeRevisionLimit` is ignored by the package manager and it's left
99+
to users to activate and deactivate revisions as they wish.
100+
101+
We will update the package manager’s runtime to name services after the
102+
associated package revision rather than the package, and create a service per
103+
active revision. This is required to allow multiple active revisions to serve
104+
traffic. The endpoints used by the composition controller to connect to
105+
functions are already recorded in the function revision resources, so this is a
106+
natural change (each active function revision will now have a distinct
107+
endpoint).
108+
109+
One additional change is necessary in the package manager to enable the
110+
composition changes described below. The package manager needs to copy labels
111+
from packages to their revisions, so that users can set a label on a function
112+
and then use it to select the relevant revision in their composition. Labels on
113+
compositions already work this way (they get copied to composition revisions),
114+
so this change will make package revisions more similar to composition
115+
revisions.
116+
117+
### Composition Changes
118+
119+
To enable compositions to refer to specific function revisions, we will add two
120+
new optional fields to composition pipeline steps: `functionRevisionRef` and
121+
`functionRevisionSelector`, mirroring the `compositionRevisionRef` and
122+
`compositionRevisionSelector` found in XRs. These new fields will allow
123+
compositions to select a specific function revision by name or by label.
124+
125+
For example, a user wishing to roll out a new version of
126+
`function-patch-and-transform` to only certain XRs may update their existing
127+
installation of the function by applying the following manifest:
128+
129+
```yaml
130+
apiVersion: pkg.crossplane.io/v1
131+
kind: Function
132+
metadata:
133+
name: function-patch-and-transform
134+
labels:
135+
release-channel: alpha
136+
spec:
137+
package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
138+
revisionHistoryLimit: 4
139+
activeRevisionLimit: 2
140+
```
141+
142+
The package manager will create a new function revision with the
143+
`release-channel: alpha` label. The user would then update their composition to
144+
reference the labeled function revision, causing a new composition revision to
145+
be created:
146+
147+
```yaml
148+
apiVersion: apiextensions.crossplane.io/v1
149+
kind: Composition
150+
metadata:
151+
name: example
152+
labels:
153+
release-channel: alpha
154+
spec:
155+
compositeTypeRef:
156+
apiVersion: custom-api.example.org/v1alpha1
157+
kind: AcmeBucket
158+
mode: Pipeline
159+
pipeline:
160+
- step: patch-and-transform
161+
functionRef:
162+
name: function-patch-and-transform
163+
functionRevisionSelector:
164+
matchLabels:
165+
release-channel: alpha
166+
input:
167+
# Removed for brevity
168+
```
169+
170+
Finally, they would update one or more XRs to use the new composition revision:
171+
172+
```yaml
173+
apiVersion: custom-api.example.org/v1alpha1
174+
kind: AcmeBucket
175+
metadata:
176+
name: my-bucket
177+
spec:
178+
compositionUpdatePolicy: Manual
179+
compositionRevisionSelector:
180+
matchLabels:
181+
release-channel: alpha
182+
```
183+
184+
Today, the function runner invoked by the composition controller finds the
185+
function endpoint for a pipeline step by first listing all function revisions
186+
with the `pkg.crossplane.io/package` label set to the function's name, then
187+
finding the single active revision in the list. This logic will change as
188+
follows:
189+
190+
1. If `functionRevisionRef` is specified, the revision will be fetched directly
191+
by name.
192+
2. If `functionRevisionSelector` is specified, the relevant `matchLabels` will
193+
be used when listing revisions (in addition to `pkg.crossplane.io/package`).
194+
3. When iterating over multiple returned revisions (because the
195+
`functionRevisionSelector` is not given or matches multiple revisions), the
196+
highest numbered active revision will be used.
197+
198+
Note that in the case where no function revision is specified, and there is only
199+
one active revision for the function, the behavior will not change from today.
200+
201+
If no active function revision is found for a pipeline step, the composition
202+
pipeline will fail. In this case, the composition controller should set an error
203+
condition on the XR and raise an event.
204+
205+
## Alternatives Considered
206+
207+
* Do nothing. For functions that don't take input, users can work around the
208+
limitation today by installing multiple versions of a function with unique
209+
names and referencing these names in their compositions. This workaround
210+
effectively simulates what is proposed in this design without support from
211+
Crossplane itself. However, it does not work for functions that take input due
212+
to [crossplane#5294], and does not work well with the package manager's
213+
dependency resolution system when using Configurations.
214+
* Make it easier for composition functions to take input from external sources
215+
such as OCI images or git repositories. This would allow function input to be
216+
versioned separately from both functions and compositions, with new
217+
composition revisions being created to reference new input versions. However,
218+
this change would introduce significant additional complexity to the
219+
composition controller, and would not solve the problem for functions that
220+
don't take input.
221+
222+
[crossplane#6139]: https://github.com/crossplane/crossplane/issues/6139
223+
[crossplane#5294]: https://github.com/crossplane/crossplane/issues/5294

0 commit comments

Comments
 (0)