-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Proposal for Lifecycle Hooks #1171
Changes from 1 commit
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 |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# Lifecycle Hooks | ||
|
||
**Author**: @tnozicka | ||
|
||
**Status**: Proposal | ||
|
||
**RFC Issue**: https://github.com/kubernetes/kubernetes/issues/14512 | ||
|
||
## Abstract | ||
The intent of this proposal it to support lifecycle hooks in all objects having rolling/recreate update; currently those are Deployments, StatefulSets and DaemonSets. | ||
Lifecycle hooks should allow users to better customize updates (rolling/recreate) without having to write their own controllers. | ||
|
||
## Lifecycle Hooks | ||
Lifecycle hook is a Job that will get triggered by the update reaching certain progress point (like 50%). | ||
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. Are these generic "lifecycle" hooks or just "update progress" hooks? The naming implies they are hooks that could apply at other points in the lifecycle (scale up, down, thresholds, delete) but they are only used for updates. Fix the naming of the usages? |
||
The progress point is determined by the ratio of new.availableReplicas to the declared replicas or by explicitly stating the number of new.availableReplicas. | ||
|
||
### Previous Implementations | ||
Lifecycle hooks are already implemented in OpenShift but we want to enhance them and implement them natively in Kubernetes. | ||
- [OpenShift proposal](https://github.com/openshift/origin/blob/master/docs/proposals/post-deployment-hooks.md) | ||
- [OpenShift docs](https://docs.openshift.org/latest/dev_guide/deployments/deployment_strategies.html#lifecycle-hooks) | ||
|
||
### Previous Kubernetes Proposals | ||
- https://github.com/kubernetes/kubernetes/pull/33545 | ||
|
||
### Use Cases | ||
The most common use case for lifecycle hooks is to have pre, mid and/or post hook. It is mostly used to run some kind of acceptance check in the middle and/or at the end to fully verify the update is working as expected and rollback if it isn't. | ||
The acceptance check may be time consuming and more thorough than what readiness and liveness probes are intended for. | ||
You can also notify external services from them, migrate database in the middle of an update, send messages to IRC channel or do anything else. | ||
|
||
[A short demo](https://youtu.be/GVNTm_K43iI) simulating lifecycle hooks using auto-pausing that has been presented at SIG-Apps meeting on August 21, 2017. | ||
|
||
#### Reusability | ||
If you are a big shop and you are running several instances of e.g. your database you want to reuse definition of lifecycle hooks e.g. for several instances of your database. | ||
This is reflected in the design bellow by having separate object to define lifecycle hooks and reference it from the objects. | ||
|
||
If you would be worried about having shared definitions so e.g. your mistakes won't spread too much you can always choose not to share those definitions and reference unique lifecycle hook objects from every instance. | ||
|
||
## API Objects | ||
### New Objects | ||
```go | ||
type LifecycleTemplate struct { | ||
TypeMeta | ||
ObjectMeta | ||
Spec LifecycleTemplateSpec | ||
} | ||
|
||
type LifecycleTemplateSpec struct { | ||
// "Always" - keep all Jobs | ||
// "OnFailure" - keep only failed Jobs | ||
// "Never" - delete Jobs immediately | ||
RetainPolicy string | ||
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. Is there ever a case where this policy might differ between hooks within the same template? 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. Different hooks can have different priority (say notifying IRC vs. migrating a database); this way you can choose to retain only some of them. 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. Those use cases seem to provide an argument for retention policy being associated with each LifecycleHook rather than the LifecycleTemplateSpec. 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. Moving this to per hook then ;) |
||
|
||
// After reaching this limit the Job history will be pruned. | ||
RevisionHistoryLimit *int32 | ||
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. This seems a bit inflexible given the 1:1 relationship between a LifecycleHook and Job, especially given the size of Hooks is variable. Does it make any sense for the history limit to be per hook? 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. Both options make sense to me; limit hooks history per rollout and per hook. Other option is to start with no limiting because with owner references it will be automatically limited by the RevisionHistoryLimit from the deployment 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.
Interesting point. I see there was some prior discussion of job pruning. My current thought is the proposal would be simplified by removing any sort of bespoke pruning capability. Pruning of jobs generally seems orthogonal. 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 am fine starting small without any explicit pruning. |
||
|
||
// List of lifecycle hooks | ||
// Can have multiple hooks at the same ProgressPoint; order independent | ||
Hooks []LifecycleHook | ||
} | ||
|
||
type LifecycleHook struct { | ||
// Unique name | ||
Name string | ||
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. Unique in what scope? 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. In the scope of |
||
|
||
// ProgressPoint specifies the point during update when the hook should be triggered. | ||
// Accepts both the number of new pods available or a percentage value representing the ratio | ||
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. If we are going to implement something like this then isn't autopausing an easier thing to implement and handle arbitrary workflow execution (start a job or really anything else) outside of the core controllers? 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.
From the scaling point of view, the amount of work will be slightly less as this will be like setting a partition, only internally, without exposing it in the API (yet). We can as well expose the partition in API (auto-pausing) but I didn't want to mix it into this proposal.
I'd prefer to see it in apps. I think this would be beneficial and helpful for the users - I don't want to decouple this and have it only in some distribution just because we can. |
||
// of new.availableReplicas to the declared replicas. In case of getting a percentage | ||
// it will try to reach the exact or the closest possible point right after it, | ||
// trigger the job, wait for it to complete and then continue. | ||
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. Document what happens with edges - 0, "0%", and "100%" |
||
// If such situation shall occur that two different ProgressPoints should be reached at | ||
// the same time, all the hooks for an earlier ProgressPoint will be ran (and finished) | ||
// before any later one. | ||
ProgressPoint intstr.IntOrString | ||
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. Has anybody yet envisioned another triggering criterion? If we think we might come up with more, a single field coupled to availableReplicas will become messy and we might want to talk about a new type here. 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 don't have a use case not covered by 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. please let's not use this type any more. It was a bad idea. Make individual fields with semantically significant names. |
||
|
||
// "Abort" - Failure for this hook is fatal, deployment is considered failed and should be rollbacked. | ||
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. There is no such thing as automatic rollback today in Deployments so in terms of what we have the deployment is stuck. If we implement automatic rollback here we should also think of the case where we don't run any hooks (only progressDeadlineSeconds). |
||
// "Ignore" - Even if the hook fails deployment continues rolling out new version. | ||
FailurePolicy string | ||
|
||
JobSpec v1.JobSpec | ||
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. How do I parameterize the job spec based on data in my deployment? Or would I have to create one hook per deployment in that case? 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. The plan was to inject some environment variables inside (like image) but I failed to mention it in the proposal 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. Is there any way for downward API to help here? The platform choosing a static and arbitrary set of fields to expose as (arbitrary) env vars is definitely useful, but allowing the users to select what data to expose and as mounted files seems very interesting to me. Crazy? 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 like the idea of extending downward API to cover this use case where the objects are separate. That would allow you to pass any deployment property and we could inject just limited and necessary number of envs like the previous image or similar. 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.
Expanding downward API to be cross-object is a pretty big change... is that desired? If so, we'd need to be really careful:
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. Reusing existing downward API could be a good argument for having the PolicyTemplate embedded in the object (Deployment, ...) at the expense of re-usability. 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.
Talked some with @tnozicka about this offline and I'm now convinced that the lifecycle hook makes more sense embedded into the deployment spec itself rather than as a reference. Being able to use the downward API for the job template seems more generally useful than reusing hook templates across deployments. Deployments themselves (and any hooks defined within) could be templated in other ways. |
||
} | ||
|
||
type LifecycleHookStatus struct { | ||
// Name of the hook | ||
Name string | ||
|
||
ProgressPoint intstr.IntOrString | ||
|
||
// States: "Running", "Succeeded", "Failed" | ||
State string | ||
|
||
// Reference to locate the Job created when executing the hook | ||
JobRef LocalObjectReference | ||
} | ||
|
||
type LifecycleHookRevisionStatus struct { | ||
// Revision of the object that this hook was run for | ||
Revision string | ||
|
||
LifecycleHookStatuses []LifecycleHookStatus | ||
} | ||
``` | ||
|
||
### Affected Objects | ||
As of now the lifecycle hooks are relevant for Deployments, StatefulSets and DaemonSets. | ||
They will require new optional field to be able to reference LifecycleTemplate and extending its status by []LifecycleHookRevisionStatus. | ||
Also the controller will need to be slightly adjusted to scale in appropriate chunks and trigger the hooks. | ||
|
||
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. Do you think that we might start with Deployment (its the most utilized and generally its used for stateless non-critical components), promote a stable implementation there, and then adapt that implementation to StatefulSet and DaemonSet? 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. Yes, my plan is to start with Deployments. |
||
```go | ||
type DeploymentSpec struct { | ||
// ... | ||
// Addition | ||
LifecycleTemplate *ObjectReference | ||
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 if I want to control the rollout process? Isn't that a job itself? 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 be a separate strategy that forces the controller to ignore the deployment. Wouldn't that process need to implement the hooks api? Sounds like a separate design proposal. |
||
} | ||
|
||
type DeploymentStatus struct { | ||
// ... | ||
// Addition | ||
LifecycleHookRevisionStatuses []LifecycleHookRevisionStatus | ||
} | ||
``` | ||
|
||
```go | ||
type StatefulSetSpec struct { | ||
// ... | ||
// Addition | ||
LifecycleTemplate *ObjectReference | ||
} | ||
|
||
type StatefulSetStatus struct { | ||
// ... | ||
// Addition | ||
LifecycleHookRevisionStatuses []LifecycleHookRevisionStatus | ||
} | ||
|
||
``` | ||
|
||
```go | ||
type DaemonSetSpec struct { | ||
// ... | ||
// Addition | ||
LifecycleTemplate *ObjectReference | ||
} | ||
|
||
type DaemonSetStatus struct { | ||
// ... | ||
// Addition | ||
LifecycleHookRevisionStatuses []LifecycleHookRevisionStatus | ||
} | ||
``` | ||
|
||
## Algorithm | ||
If there is a LifecycleTemplate referenced from an object: | ||
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. Is this an owner reference? 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. there is an explicit reference from DeploymentSpec, StatefulSetSpec and DaemonSetSpec - |
||
|
||
1. Calculate next partition point to reach the closest lifecycle hook progress point and scale replicas in update appropriately. If there is no hook remaining, GOTO 5. | ||
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.
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. Some concrete examples for 3:
I think at least once is probably ok, but I don't know that all users would expect at-least-once. Similar to daemonset discussion - will users naturally assume these are at-most-once, and if so, will they experience catastrophic failure due to it? 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. Of the examples from the previous comment, I think all of them imply the controller MUST block until the job completes. Not running a database migration could be catastrophic. 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.
Yes. (Although there is always possibility of adding an option to customize it.)
User will get whatever guarantees Kubernetes Job gives him. (But it should be idempotent.)
Nothing, once we reach certain progress point we trigger the hook and never go back. 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. need to think about details of rollback in case of 2. more but it should be based on revision 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.
Would this be the same mechanism we use to determine whether a replicaset exists for a given deployment? That is, a deterministically named job based on the pod template spec hash. 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 we could associate all the hooks (Jobs) by setting controllerRef to particular RS. If we decide to run hooks for rollbacks, those could be distinguished by label. We would have probably done it either way for purposes of adoption and garbage collection. |
||
2. When partition point is reached, run the hook by creating a Job using LifecycleHook.JobSpec | ||
3. | ||
1. If the hook failed and FailurePolicy is Abort - emit event, fail the update and initiate rollback (GOTO 5.) | ||
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. Define rollback here. I'm not sure whether you mean "give up on the new deployment and mutate the deployment back to the old revision" or "go into very long backoff". I.e. perm-fail vs not-perm fail 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 mean fail this rollout for good and rollback to previous (working) revision |
||
2. If the hook failed and FailurePolicy is Ignore - only emit event | ||
4. GOTO 1. | ||
5. Finish | ||
|
||
(In case of rollover cancel any hooks running and don't execute new ones.) | ||
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 do you mean by rollover? 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. |
||
|
||
This can be applied to all the existing update strategies as that's essentially dependent only on the ability to have a certain ratio of new.availableReplicas to the declared replicas and running the hook between changing that ratio. |
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.
All docs in the design-proposals directly belong in SIG-specific subdirectories
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.
Yes, I'm gonna move it to KEP; this structure is year old based on what was there at the time it started.