-
Notifications
You must be signed in to change notification settings - Fork 101
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
Add a "controller engine" that manages the lifecycles of controllers #160
Conversation
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 <negz@rk0n.org>
*/ | ||
|
||
// Package controller provides utilties for working with controllers. | ||
package controller |
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.
I have mixed feelings about this package name; I like how controller.NewEngine
reads but it seems like it will frequently conflict with controller-runtime/pkg/controller
.
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.
I prefer the symmetry despite the naming conflicts 👍
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.
I've so far worked around this by importing the controller-runtime/pkg/controller
as ctrlr
(which is close to our convention of importing sigs.k8s.io/controller-runtime
as ctrl
).
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.
kctrl
for controller-runtime
and ctrl
for ours?
A controller engine is somewhat like a controller "sub-manager", in that it's effectively a group of controllers. Unlike a typical controller manager, the lifecycle of the controllers an engine manages are not coupled to the lifecycle of the engine itself. An engine may be used by a parent controller to start and stop child controllers in accordance with configuration provided by the custom resource that the parent controller watches. Signed-off-by: Nic Cope <negz@rk0n.org>
*/ | ||
|
||
// Package controller provides utilties for working with controllers. | ||
package controller |
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.
I prefer the symmetry despite the naming conflicts 👍
// An Engine manages the lifecycles of controller-runtime controllers (and their | ||
// caches). The lifecycles of the controllers are not coupled to lifecycle of | ||
// the engine, nor to the lifecycle of the controller manager it uses. | ||
type Engine struct { |
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.
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.
Yeah, that's the idea. I liked the ControllerEngine
abstraction around the controller-runtime machinery, and it seems like we'll probably have at least 2-3 controllers that are managing groups of child controllers, so I wanted to clean it up a little and add it here.
I've plumbed this up to @muvaf's work in crossplane/crossplane#1412 and run it to make sure it works as expected. |
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.
Thanks for moving this into crossplane-runtime. I'm suspicious whether it will be used by any other than core crossplane but it won't hurt to have it here either.
Could you also explain somewhere as comment why you needed to deal with concurrency issues?
*/ | ||
|
||
// Package controller provides utilties for working with controllers. | ||
package controller |
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.
kctrl
for controller-runtime
and ctrl
for ours?
e.newCtrl = fn | ||
} | ||
} | ||
|
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.
I see that there isn't WithLogger
option. Do you intend to keep engine itself log-less? The passed in reconcilers will have their own logger so it shouldn't be a big concern but just checking.
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.
Yeah, at least for now. I'm not worried about adding one, but I noticed the logs we had were mostly just duplicating existing logs from controller-runtime.
go func() { | ||
<-e.mgr.Elected() | ||
e.done(name, errors.Wrap(ca.Start(stop), errCrashCache)) | ||
}() |
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.
I don't exactly know why but controller-runtime waits for the cache to sync by calling ca.WaitForCacheSync(stop)
after running its go routine.
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.
Should be good to go after addressing this.
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.
I figured we could avoid this because controller.NewUnmanaged
calls WaitForCache
before it starts the controller.
// The default new cache and new controller functions. | ||
var ( | ||
DefaultNewCacheFn NewCacheFn = cache.New | ||
DefaultNewControllerFn NewControllerFn = controller.NewUnmanaged |
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.
Why export these and not directly use cache.New
and controller.NewUnmanaged
directly?
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.
Mostly just for documentation purposes - possibly a bit overkill but I figured it would make it obvious to GoDoc readers what the defaults were.
|
||
// Err returns any error encountered by the named controller. The returned error | ||
// is always nil if the named controller is running. | ||
func (e *Engine) Err(name string) error { |
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.
I see that you're not using Err
function in crossplane/crossplane#1440 How should this be used? Check on errors in every reconcile of whoever called Start
?
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.
Yeah, something like that. I figured it could be used to log or emit a condition if and when we discover that the controller has crashed. could be something like:
if !ctrl.IsRunning(name) {
if err := ctrl.Err(name); err != nil {
log.Debug("womp womp", "error", err)
}
_ = ctrl.Start(name, o)
}
@muvaf I wanted to allow the cache and controller goroutines a way to surface any error they encountered, and also to close the stop channel if they encountered an error (or just stopped for some other reason). This ensures that if the controller crashes it stops the cache too and vice versa. The map of stop channels can't be shared across goroutines without some kind of lock, and channels also panic if they're closed multiple times, so I ended up playing with a few patterns trying to use |
Description of your changes
A controller engine is somewhat like a controller "sub-manager", in that it's effectively a group of controllers. Unlike a typical controller manager, the lifecycle of the controllers an engine manages are not coupled to the lifecycle of the engine itself. An engine may be used by a parent controller to start and stop child controllers in accordance with configuration provided by the custom resource that the parent controller watches
Checklist
I have:
make reviewable
to ensure this PR is ready for review.clusterrole.yaml
to include any new types.