Lifecycle hooks
How convoy runs Jobs and Pods on the target cluster before and after a sync, on a sync failure, and on deletion.
A lifecycle hook is a Job or a Pod that convoy creates on the target cluster at a fixed point
of a sync or of a deletion. Use hooks for work that is not part of the rendered manifests.
Examples are database migrations, smoke tests and notifications. You write the hooks in the
top-level lifecycle field of the Release, or through the /lifecycle subresource. For the fields
and admission rules, see Release lifecycle hooks.
Example#
apiVersion: core.convoy.kumodo.io/v1alpha1
kind: Release
metadata:
name: podinfo-in-cluster-helm-with-lifecycle
namespace: default
lifecycle:
preSync:
- name: pre-sleep
type: Job
timeout: 30s
onFailure: Continue
cleanup: Never
deleteBeforeCreation: true
jobTemplate:
metadata:
labels:
test-label: "true"
annotations:
test-annotation: "true"
spec:
template:
spec:
restartPolicy: Never
containers:
- name: job-worker
image: "busybox:1.38.0"
command: ["sh", "-c", "sleep 10"]
postSync:
- name: post-sleep
type: Job
runAfter: Healthy
timeout: 30s
onFailure: Continue
cleanup: OnSuccess
jobTemplate:
metadata:
labels:
test-label: "true"
annotations:
test-annotation: "true"
spec:
template:
spec:
restartPolicy: Never
containers:
- name: job-worker
image: "busybox:1.38.0"
command: ["sh", "-c", "sleep 10"]
mutations:
- type: syncWave
waveStrategy:
strategy: waitForHealthy
wave: 1
target:
group: ""
version: v1
kind: Service
spec:
clusterRef:
name: dev-local
source:
type: gitRepository
gitRepository:
name: podinfo
namespace: default
ref:
branch: master
content:
type: helm
helm:
path: charts/podinfo
releaseName: podinfo-helm
values:
- type: inline
inline:
probes:
readiness:
initialDelaySeconds: 15
liveness:
initialDelaySeconds: 15
syncPolicy:
automatic: true
prune:
orphaned: false
includeCRDs: false
includeNamespaces: false
interval: 1m
healthAssessment:
globalRuleSetSelector:
matchLabels:
convoy.kumodo.io/default: "true"
deadline: 1m
Phases#
| Phase | When it runs |
|---|---|
preSync | After the dry run and before the first sync wave. A running hook holds the sync. |
postSync | After all waves are applied and orphans are pruned. |
syncFailed | After the sync fails in the apply stage. |
preDelete | When you delete the Release, before convoy deletes the applied objects. |
postDelete | After convoy deletes the applied objects. |
convoy runs preSync, postSync and syncFailed hooks one time for each render. A new render
runs them again. A reconcile of the same render does not run them again.
preSync#
While a preSync hook runs, convoy applies nothing. Applied is False with reason
WaitingForHook. If the Release holds at a cluster wave, the preSync
hooks do not start.
postSync and runAfter#
runAfter sets when a postSync hook starts. It is allowed only on postSync hooks.
Applied(default): the hook starts immediately after the apply.Healthy: the hook starts when all resources of the Release areHealthyfor the current render. This includes the resources of all waves.
Without spec.healthAssessment, Healthy has the same effect as Applied. runAfter only
controls the start of the hook. convoy does not check it again after the hook starts.
A postSync hook does not change the result of the sync. The apply occurred before the hook.
syncFailed#
syncFailed hooks run when the render succeeded, but a step of the apply stage failed. Examples
are a failed dry run, a failed apply, a failed prune, and a failed wave gate. They do not run when
these fail:
- the fetch of the source, or the render
- the target cluster
- a
preSynchook - a cluster wave timeout (
ClusterWaveFailed)
convoy starts syncFailed hooks and does not wait for them. Their result does not change the
sync. onFailure has no effect.
preDelete and postDelete#
convoy runs the delete hooks also when spec.deletionPolicy.keepWorkloads is true. convoy
skips the hooks in two cases:
- The
Clusterobject does not exist. - The target cluster is not reachable, and
blockOnClusterUnreachableisfalse.
With blockOnClusterUnreachable: true, an unreachable cluster blocks the deletion until it is
reachable. See Deletion.
The workload#
Each hook has type: Job with a jobTemplate, or type: Pod with a podTemplate. A template
has a partial metadata and a Kubernetes spec:
metadatacan set onlynamespace,labelsandannotations. Without anamespace, convoy uses the target namespace ofspec.content(releaseNamespaceortargetNamespace). If that is also empty, convoy usesdefault.- convoy sets the name of the workload. The name is
{phase}-{name}-{hash}, for examplepresync-migrate-1f2e3d4c. The phase is in lowercase. convoy shortens the hook name to 32 characters. The hash comes from the Release name, the hook name and the render. - convoy adds these labels. They replace your labels with the same key.
| Label | Value |
|---|---|
convoy.kumodo.io/managed-by | convoy |
convoy.kumodo.io/release-name | The name of the Release |
convoy.kumodo.io/release-namespace | The namespace of the Release |
convoy.kumodo.io/release-uid | The UID of the Release |
convoy.kumodo.io/hook-phase | The phase, for example PreSync |
convoy.kumodo.io/hook-name | The name of the hook |
The workload is on a different cluster, so it has no owner reference. convoy finds the workloads
of a hook with the release-uid, hook-phase and hook-name labels.
Templates and the if condition#
convoy renders the template as a Go template before it creates the workload. The if field is a
CEL expression. Both use the same values:
| Value | Contents |
|---|---|
cluster | The keys of spec.metadata of the Cluster, and cluster.name |
release | name, namespace, labels and annotations of the Release |
lifecycle:
postSync:
- name: smoke-test
type: Job
if: 'cluster.tier == "production"'
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: test
image: curlimages/curl:8.10.1
args: ["https://{{ .release.name }}.{{ .cluster.domain }}/healthz"]- A key that does not exist renders as an empty value. It does not cause an error.
- An empty
ifistrue. - If
ifisfalse, or it cannot be evaluated, convoy skips the hook. It creates no workload and records no entry. A skipped hook is not instatus.lifecycle. - Admission does not compile the
ifexpression or the template.
Mutations do not change hook templates. The mutation pipeline changes only the rendered objects.
Order in a phase#
weight puts the hooks of a phase in groups. convoy runs the groups in ascending order of
weight:
- convoy starts all hooks of the lowest group. Hooks with the same weight run in parallel.
- convoy waits until all hooks of the group finish.
- convoy cleans up the finished hooks and starts the next group.
- When the last group finishes, the phase is complete.
Timeout#
timeout is the maximum time from the creation of the workload. When the timeout passes and
the workload did not finish, the hook is TimedOut. Without timeout, convoy waits without a
time limit.
convoy does not stop the workload at the timeout. The cleanup policy can delete it.
Failure#
A hook fails when the Job has the Failed condition, or the Pod has the Failed phase. A
TimedOut hook is also a failure. onFailure sets what convoy does next:
onFailure | preSync | postSync | preDelete | postDelete |
|---|---|---|---|---|
empty or Continue | The phase continues. | The phase continues. | The phase continues. | The phase continues. |
Fail | The sync stops. Applied=False, reason HookFailed. | Later groups do not run. The apply stays. | The deletion stops. The finalizer stays. | convoy logs the failure and completes the deletion. |
convoy does not run a failed hook again for the same render. A new render runs it again.
Cleanup#
cleanup sets what convoy does with the workload when the hook finishes:
cleanup | Workload deleted when |
|---|---|
empty or Never | Never |
OnSuccess | The hook succeeded |
OnCompletion | The hook finished, also after a failure |
A hook that fails with onFailure: Fail stops the phase before the cleanup step. Thus, convoy
does not delete its workload, also with cleanup: OnCompletion.
deleteBeforeCreation: true deletes all earlier workloads of the hook before convoy creates the
new one. The name changes with each render. Thus, with cleanup: Never and
deleteBeforeCreation: false, one workload for each render stays on the cluster.
There is no garbage collection of hook workloads. convoy does not delete the workloads that stay after the deletion of the Release. A retention policy is planned. Not built
Status#
convoy records each hook in status.lifecycle, in a list for each phase:
status:
lifecycle:
preSync:
- name: migrate
source: lifecycle
kind: Job
apiVersion: batch/v1
resourceNamespace: default
resourceName: presync-migrate-1f2e3d4c
state: Failed
renderRef: sha256:…
followUpAction: Fail
message: …stateisRunning,Succeeded,FailedorTimedOut.followUpActionis theonFailurevalue. convoy sets it only for aFailedorTimedOuthook. WithoutonFailure, the value is empty, and the hook acts asContinue.- Each entry shows the last run of the hook.
renderRefis the render of that run. An entry from an earlier render stays until the hook runs again.
The LifecycleSucceeded condition sums up all entries:
| Entries | Status | Reason |
|---|---|---|
| No entry | The condition is not set. | |
At least one Failed or TimedOut | False | HookFailed |
At least one Running | Unknown | Running |
All Succeeded | True | AllSucceeded |
The condition reports a failure also when onFailure is Continue.
The Lifecycle printer column of kubectl get releases uses only the entries of the current
render. Its value is N/A, Running:<phase>, Failed:<phase> or Completed:<phase>.
| Field | Type | Description |
|---|---|---|
lifecycle | ReleaseLifecycle | ReleaseLifecycle defines the desired state of the ReleaseLifecycle. |
lifecycle.postDelete | []LifecycleHookSpeclist type: atomic | PostDelete are the post-delete hooks. |
lifecycle.postDelete[].cleanup required | string | Cleanup is the action to take when the hook completes.
|
lifecycle.postDelete[].deleteBeforeCreation | boolean | DeleteBeforeCreation is true if the hook should delete the resources before they are created. |
lifecycle.postDelete[].if | string | If is the hook execution condition written in CEL expression format. |
lifecycle.postDelete[].jobTemplate | LifecycleHookJobTemplate | JobTemplate is the job template for the hook. Mutual exclusive with PodTemplate. |
lifecycle.postDelete[].jobTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.postDelete[].jobTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.postDelete[].jobTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.postDelete[].jobTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.postDelete[].jobTemplate.spec required | JobSpec | Spec contains the Job spec. |
lifecycle.postDelete[].name required | string | Name is the name of the hook. |
lifecycle.postDelete[].onFailure required | string | OnFailure is the action to take when the hook fails.
|
lifecycle.postDelete[].podTemplate | LifecycleHookPodTemplate | PodTemplate is the pod template for the hook. Mutual exclusive with JobTemplate. |
lifecycle.postDelete[].podTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.postDelete[].podTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.postDelete[].podTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.postDelete[].podTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.postDelete[].podTemplate.spec required | PodSpec | Spec contains the Pod spec. |
lifecycle.postDelete[].runAfter | string | RunAfter defines the Release condition that should be achieved before running the PostSync lifecycle hook(s).
|
lifecycle.postDelete[].timeout | Duration | Timeout is the timeout for the hook. If not set, controller will wait indefinitely. |
lifecycle.postDelete[].type required | string | Type is the type of the hook.
|
lifecycle.postDelete[].weight required | integer | Weight is the weight of the hook. |
lifecycle.postSync | []LifecycleHookSpeclist type: atomic | PostSync are the post-sync hooks. |
lifecycle.postSync[].cleanup required | string | Cleanup is the action to take when the hook completes.
|
lifecycle.postSync[].deleteBeforeCreation | boolean | DeleteBeforeCreation is true if the hook should delete the resources before they are created. |
lifecycle.postSync[].if | string | If is the hook execution condition written in CEL expression format. |
lifecycle.postSync[].jobTemplate | LifecycleHookJobTemplate | JobTemplate is the job template for the hook. Mutual exclusive with PodTemplate. |
lifecycle.postSync[].jobTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.postSync[].jobTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.postSync[].jobTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.postSync[].jobTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.postSync[].jobTemplate.spec required | JobSpec | Spec contains the Job spec. |
lifecycle.postSync[].name required | string | Name is the name of the hook. |
lifecycle.postSync[].onFailure required | string | OnFailure is the action to take when the hook fails.
|
lifecycle.postSync[].podTemplate | LifecycleHookPodTemplate | PodTemplate is the pod template for the hook. Mutual exclusive with JobTemplate. |
lifecycle.postSync[].podTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.postSync[].podTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.postSync[].podTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.postSync[].podTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.postSync[].podTemplate.spec required | PodSpec | Spec contains the Pod spec. |
lifecycle.postSync[].runAfter | string | RunAfter defines the Release condition that should be achieved before running the PostSync lifecycle hook(s).
|
lifecycle.postSync[].timeout | Duration | Timeout is the timeout for the hook. If not set, controller will wait indefinitely. |
lifecycle.postSync[].type required | string | Type is the type of the hook.
|
lifecycle.postSync[].weight required | integer | Weight is the weight of the hook. |
lifecycle.preDelete | []LifecycleHookSpeclist type: atomic | PreDelete are the pre-delete hooks. |
lifecycle.preDelete[].cleanup required | string | Cleanup is the action to take when the hook completes.
|
lifecycle.preDelete[].deleteBeforeCreation | boolean | DeleteBeforeCreation is true if the hook should delete the resources before they are created. |
lifecycle.preDelete[].if | string | If is the hook execution condition written in CEL expression format. |
lifecycle.preDelete[].jobTemplate | LifecycleHookJobTemplate | JobTemplate is the job template for the hook. Mutual exclusive with PodTemplate. |
lifecycle.preDelete[].jobTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.preDelete[].jobTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.preDelete[].jobTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.preDelete[].jobTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.preDelete[].jobTemplate.spec required | JobSpec | Spec contains the Job spec. |
lifecycle.preDelete[].name required | string | Name is the name of the hook. |
lifecycle.preDelete[].onFailure required | string | OnFailure is the action to take when the hook fails.
|
lifecycle.preDelete[].podTemplate | LifecycleHookPodTemplate | PodTemplate is the pod template for the hook. Mutual exclusive with JobTemplate. |
lifecycle.preDelete[].podTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.preDelete[].podTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.preDelete[].podTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.preDelete[].podTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.preDelete[].podTemplate.spec required | PodSpec | Spec contains the Pod spec. |
lifecycle.preDelete[].runAfter | string | RunAfter defines the Release condition that should be achieved before running the PostSync lifecycle hook(s).
|
lifecycle.preDelete[].timeout | Duration | Timeout is the timeout for the hook. If not set, controller will wait indefinitely. |
lifecycle.preDelete[].type required | string | Type is the type of the hook.
|
lifecycle.preDelete[].weight required | integer | Weight is the weight of the hook. |
lifecycle.preSync | []LifecycleHookSpeclist type: atomic | PreSync are the pre-sync hooks. |
lifecycle.preSync[].cleanup required | string | Cleanup is the action to take when the hook completes.
|
lifecycle.preSync[].deleteBeforeCreation | boolean | DeleteBeforeCreation is true if the hook should delete the resources before they are created. |
lifecycle.preSync[].if | string | If is the hook execution condition written in CEL expression format. |
lifecycle.preSync[].jobTemplate | LifecycleHookJobTemplate | JobTemplate is the job template for the hook. Mutual exclusive with PodTemplate. |
lifecycle.preSync[].jobTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.preSync[].jobTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.preSync[].jobTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.preSync[].jobTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.preSync[].jobTemplate.spec required | JobSpec | Spec contains the Job spec. |
lifecycle.preSync[].name required | string | Name is the name of the hook. |
lifecycle.preSync[].onFailure required | string | OnFailure is the action to take when the hook fails.
|
lifecycle.preSync[].podTemplate | LifecycleHookPodTemplate | PodTemplate is the pod template for the hook. Mutual exclusive with JobTemplate. |
lifecycle.preSync[].podTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.preSync[].podTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.preSync[].podTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.preSync[].podTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.preSync[].podTemplate.spec required | PodSpec | Spec contains the Pod spec. |
lifecycle.preSync[].runAfter | string | RunAfter defines the Release condition that should be achieved before running the PostSync lifecycle hook(s).
|
lifecycle.preSync[].timeout | Duration | Timeout is the timeout for the hook. If not set, controller will wait indefinitely. |
lifecycle.preSync[].type required | string | Type is the type of the hook.
|
lifecycle.preSync[].weight required | integer | Weight is the weight of the hook. |
lifecycle.syncFailed | []LifecycleHookSpeclist type: atomic | SyncFailed are the sync-failed hooks. |
lifecycle.syncFailed[].cleanup required | string | Cleanup is the action to take when the hook completes.
|
lifecycle.syncFailed[].deleteBeforeCreation | boolean | DeleteBeforeCreation is true if the hook should delete the resources before they are created. |
lifecycle.syncFailed[].if | string | If is the hook execution condition written in CEL expression format. |
lifecycle.syncFailed[].jobTemplate | LifecycleHookJobTemplate | JobTemplate is the job template for the hook. Mutual exclusive with PodTemplate. |
lifecycle.syncFailed[].jobTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.syncFailed[].jobTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.syncFailed[].jobTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.syncFailed[].jobTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.syncFailed[].jobTemplate.spec required | JobSpec | Spec contains the Job spec. |
lifecycle.syncFailed[].name required | string | Name is the name of the hook. |
lifecycle.syncFailed[].onFailure required | string | OnFailure is the action to take when the hook fails.
|
lifecycle.syncFailed[].podTemplate | LifecycleHookPodTemplate | PodTemplate is the pod template for the hook. Mutual exclusive with JobTemplate. |
lifecycle.syncFailed[].podTemplate.metadata | PartialMetadata | Metadata contains partial object metadata. |
lifecycle.syncFailed[].podTemplate.metadata.annotations | map[string]string | Annotations is the annotations that will be set on the lifecycle hook workload. |
lifecycle.syncFailed[].podTemplate.metadata.labels | map[string]string | Labels is the labels that will be set on the lifecycle hook workload. |
lifecycle.syncFailed[].podTemplate.metadata.namespace | string | Namespace is the namespace where the lifecycle hook workload should run. Defaults to release target namespace if not set. |
lifecycle.syncFailed[].podTemplate.spec required | PodSpec | Spec contains the Pod spec. |
lifecycle.syncFailed[].runAfter | string | RunAfter defines the Release condition that should be achieved before running the PostSync lifecycle hook(s).
|
lifecycle.syncFailed[].timeout | Duration | Timeout is the timeout for the hook. If not set, controller will wait indefinitely. |
lifecycle.syncFailed[].type required | string | Type is the type of the hook.
|
lifecycle.syncFailed[].weight required | integer | Weight is the weight of the hook. |
Helm chart hooks#
convoy does not translate Helm hooks into lifecycle hooks. It drops Helm test hooks. It applies
other objects with a helm.sh/hook annotation as normal resources. A translation of Helm hooks
into lifecycle hooks is planned. Not built
In the code
convoy v0.9.0
internal/controller/release.(*Controller).runHookPhaseinternal/controller/release/hookphase.go:62internal/controller/release.(*Controller).runAfterSatisfiedinternal/controller/release/hookphase.go:215internal/controller/release.(*Controller).cleanupHookinternal/controller/release/hookphase.go:194internal/controller/release.buildHookWorkloadinternal/controller/release/hookworkload.go:121internal/controller/release.hookWorkloadNameinternal/controller/release/hookworkload.go:86internal/render.EvalHookIfinternal/render/hooks.go:44internal/controller/release.lifecycleConditioninternal/controller/release/hookstatus.go:110api/core.validateLifecycleHookapi/core/validation.go:421