@@ -4,7 +4,7 @@ Author: @sttts @embik
4
4
5
5
Initial implementation: @vincepri
6
6
7
- Last Updated on: 2025-01-06
7
+ Last Updated on: 2025-01-07
8
8
9
9
## Table of Contents
10
10
@@ -118,11 +118,10 @@ type Provider interface {
118
118
}
119
119
```
120
120
121
- A cluster provider is responsible for constructing a ` cluster.Cluster ` instance
122
- upon calls to ` Get(ctx, clusterName) ` and returning it. Providers should keep track
123
- of created clusters and return them again if the same name is requested. Since
124
- providers are responsible for constructing the ` cluster.Cluster ` instance, they
125
- can make decisions about e.g. reusing existing informers.
121
+ A cluster provider is responsible for constructing ` cluster.Cluster ` instances and returning
122
+ upon calls to ` Get(ctx, clusterName) ` . Providers should keep track of created clusters and
123
+ return them again if the same name is requested. Since providers are responsible for constructing
124
+ the ` cluster.Cluster ` instance, they can make decisions about e.g. reusing existing informers.
126
125
127
126
The ` cluster.Cluster ` _ SHOULD_ be extended with a unique name identifier:
128
127
@@ -159,9 +158,9 @@ type Aware interface {
159
158
}
160
159
```
161
160
162
- ` ctrl.Manager ` will implement ` cluster.Aware ` . It is the cluster provider's responsibility
163
- to call ` Engage ` and ` Disengage ` on a ` ctrl.Manager ` instance when clusters join or leave
164
- the set of target clusters that should be reconciled.
161
+ ` ctrl.Manager ` will implement ` cluster.Aware ` . As specified in the ` Provider ` interface,
162
+ it is the cluster provider's responsibility to call ` Engage ` and ` Disengage ` on a ` ctrl.Manager `
163
+ instance when clusters join or leave the set of target clusters that should be reconciled.
165
164
166
165
The internal ` ctrl.Manager ` implementation in turn will call ` Engage ` and ` Disengage ` on all
167
166
its runnables that are cluster-aware (i.e. that implement the ` cluster.Aware ` interface).
@@ -182,7 +181,42 @@ The multi-cluster controller implementation reacts to engaged clusters by starti
182
181
a new ` TypedSyncingSource ` that also wraps the context passed down from the call to ` Engage ` ,
183
182
which _ MUST_ be canceled by the cluster provider at the end of a cluster's lifecycle.
184
183
185
- Instead of extending ` reconcile.Request ` , implementations _ SHOULD_ bring their
184
+ The ` ctrl.Manager ` _ SHOULD_ be extended by a ` cluster.Cluster ` getter:
185
+
186
+ ``` golang
187
+ // pkg/manager
188
+ type Manager interface {
189
+ // ...
190
+ GetCluster (ctx context.Context , clusterName string ) (cluster.Cluster , error )
191
+ }
192
+ ```
193
+
194
+ The embedded ` cluster.Cluster ` corresponds to ` GetCluster(ctx, "") ` . We call the
195
+ clusters with non-empty name "provider clusters" or "enganged clusters", while
196
+ the embedded cluster of the manager is called the "default cluster" or "hub
197
+ cluster".
198
+
199
+ To provide information about the source cluster of a request, a new type
200
+ ` reconcile.ClusterAwareRequest ` _ SHOULD_ be added:
201
+
202
+ ``` golang
203
+ // pkg/reconcile
204
+ type ClusterAwareRequest struct {
205
+ Request
206
+ ClusterName string
207
+ }
208
+ ```
209
+
210
+ This struct embeds a ` reconcile.Request ` to store the "usual" information (name and namespace)
211
+ about an object, plus the name of the originating cluster.
212
+
213
+ Given that an empty cluster name represents the "default cluster", a ` reconcile.ClusterAwareRequest `
214
+ can be used as ` request ` type even for controllers that do not have an active cluster provider.
215
+ The cluster name will simply be an empty string, which is compatible with calls to ` mgr.GetCluster ` .
216
+
217
+ ### BYO Request Type
218
+
219
+ Instead of using the new ` reconcile.ClusterAwareRequest ` , implementations _ CAN_ also bring their
186
220
own request type through the generics support in ` Typed* ` types (` request comparable ` ).
187
221
188
222
Optionally, a passed ` TypedEventHandler ` will be duplicated per engaged cluster if they
@@ -200,29 +234,12 @@ This might be necessary if a BYO `TypedEventHandler` needs to store information
200
234
the engaged cluster (e.g. because the events do not supply information about the cluster in
201
235
object annotations) that it has been started for.
202
236
203
- The ` ctrl.Manager ` _ SHOULD_ be extended by a ` cluster.Cluster ` getter:
204
-
205
- ``` golang
206
- // pkg/manager
207
- type Manager interface {
208
- // ...
209
- GetCluster (ctx context.Context , clusterName string ) (cluster.Cluster , error )
210
- }
211
- ```
212
-
213
- The embedded ` cluster.Cluster ` corresponds to ` GetCluster(ctx, "") ` . We call the
214
- clusters with non-empty name "provider clusters" or "enganged clusters", while
215
- the embedded cluster of the manager is called the "default cluster" or "hub
216
- cluster".
217
-
218
-
219
237
### Multi-Cluster-Compatible Reconcilers
220
238
221
239
Reconcilers can be made multi-cluster-compatible by changing client and cache
222
240
accessing code from directly accessing ` mgr.GetClient() ` and ` mgr.GetCache() ` to
223
- going through ` mgr.GetCluster(clusterName).GetClient() ` and
224
- ` mgr.GetCluster(clusterName).GetCache() ` . ` clusterName ` needs to be extracted from
225
- the BYO ` request ` type (e.g. a ` clusterName ` field in the type itself).
241
+ going through ` mgr.GetCluster(req.ClusterName).GetClient() ` and
242
+ ` mgr.GetCluster(req.ClusterName).GetCache() ` .
226
243
227
244
A typical snippet at the beginning of a reconciler to fetch the client could look like this:
228
245
@@ -234,7 +251,7 @@ if err != nil {
234
251
client := cl.GetClient ()
235
252
```
236
253
237
- Due to the BYO ` request ` type, controllers using the ` For ` builder function need to be built/changed like this :
254
+ Due to ` request.ClusterAwareRequest ` , changes to the controller builder process are minimal :
238
255
239
256
``` golang
240
257
// previous
@@ -244,32 +261,19 @@ builder.TypedControllerManagedBy[reconcile.Request](mgr).
244
261
Complete (reconciler)
245
262
246
263
// new
247
- builder.TypedControllerManagedBy [ClusterRequest ](mgr).
264
+ builder.TypedControllerManagedBy [reconcile. ClusterAwareRequest ](mgr).
248
265
Named (" multi-cluster-controller" ).
249
- Watches (&corev1.Pod {}, &ClusterRequestEventHandler {}).
266
+ For (&corev1.Pod {}).
250
267
Complete (reconciler)
251
268
```
252
269
253
- With ` ClusterRequest ` and ` ClusterRequestEventHandler ` being BYO types. ` reconciler `
254
- can be e.g. of type ` reconcile.TypedFunc[ClusterRequest] ` .
255
-
256
- ` ClusterRequest ` will likely often look like this (but since it is a BYO type, it could store other information as well):
257
-
258
- ``` golang
259
- type ClusterRequest struct {
260
- reconcile.Request
261
- ClusterName string
262
- }
263
- ```
264
-
265
- Controllers that use ` Owns ` cannot be converted to multi-cluster controllers
266
- without a BYO type re-implementation of ` handler.EnqueueRequestForOwner ` matching
267
- the BYO type, which is considered out of scope for now.
270
+ The builder will chose the correct ` EventHandler ` implementation for both ` For ` and ` Owns `
271
+ depending on the ` request ` type used.
268
272
269
- With the described changes (use ` GetCluster(ctx, clusterName ) ` , making ` reconciler `
270
- a ` TypedFunc[ClusterRequest] ` and migrating to ` Watches ` ) an existing controller will automatically act as
271
- * uniform multi-cluster controller* . It will reconcile resources from cluster ` X `
272
- in cluster ` X ` .
273
+ With the described changes (use ` GetCluster(ctx, req.ClusterName ) ` , making ` reconciler `
274
+ a ` TypedFunc[reconcile.ClusterAwareRequest] ` ) an existing controller will automatically act as
275
+ * uniform multi-cluster controller* if a cluster provider is configured.
276
+ It will reconcile resources from cluster ` X ` in cluster ` X ` .
273
277
274
278
For a manager with ` cluster.Provider ` , the builder _ SHOULD_ create a controller
275
279
that sources events ** ONLY** from the provider clusters that got engaged with
@@ -313,20 +317,19 @@ builder.NewControllerManagedBy(mgr).
313
317
### Controller Author without self-interest in multi-cluster, but open for adoption in multi-cluster setups
314
318
315
319
- Replace ` mgr.GetClient() ` and ` mgr.GetCache ` with ` mgr.GetCluster(req.ClusterName).GetClient() ` and ` mgr.GetCluster(req.ClusterName).GetCache() ` .
316
- - Switch from ` For ` and ` Owns ` builder calls to ` watches `
317
320
- Make manager and controller plumbing vendor'able to allow plugging in multi-cluster provider and BYO request type.
318
321
319
322
### Controller Author who wants to support certain multi-cluster setups
320
323
321
324
- Do the ` GetCluster ` plumbing as described above.
322
- - Vendor 3rd-party multi-cluster providers and wire them up in ` main.go `
325
+ - Vendor 3rd-party multi-cluster providers and wire them up in ` main.go ` .
323
326
324
327
## Risks and Mitigations
325
328
326
329
- The standard behaviour of controller-runtime is unchanged for single-cluster controllers.
327
- - The activation of the multi-cluster mode is through attaching the ` cluster.Provider ` to the manager.
328
- To make it clear that the semantics are experimental, we name the ` manager.Options ` field
329
- ` ExperimentalClusterProvider ` .
330
+ - The activation of the multi-cluster mode is through usage of a ` request.ClusterAwareRequest ` request type and
331
+ attaching the ` cluster.Provider ` to the manager. To make it clear that the semantics are experimental, we name
332
+ the ` manager.Options ` field ` ExperimentalClusterProvider ` .
330
333
- We only extend these interfaces and structs:
331
334
- ` ctrl.Manager ` with ` GetCluster(ctx, clusterName string) (cluster.Cluster, error) ` and ` cluster.Aware ` .
332
335
- ` cluster.Cluster ` with ` Name() string ` .
0 commit comments