-
Notifications
You must be signed in to change notification settings - Fork 1.2k
✨Allow controllers to be started and stopped separately from the manager #863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Welcome @negz! |
Hi @negz. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/assign @gerred |
/ok-to-test love this, really elegant solution |
This seems a breaking change for clients, unless I'm missing something? |
@vincepri Could you elaborate on what makes it a breaking change? It's not immediately obvious to me. All existing functions, and methods continue to exist and function close to identically. Running through the changes:
Is it the third change you're concerned about? I typically would not consider new methods to be a breaking change, but I could see how it would be if we assume clients may be maintaining their own implementations of |
Makes sense, I missed that last call to mgr.Add in the |
Could you also add a test similar to the goroutine test for starting/stopping a controller using this code? |
Happy to add a test - are you thinking of a test like the existing one that ensures goroutines are reaped when we stop a manager? |
I was thinking of that test case, but doing that for one controller pretty much devolves to the existing manager and controller test cases. Nevermind 👍 |
@vincepri Sure. Where is the best place to add examples? Also, I wonder how far such an example should go? I imagine that few folks will ever call func ConfigureExample(m ctrl.Manager) error {
// A reconciler that knows how to reconcile *v1alpha1.Example
r := &ExampleReconciler{}
// Configure creates a new controller but does not add it to the supplied manager.
c, err := controller.Configure("example", m, controller.Options{Reconciler: r})
if err != nil {
return err
}
// Request that our configured controller watch for *v1alpha1.Example
if err := c.Watch(&source.Kind{Type: &v1alpha1.Example{}}, &handler.EnqueueRequestForObject{}); err != nil {
return err
}
// Create a stop channel for our controller. The controller will stop when this
// channel is closed.
stop := make(chan struct{})
// Start our controller in a goroutine so that we do not block.
go func() {
// Block until our controller manager is elected leader. We presume our entire
// process will terminate if we lose leadership, so we don't need to handle that.
<-m.Elected()
// Start our controller. This will block until the stop channel is closed, or the
// controller returns an error.
if err := c.Start(stop); err != nil {
log.Error(err, "cannot run experiment controller")
}
}()
return nil
} |
I think, if we decide an example is appropriate, that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work.
Can you close the stop channel in the example just to make it super explicit you can individually stop this controller too? I see the comment but code always speaks louder for me Pending the checked-in example, lgtm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My goal is not to arrive at the ideal API for this concept, but rather to enable the use case with the smallest possible change to controller-runtime
I would like to mention that this is not what we should strive for, this project is a library that needs to be easy to use and understand and be as compatible as possible. Keeping the code change for a given feature small is less important than making sure we expose easy to understand apis we feel comfortable maintaining long term.
That being said, I like how this is implemented, just a couple of nits around godocs and naming.
@alvaroaleman That's fair. A more direct way for me to frame this would have been "my goal is to enable access to this functionality without blocking on redesigning graceful termination in controller-runtime". I had this in mind because the request for this functionality (#730) was rolled into the broader graceful termination redesign issue (#764). |
I believe all feedback has now been addressed! |
As far as I can tell the main reason we add controllers to the manager is to ensure that we've been elected leader before we start any controllers. The downside of this design is that it's not possible to stop individual controllers, or remove controllers from the manager. I believe this commit is the minimum possible change necessary to allow controllers to be started and stopped on-demand. It allows a controller to be created, started, and stopped without ever being added to the manager. Any controller that is started separately from the manager must handle its own leader election. kubernetes-sigs#730 Signed-off-by: Nic Cope <[email protected]>
Most fields are set explicitly when we create a new controller, but this one relied solely on the controller manager performing injection during the Add phase. Signed-off-by: Nic Cope <[email protected]>
Signed-off-by: Nic Cope <[email protected]>
Signed-off-by: Nic Cope <[email protected]>
It's more intuitive to close the channel when a process (i.e. election) has completed, rather than when a state (i.e. leading) has been entered. Signed-off-by: Nic Cope <[email protected]>
(Or when no election is desired.) Signed-off-by: Nic Cope <[email protected]>
@DirectXMan12 I've added a tiny test for the I agree in principle that expanding the method set of a public interface is a breaking change, as API consumers might be defining types that would satisfy the interface before the change, but would not after. However in practice I'm skeptical that there are alternative implementations of the |
⚒️ add additional controller tests
@negz: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
/hold cancel |
Thanks for helping us get this merged @alexeldeib! |
kubernetes-sigs/controller-runtime#863 We'd like to use the above PR, which is not yet included in a controller-runtime release. Updating controller-runtime requires updating a few other dependencies, which changed the signature of client-go clientset methods. This commit removes the only two uses of clientset from crossplane-runtime. pkg/test/integration now uses a controller-runtime client.Client. pkg/test.Env has been removed, as it no longer has any known users. Signed-off-by: Nic Cope <[email protected]>
I'm excited about this change but wanted to point out some practicalities that we might want to document:
Also - would it make sense to bound an unmanaged controller's lifespan to that of the manager given that the manager shutting down would also shut down dependencies of the controller (e.g. the cache)? |
https://pkg.go.dev/github.com/crossplane/crossplane-runtime/pkg/controller o := kcontroller.Options{Reconciler: r}
c.Start(requirement.ControllerName(p.GetName()), o,
controller.For(rq, &handler.EnqueueRequestForObject{}),
controller.For(cp, &EnqueueRequestForRequirement{})) @shomron we wrote a little wrapper to help with this that couples cache lifecycles to controller lifecycles. I agree it's not ideal, but it does the trick. it seems like there's a broader refactor in the wings for controller lifecycle management which will hopefully improve on this. |
Fixes #730
Increasingly in Crossplane we find that we'd like to use one kind of custom resource to describe how another, arbitrary, kind of custom resource should be reconciled. For example a
kind: Template
resource may configure how akind: Widget
resource should be reconciled. This involves the controller that watches forkind: Template
managing the lifecycles of another set of controllers, including the controller that watches forkind: Widget
. This is not currently possible in controller-runtime because all controllers are implicitly added to and started by the controller manager, which has no facility for stopping or removing a controller.This PR makes it possible to start, run, and stop a controller without ever adding the controller to the controller manager. My goal is not to arrive at the ideal API for this concept, but rather to enable the use case with the smallest possible change to controller-runtime. I imagine this will be niche functionality, and functionality that is slated for a broader refactoring at some point per #764.
I've tested this functionality using negz/crossplane@28e76ec, which contains an example implementation of a controller that starts and stops other controllers.