-
Notifications
You must be signed in to change notification settings - Fork 1.3k
✨ Add pre-start hook support to manager #3429
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
base: main
Are you sure you want to change the base?
Changes from all commits
07216dd
9c4c8f0
24887b2
489ced4
f7cb56b
60a2b1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,7 @@ const ( | |
| defaultRenewDeadline = 10 * time.Second | ||
| defaultRetryPeriod = 2 * time.Second | ||
| defaultGracefulShutdownPeriod = 30 * time.Second | ||
| defaultHookPeriod = 15 * time.Second | ||
|
|
||
| defaultReadinessEndpoint = "/readyz" | ||
| defaultLivenessEndpoint = "/healthz" | ||
|
|
@@ -166,6 +167,13 @@ type controllerManager struct { | |
| // internalProceduresStop channel is used internally to the manager when coordinating | ||
| // the proper shutdown of servers. This channel is also used for dependency injection. | ||
| internalProceduresStop chan struct{} | ||
|
|
||
| // prestartHooks are functions that are run immediately before calling the Start functions | ||
| // of the leader election runnables. | ||
| prestartHooks []Runnable | ||
|
|
||
| // hookTimeout is the duration given to each hook to return successfully. | ||
| hookTimeout time.Duration | ||
| } | ||
|
|
||
| type hasCache interface { | ||
|
|
@@ -240,6 +248,24 @@ func (cm *controllerManager) GetHTTPClient() *http.Client { | |
| return cm.cluster.GetHTTPClient() | ||
| } | ||
|
|
||
| // Hook allows you to add hooks. | ||
| func (cm *controllerManager) Hook(hook HookType, runnable Runnable) error { | ||
| cm.Lock() | ||
| defer cm.Unlock() | ||
|
|
||
| if cm.started { | ||
| return fmt.Errorf("unable to add new hook because the manager has already been started") | ||
| } | ||
|
|
||
| switch hook { | ||
| case HookPrestartType: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be HookTypePrestart? |
||
| cm.prestartHooks = append(cm.prestartHooks, runnable) | ||
| return nil | ||
| } | ||
|
|
||
| return errors.New("hook type not supported") | ||
| } | ||
|
|
||
| func (cm *controllerManager) GetConfig() *rest.Config { | ||
| return cm.cluster.GetConfig() | ||
| } | ||
|
|
@@ -648,6 +674,27 @@ func (cm *controllerManager) initLeaderElector() (*leaderelection.LeaderElector, | |
| } | ||
|
|
||
| func (cm *controllerManager) startLeaderElectionRunnables() error { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's rename the func to startPreStartHooksAndLeaderElectionRunnables |
||
| cm.logger.Info("Running prestart hooks") | ||
| for _, hook := range cm.prestartHooks { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other than that being what you found in the original PR, what is the reason for starting the hooks sequentially and not in parallel? Couldn't the sequential starting be achieved if the user instead submitted one hook that does everything sequentlally?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably need to think about how we would collect errors from pre-start hooks on startup and use that to bail out if there are any errors, but that sounds like it would be fairly straightforward with a waitgroup
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree. I think we should start them in parallel |
||
| var ctx context.Context | ||
| var cancel context.CancelFunc | ||
|
|
||
| if cm.hookTimeout < 0 { | ||
| ctx, cancel = context.WithCancel(cm.internalCtx) | ||
| } else { | ||
| ctx, cancel = context.WithTimeout(cm.internalCtx, cm.hookTimeout) | ||
| } | ||
|
|
||
| defer cancel() | ||
|
|
||
| if err := hook.Start(ctx); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| // All the prestart hooks have ben run, clear the slice to free the underlying resources. | ||
| cm.prestartHooks = nil | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a log here |
||
|
|
||
| return cm.runnables.LeaderElection.Start(cm.internalCtx) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,6 +82,9 @@ type Manager interface { | |
| // AddReadyzCheck allows you to add Readyz checker | ||
| AddReadyzCheck(name string, check healthz.Checker) error | ||
|
|
||
| // Hook allows to add Runnables as hooks to modify the behavior. | ||
| Hook(hook HookType, runnable Runnable) error | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know @vincepri had suggested this name in the other PR, but I am not a huge fan because IMHO:
I would suggest But lets wait for a second opinion from @sbueringer before you end up changing this back and forth
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, AddPrestartHook sounds good. Is there some prior art from other components (kube-controller-manager, other controller frameworks) that might give us an idea how likely it is that we'll have other types of hooks in the future and what these might be? (but if we don't have more data let's go with AddPrestartHook) Hm. Is "Prestart" precise enough to describe "before leader election runnables"? No strong opinion, but it's pretty generic.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. + "to modify the behavior" sounds strange. Let's try to explain this a bit better |
||
|
|
||
| // Start starts all registered Controllers and blocks until the context is cancelled. | ||
| // Returns an error if there is an error starting any controller. | ||
| // | ||
|
|
@@ -281,6 +284,10 @@ type Options struct { | |
| // +optional | ||
| Controller config.Controller | ||
|
|
||
| // HookTimeout is the duration given to each hook to return successfully. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WDYT about the idea of having a per-hook timeout rather than a global one for all hooks? Different hook may need different timeouts |
||
| // To use hooks without timeout, set to a negative duration, e.g. time.Duration(-1) | ||
| HookTimeout *time.Duration | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When @leontappe and I evaluated the proposed change in the old PR we decided to keep the Given that a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the comparisons are not great, I would probably build this as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer an explicit value meaning no timeout that we document personally, so I'd say if this is 0, it's no timeout
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What other timeout fields do we have in CR as prior art? Or do we only have GracefulShutdownTimeout? (just to end up with something somewhat consistent, it makes sense to make different decisions if the cases are different but we should minimize variations if possible) |
||
|
|
||
| // makeBroadcaster allows deferring the creation of the broadcaster to | ||
| // avoid leaking goroutines if we never call Start on this manager. It also | ||
| // returns whether or not this is a "owned" broadcaster, and as such should be | ||
|
|
@@ -295,6 +302,15 @@ type Options struct { | |
| newPprofListener func(addr string) (net.Listener, error) | ||
| } | ||
|
|
||
| // HookType defines hooks for use with AddHook. | ||
| type HookType int | ||
|
|
||
| const ( | ||
| // HookPrestartType defines a hook that is run after leader election and immediately before | ||
| // calling Start on the runnables that needed leader election. | ||
| HookPrestartType HookType = iota | ||
| ) | ||
|
|
||
| // BaseContextFunc is a function used to provide a base Context to Runnables | ||
| // managed by a Manager. | ||
| type BaseContextFunc func() context.Context | ||
|
|
@@ -465,6 +481,7 @@ func New(config *rest.Config, options Options) (Manager, error) { | |
| livenessEndpointName: options.LivenessEndpointName, | ||
| pprofListener: pprofListener, | ||
| gracefulShutdownTimeout: *options.GracefulShutdownTimeout, | ||
| hookTimeout: *options.HookTimeout, | ||
| internalProceduresStop: make(chan struct{}), | ||
| leaderElectionStopped: make(chan struct{}), | ||
| leaderElectionReleaseOnCancel: options.LeaderElectionReleaseOnCancel, | ||
|
|
@@ -577,6 +594,11 @@ func setOptionsDefaults(config *rest.Config, options Options) (Options, error) { | |
| options.GracefulShutdownTimeout = &gracefulShutdownTimeout | ||
| } | ||
|
|
||
| if options.HookTimeout == nil { | ||
| hookTimeout := defaultHookPeriod | ||
| options.HookTimeout = &hookTimeout | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: if we keep this let's use ptr.To() |
||
| } | ||
|
|
||
| if options.Logger.GetSink() == nil { | ||
| options.Logger = log.Log | ||
| } | ||
|
|
||
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.
nit