Skip to content

Commit 3f2fa4f

Browse files
committed
Adjust for reconcile.ClusterAwareRequest
Signed-off-by: Marvin Beckers <[email protected]>
1 parent 799a911 commit 3f2fa4f

File tree

1 file changed

+59
-56
lines changed

1 file changed

+59
-56
lines changed

designs/multi-cluster.md

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Author: @sttts @embik
44

55
Initial 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

127126
The `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

166165
The internal `ctrl.Manager` implementation in turn will call `Engage` and `Disengage` on all
167166
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
182181
a new `TypedSyncingSource` that also wraps the context passed down from the call to `Engage`,
183182
which _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
186220
own request type through the generics support in `Typed*` types (`request comparable`).
187221

188222
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
200234
the engaged cluster (e.g. because the events do not supply information about the cluster in
201235
object 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

221239
Reconcilers can be made multi-cluster-compatible by changing client and cache
222240
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()`.
226243

227244
A typical snippet at the beginning of a reconciler to fetch the client could look like this:
228245

@@ -234,7 +251,7 @@ if err != nil {
234251
client := 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

274278
For a manager with `cluster.Provider`, the builder _SHOULD_ create a controller
275279
that 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

Comments
 (0)