@@ -4,7 +4,7 @@ Author: @sttts @embik
44
55Initial implementation: @vincepri
66
7- Last Updated on: 2025-01-06
7+ Last Updated on: 2025-01-07
88
99## Table of Contents
1010
@@ -118,11 +118,10 @@ type Provider interface {
118118}
119119```
120120
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.
126125
127126The ` cluster.Cluster ` _ SHOULD_ be extended with a unique name identifier:
128127
@@ -159,9 +158,9 @@ type Aware interface {
159158}
160159```
161160
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.
165164
166165The internal ` ctrl.Manager ` implementation in turn will call ` Engage ` and ` Disengage ` on all
167166its 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
182181a new ` TypedSyncingSource ` that also wraps the context passed down from the call to ` Engage ` ,
183182which _ MUST_ be canceled by the cluster provider at the end of a cluster's lifecycle.
184183
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
186220own request type through the generics support in ` Typed* ` types (` request comparable ` ).
187221
188222Optionally, 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
200234the engaged cluster (e.g. because the events do not supply information about the cluster in
201235object annotations) that it has been started for.
202236
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-
219237### Multi-Cluster-Compatible Reconcilers
220238
221239Reconcilers can be made multi-cluster-compatible by changing client and cache
222240accessing 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() ` .
226243
227244A typical snippet at the beginning of a reconciler to fetch the client could look like this:
228245
@@ -234,7 +251,7 @@ if err != nil {
234251client := cl.GetClient ()
235252```
236253
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 :
238255
239256``` golang
240257// previous
@@ -244,32 +261,19 @@ builder.TypedControllerManagedBy[reconcile.Request](mgr).
244261 Complete (reconciler)
245262
246263// new
247- builder.TypedControllerManagedBy [ClusterRequest ](mgr).
264+ builder.TypedControllerManagedBy [reconcile. ClusterAwareRequest ](mgr).
248265 Named (" multi-cluster-controller" ).
249- Watches (&corev1.Pod {}, &ClusterRequestEventHandler {}).
266+ For (&corev1.Pod {}).
250267 Complete (reconciler)
251268```
252269
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.
268272
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 ` .
273277
274278For a manager with ` cluster.Provider ` , the builder _ SHOULD_ create a controller
275279that sources events ** ONLY** from the provider clusters that got engaged with
@@ -313,20 +317,19 @@ builder.NewControllerManagedBy(mgr).
313317### Controller Author without self-interest in multi-cluster, but open for adoption in multi-cluster setups
314318
315319- 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 `
317320- Make manager and controller plumbing vendor'able to allow plugging in multi-cluster provider and BYO request type.
318321
319322### Controller Author who wants to support certain multi-cluster setups
320323
321324- 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 ` .
323326
324327## Risks and Mitigations
325328
326329- 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 ` .
330333- We only extend these interfaces and structs:
331334 - ` ctrl.Manager ` with ` GetCluster(ctx, clusterName string) (cluster.Cluster, error) ` and ` cluster.Aware ` .
332335 - ` cluster.Cluster ` with ` Name() string ` .
0 commit comments