Skip to content

Commit 5c6fb4b

Browse files
authored
Merge pull request #7932 from killianmuldoon/variable-discovery-proposal
🌱 Add variable discovery to topology mutation proposal
2 parents 8a30fc1 + 4f1aadb commit 5c6fb4b

File tree

2 files changed

+227
-12
lines changed

2 files changed

+227
-12
lines changed

docs/book/src/tasks/experimental-features/runtime-sdk/implement-topology-mutation-hook.md

Lines changed: 205 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ Please note Runtime SDK is an advanced feature. If implemented incorrectly, a fa
1010

1111
## Introduction
1212

13-
The Topology Mutation Hooks are going to be called during each Cluster topology reconciliation. More specifically
14-
we are going to call two different hooks for each reconciliation:
13+
Three different hooks are called as part of Topology Mutation - two in the Cluster topology reconciler and one in the ClusterClass reconciler.
1514

15+
**Cluster topology reconciliation**
1616
* **GeneratePatches**: GeneratePatches is responsible for generating patches for the entire Cluster topology.
1717
* **ValidateTopology**: ValidateTopology is called after all patches have been applied and thus allow to validate
1818
the resulting objects.
1919

20+
**ClusterClass reconciliation**
21+
* **DiscoverVariables**: DiscoverVariables is responsible for providing variable definitions for a specific external patch.
22+
2023
![Cluster topology reconciliation](../../../images/runtime-sdk-topology-mutation.png)
2124

2225
Please see the corresponding [CAEP](https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20220330-topology-mutation-hook.md)
23-
for additional background information.
26+
for additional background information.
2427

2528
## Inline vs. external patches
2629

@@ -35,11 +38,121 @@ External patches have the following advantages:
3538
* External patches can use external data (e.g. from cloud APIs) during patch generation.
3639
* External patches can be easily reused across ClusterClasses.
3740

41+
## External variable definitions
42+
The DiscoverVariables hook can be used to supply variable definitions for use in external patches. These variable definitions are added to
43+
the status of any applicable ClusterClasses. Clusters using the ClusterClass can then set values for those variables.
44+
45+
### External variable discovery in the ClusterClass
46+
External variable definitions are discovered by calling the DiscoverVariables runtime hook. This hook is called from the ClusterClass reconciler.
47+
Once discovered the variable definitions are validated and stored in ClusterClass status.
48+
49+
```yaml
50+
apiVersion: cluster.x-k8s.io/v1beta1
51+
kind: ClusterClass
52+
# metadata
53+
spec:
54+
# Inline variable definitions
55+
variables:
56+
# This variable is unique and can be accessed globally.
57+
- name: no-proxy
58+
required: true
59+
schema:
60+
openAPIV3Schema:
61+
type: string
62+
default: "internal.com"
63+
example: "internal.com"
64+
description: "comma-separated list of machine or domain names excluded from using the proxy."
65+
# This variable is also defined by an external DiscoverVariables hook.
66+
- name: http-proxy
67+
schema:
68+
openAPIV3Schema:
69+
type: string
70+
default: "proxy.example.com"
71+
example: "proxy.example.com"
72+
description: "proxy for http calls."
73+
# External patch definitions.
74+
patches:
75+
- name: lbImageRepository
76+
external:
77+
generateExtension: generate-patches.k8s-upgrade-with-runtimesdk
78+
validateExtension: validate-topology.k8s-upgrade-with-runtimesdk
79+
## Call variable discovery for this patch.
80+
discoverVariablesExtension: discover-variables.k8s-upgrade-with-runtimesdk
81+
status:
82+
# observedGeneration is used to check that the current version of the ClusterClass is the same as that when the Status was previously written.
83+
# if metadata.generation isn't the same as observedGeneration Cluster using the ClusterClass should not reconcile.
84+
observedGeneration: xx
85+
# variables contains a list of all variable definitions, both inline and from external patches, that belong to the ClusterClass.
86+
variables:
87+
- name: no-proxy
88+
definitions:
89+
- namespace: inline
90+
required: true
91+
schema:
92+
openAPIV3Schema:
93+
type: string
94+
default: "internal.com"
95+
example: "internal.com"
96+
description: "comma-separated list of machine or domain names excluded from using the proxy."
97+
- name: http-proxy
98+
definitions:
99+
- namespace: inline
100+
schema:
101+
openAPIV3Schema:
102+
type: string
103+
default: "proxy.example.com"
104+
example: "proxy.example.com"
105+
description: "proxy for http calls."
106+
- namespace: lbImageRepository
107+
schema:
108+
openAPIV3Schema:
109+
type: string
110+
default: "different.example.com"
111+
example: "different.example.com"
112+
description: "proxy for http calls."
113+
```
114+
115+
### Variable namespacing
116+
Variable definitions can be inline in the ClusterClass or from any number of external DiscoverVariables hooks. The source
117+
of a variable definition is recorded in the `namespace` field in ClusterClass `.status.variables`.
118+
Variables that are defined by an external DiscoverVariables hook will have the name of the patch they are associated with as their namespace.
119+
Variables that are defined in the ClusterClass `.spec.variables` will have the namespace `inline`.
120+
Note: `inline` is a reserved namespace. It can not be used as the name of an external patch to avoid conflicts.
121+
122+
If all variables that share a name have equivalent schemas the variables are considered `global` . `global` variables can
123+
be set without providing a namespace - [see below](#setting-values-for-variables-in-the-cluster). The CAPI components will
124+
consider variable definitions to be equivalent when they share a name and their schema is exactly equal.
125+
126+
### Setting values for variables in the Cluster
127+
Setting variables that are defined with external variable definitions requires attention to be paid to variable namespacing, as exposed in the ClusterClass status.
128+
Variable values are set in Cluster `.spec.topology.variables`.
129+
130+
```yaml
131+
apiVersion: cluster.x-k8s.io/v1beta1
132+
kind: Cluster
133+
#metadata
134+
spec:
135+
topology:
136+
variables:
137+
# namespace is not needed as this variable is global.
138+
- name: no-proxy
139+
value: "internal.domain.com"
140+
# namespaced variables require values for each individual schema.
141+
- name: http-proxy
142+
namespace: inline
143+
value: http://proxy.example2.com:1234
144+
- name: http-proxy
145+
namespace: lbImageRepository
146+
value:
147+
host: proxy.example2.com
148+
port: 1234
149+
```
150+
38151
## Using one or multiple external patch extensions
39152

40153
Some considerations:
41154
* In general a single external patch extension is simpler than many, as only one extension
42-
then has to be built, deployed and managed.
155+
then has to be built, deployed and managed.
43156
* A single extension also requires less HTTP round-trips between the CAPI controller and the extension(s).
44157
* With a single extension it is still possible to implement multiple logical features using different variables.
45158
* When implementing multiple logical features in one extension it's recommended that they can be conditionally
@@ -50,8 +163,9 @@ Some considerations:
50163
## Guidelines
51164

52165
For general Runtime Extension developer guidelines please refer to the guidelines in [Implementing Runtime Extensions](implement-extensions.md#guidelines).
53-
This section outlines considerations specific to Topology Mutation hooks:
166+
This section outlines considerations specific to Topology Mutation hooks.
54167

168+
### Patch extension guidelines
55169
* **Input validation**: An External Patch Extension must always validate its input, i.e. it must validate that
56170
all variables exist, have the right type and it must validate the kind and apiVersion of the templates which
57171
should be patched.
@@ -68,9 +182,19 @@ This section outlines considerations specific to Topology Mutation hooks:
68182
* **Avoid Dependencies**: An External Patch Extension must be independent of other External Patch Extensions. However
69183
if dependencies cannot be avoided, it is possible to control the order in which patches are executed via the ClusterClass.
70184
* **Error messages**: For a given request (a set of templates and variables) an External Patch Extension must
71-
always return the same error message. Otherwise the system might became unstable due to controllers being overloaded
185+
always return the same error message. Otherwise the system might become unstable due to controllers being overloaded
72186
by continuous changes to Kubernetes resources as these messages are reported as conditions. See [error messages](implement-extensions.md#error-messages).
73187

188+
### Variable discovery guidelines
189+
* **Distinctive variable names**: Names should be carefully chosen, and if possible generic names should be avoided.
190+
Using a generic name could lead to conflicts if the variables defined for this patch are used in combination with other
191+
patches providing variables with the same name.
192+
* **Avoid breaking changes to variable definitions**: Changing a variable definition can lead to problems on existing
193+
clusters because reconciliation will stop if variable values do not match the updated definition. When more than one variable
194+
with the same name is defined, changes to variable definitions can require explicit values for each patch.
195+
Updates to the variable definition should be carefully evaluated, and very well documented in extension release notes,
196+
so ClusterClass authors can evaluate impacts of changes before performing an upgrade.
197+
74198
## Definitions
75199

76200
### GeneratePatches
@@ -194,6 +318,81 @@ function openSwaggerUI() {
194318
}
195319
</script>
196320

321+
### DiscoverVariables
322+
323+
A DiscoverVariables call returns definitions for one or more variables.
324+
325+
#### Example Request:
326+
327+
* The request is a simple call to the Runtime hook.
328+
329+
```yaml
330+
apiVersion: hooks.runtime.cluster.x-k8s.io/v1alpha1
331+
kind: DiscoverVariablesRequest
332+
settings: <Runtime Extension settings>
333+
```
334+
335+
#### Example Response:
336+
337+
```yaml
338+
apiVersion: hooks.runtime.cluster.x-k8s.io/v1alpha1
339+
kind: DiscoverVariablesResponse
340+
status: Success # or Failure
341+
message: ""
342+
variables:
343+
- name: etcdImageTag
344+
required: true
345+
schema:
346+
openAPIV3Schema:
347+
type: string
348+
default: "3.5.3-0"
349+
example: "3.5.3-0"
350+
description: "etcdImageTag sets the tag for the etcd image."
351+
- name: preLoadImages
352+
required: false
353+
schema:
354+
openAPIV3Schema:
355+
default: []
356+
type: array
357+
items:
358+
type: string
359+
description: "preLoadImages sets the images for the docker machines to preload."
360+
- name: podSecurityStandard
361+
required: false
362+
schema:
363+
openAPIV3Schema:
364+
type: object
365+
properties:
366+
enabled:
367+
type: boolean
368+
default: true
369+
description: "enabled enables the patches to enable Pod Security Standard via AdmissionConfiguration."
370+
enforce:
371+
type: string
372+
default: "baseline"
373+
description: "enforce sets the level for the enforce PodSecurityConfiguration mode. One of privileged, baseline, restricted."
374+
audit:
375+
type: string
376+
default: "restricted"
377+
description: "audit sets the level for the audit PodSecurityConfiguration mode. One of privileged, baseline, restricted."
378+
warn:
379+
type: string
380+
default: "restricted"
381+
description: "warn sets the level for the warn PodSecurityConfiguration mode. One of privileged, baseline, restricted."
382+
...
383+
```
384+
385+
For additional details, you can see the full schema in <button onclick="openSwaggerUI()">Swagger UI</button>.
386+
TODO: Add openAPI definition to the SwaggerUI
387+
<script>
388+
// openSwaggerUI calculates the absolute URL of the RuntimeSDK YAML file and opens Swagger UI.
389+
function openSwaggerUI() {
390+
var schemaURL = new URL("runtime-sdk-openapi.yaml", document.baseURI).href
391+
window.open("https://editor.swagger.io/?url=" + schemaURL)
392+
}
393+
</script>
394+
395+
197396
## Dealing with Cluster API upgrades with apiVersion bumps
198397

199398
There are some special considerations regarding Cluster API upgrades when the upgrade includes a bump

docs/proposals/20220330-topology-mutation-hook.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ title: Topology Mutation Hook
33
authors:
44
- "@sbueringer"
55
- "@fabriziopandini"
6+
- "@killianmuldoon"
67
reviewers:
78
- "@CecileRobertMichon"
89
- "@enxebre"
910
- "@vincepri"
10-
- "@killianmuldoon"
1111
- "@ykakarap"
1212
creation-date: 2022-03-30
1313
last-updated: 2022-03-30
@@ -58,8 +58,11 @@ Refer to the [Cluster API Book Glossary](https://cluster-api.sigs.k8s.io/referen
5858

5959
- **Inline patches**: are defined inline in a ClusterClass and implemented by the core CAPI controller.
6060
- **External patches**: are patches generated by an external component.
61-
- **Topology Mutation Hook**: is the hook defined in this proposal that allows users to plug in an external component that generates patches.
61+
- **Topology Mutation Hook**: is a hook defined in this proposal that allows users to plug in an external component that generates patches.
6262
- **External patch extension**: is an external component that generates patches.
63+
- **Inline variables**: are variables defined inline in a ClusterClass.
64+
- **External variables**: are variables defined by an external component.
65+
- **Variable Discovery Hook**: is a hook defined in this proposal that allows an external component to supply variable definitions.
6366

6467
## Summary
6568

@@ -96,7 +99,6 @@ The main idea behind Topology Mutation Hook is to move the complexity that is cu
9699

97100
### Future work
98101

99-
* Explore a solution how External Patch Extensions can bring their own variable definitions to shift the responsibility of variable definition and management from ClusterClass authors to External Patch Extension authors. For now it’s the responsibility of the ClusterClass author.
100102
* Explore a solution to detect and prevent an External Patch Extension to trigger infinite reconciles
101103

102104

@@ -115,11 +117,12 @@ As an External Patch Extension developer:
115117
* I want to unit test the code/logic which generates external patches.
116118
* I want to be able to generate external patches in either JSON Patch or JSON Merge Patch format.
117119
* I want to generate external patches based on external data, for example by querying a cloud API.
120+
* I want to supply the variable definitions, including schema and defaulting rules, for variables used in external patches.
118121
* I want to validate the templates after all patches have been applied, so I can be sure that other External Patch Extensions didn't overwrite my changes.
119122

120123
### Cluster Operator guide
121124

122-
As a Cluster operator, to use ClusterClasses with an External Patch Extensions you have to deploy and register it. You can find the full documentation on how to deploy a Runtime Extension in the [Runtime SDK proposal](https://github.com/kubernetes-sigs/cluster-api/blob/75b39db545ae439f4f6203b5e07496d3b0a6aa75/docs/proposals/20220221-runtime-SDK.md#deploy-runtime-extensions).
125+
As a Cluster operator, to use ClusterClasses with an External Patch Extension you have to deploy and register it. You can find the full documentation on how to deploy a Runtime Extension in the [Runtime SDK proposal](https://github.com/kubernetes-sigs/cluster-api/blob/75b39db545ae439f4f6203b5e07496d3b0a6aa75/docs/proposals/20220221-runtime-SDK.md#deploy-runtime-extensions).
123126

124127
An External Patch Extension can be registered by applying:
125128
```yaml
@@ -138,7 +141,7 @@ Once the extension is registered the discovery hook is called and the Extension
138141
139142
### ClusterClass author guide
140143
141-
A ClusterClass author can use an External Patch Extension by referencing it in a ClusterClass and adding the corresponding variable definitions.
144+
A ClusterClass author can use an External Patch Extension by referencing it in a ClusterClass.
142145
143146
A ClusterClass can have external patches, inline patches or both. The patches will then be applied in the order in which
144147
they are defined. The extension fields of the external patch must match the unique name of RuntimeExtensions assigned during discovery.
@@ -155,6 +158,7 @@ spec:
155158
- name: external-patch-1
156159
external:
157160
generateExtension: "http-proxy.my-awesome-patch"
161+
discoverVariablesExtension: "variables.my-awesome-patch"
158162
validateExtension: "http-proxy-validate.my-awesome-patch"
159163
settings:
160164
firstSetting: "red"
@@ -165,7 +169,10 @@ spec:
165169
...
166170
```
167171

168-
If the External Patch Extension requires variable definitions, they have to be added to ClusterClass.spec.variables. It is up to the External Patch Extension developer to document them including their OpenAPI schema. As future work we will explore a solution to discover variable definitions from External Patch Extensions automatically.
172+
If the External Patch Extension requires variable definitions they must be defined and supplied using a Variable Discovery Hook. It is up to the External Patch Extension developer to define the variables, including their OpenAPI schema.
173+
174+
Note: In a previous version of this proposal variables defined inline in the ClusterClass `.spec` could be used in external patches.
175+
With the introduction of Variable Discovery variables used in an external patch must come from an associated DiscoverVariables hook.
169176

170177
### Developer guide
171178

@@ -224,6 +231,15 @@ Mitigations:
224231
* External Patch Extension developers should ensure fast responses under all circumstances.
225232
* Cluster operators can set a timeout on the RuntimeExtensionConfiguration to ensure Cluster topology reconciliation for all Clusters is not slowed down by one slow External Patch Extension. This only helps if the slow External Patch Extension is not used for all Clusters.
226233

234+
#### Clashing external variable definitions
235+
Variable definitions supplied externally by an External Patch Extension through a Variable Discovery Hook can change when the definition in the External Patch Extension changes. This can lead to a clash where variables that previously had the same name and definition no longer have the same definition.
236+
237+
Mitigations:
238+
* Variable Discovery Hooks allow addressing variables using namespacing, where the variable value setting in the Cluster
239+
includes the name of the Patch as a namespace.
240+
* ClusterClass authors should pro-actively test any changes to ClusterClasses and associated Runtime Extensions to avoid clashing variable definitions.
241+
* External Patch extension authors should extensively document their patches, variables and their usage.
242+
227243
## Alternatives
228244

229245
### Extending inline patches vs. introducing external patches

0 commit comments

Comments
 (0)