Run a database migration and a smoke test with lifecycle hooks
Run a migration Job before convoy applies a render, and a smoke test after the Release is healthy.
This guide adds two lifecycle hooks to a Release. A preSync hook runs a database migration
before convoy applies the render. If the migration fails, convoy applies nothing. A postSync
hook runs a smoke test after all resources of the Release are healthy. You write the hooks
through the /lifecycle subresource with the kubectl convoy plugin. For the full behavior, see
Lifecycle hooks.
Before you start#
- Make sure that the
GitRepositorypodinfoand theClusterdev-localexist. See Deploy from Git. - Make sure that a
GlobalHealthRuleSetwith the labelconvoy.kumodo.io/default: "true"exists. See Add health rules to a Release. - Save each example under the file name in its title bar. The commands use these file names.
Steps#
1. Deploy the Release with example hooks#
The example has a preSync hook and a postSync hook. Both hooks run sleep 10. The Release
has spec.healthAssessment, so runAfter: Healthy waits for healthy resources.
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
Apply the Release.
kubectl apply -f releases/release-gitrepository-helm-in-cluster-with-lifecycle.yamlMonitor the Release.
kubectl get releases podinfo-in-cluster-helm-with-lifecycle -wExamine the
LIFECYCLEcolumn. The value changes fromRunning:PreSynctoRunning:PostSync, and then toCompleted:PostSync.
2. Write the hooks file#
The file contains a ReleaseLifecycle object. It is not a Kubernetes object. The top-level keys
are the phases. Save this content as hooks.yaml:
preSync:
- name: migrate
type: Job
timeout: 5m
onFailure: Fail
cleanup: OnSuccess
deleteBeforeCreation: true
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: busybox:1.38.0
command: ["sh", "-c", "echo running database migrations"]
postSync:
- name: smoke-test
type: Job
runAfter: Healthy
timeout: 2m
onFailure: Continue
cleanup: OnSuccess
jobTemplate:
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: smoke-test
image: curlimages/curl:8.10.1
args: ["-fsS", "http://podinfo-helm:9898/healthz"]- Replace the image and the command of
migratewith your migration tool. - Keep
onFailure: Failonmigrate. A failed migration then stops the sync before convoy applies an object. - Keep
runAfter: Healthyonsmoke-test. The test then starts after all resources are healthy for the current render. - Set a low
backoffLimit. A Job fails only after all its retries fail.
The hook workload runs in the target namespace of spec.content. Without a target namespace, it
runs in default. Thus the smoke test can use the short Service name podinfo-helm.
3. Select the cleanup behavior#
cleanup and deleteBeforeCreation control the hook workloads on the target cluster:
| Setting | Effect | Use it when |
|---|---|---|
cleanup: Never (default) | convoy does not delete the workload. | You want to keep all logs. |
cleanup: OnSuccess | convoy deletes the workload after a success. | You want to keep the logs of a failure only. |
cleanup: OnCompletion | convoy deletes the workload after a success or a failure. | You do not need the logs. |
deleteBeforeCreation: true | convoy deletes the earlier workloads of the hook before it creates a new one. | You want one workload for each hook on the cluster. |
- A hook that fails with
onFailure: Failstops the phase before the cleanup step. Thus its workload stays, also withcleanup: OnCompletion. - convoy does not collect old hook workloads. With
cleanup: NeveranddeleteBeforeCreation: false, one workload stays for each render.
4. Set the hooks#
Replace the hooks of the Release.
kubectl convoy release lifecycle set podinfo-in-cluster-helm-with-lifecycle -f hooks.yamlRead the hooks back.
kubectl convoy release lifecycle get podinfo-in-cluster-helm-with-lifecycle
lifecycle set replaces all phases. convoy clears each phase that is not in the file. The new
hooks have new names, so convoy runs them also for the current render.
5. Monitor the hooks#
Monitor the Release.
kubectl get releases podinfo-in-cluster-helm-with-lifecycle -wExamine the
LIFECYCLEcolumn. The value isRunning:PreSyncwhilemigrateruns.List the hook workloads.
kubectl get jobs -n default -l convoy.kumodo.io/release-name=podinfo-in-cluster-helm-with-lifecycle
The workload names have the format {phase}-{name}-{hash}, for example presync-migrate-1f2e3d4c.
Check the result#
Read the hook records.
kubectl get releases podinfo-in-cluster-helm-with-lifecycle -o jsonpath='{.status.lifecycle}'Find the entries
migrateinpreSyncandsmoke-testinpostSync. Each entry must havestate: Succeeded.Find the
LifecycleSucceededcondition. Its status must beTruewith reasonAllSucceeded.kubectl describe releases podinfo-in-cluster-helm-with-lifecycleExamine the
LIFECYCLEcolumn. The value must beCompleted:PostSync.
A hook with an if expression that is false does not run. It has no entry in
status.lifecycle.
If migrate fails, you see these values:
LIFECYCLEisFailed:PreSync.AppliedisFalsewith reasonHookFailed.status.lastAttempted.failure.stageisHookFailed, andhookNameismigrate.- The Job of
migratestays on the cluster. Read its logs to find the cause.
convoy does not run a failed hook again for the same render. Correct the cause, then push a new revision to the source. The new render runs the hooks again.
LifecycleSucceeded · written by convoy-controller-manager (field manager convoy-release)
| Reason | Status | Meaning |
|---|---|---|
AllSucceeded | True | All lifecycle hooks succeeded or were skipped. |
Running | Unknown | At least one lifecycle hook is still running. |
HookFailed | False | A lifecycle hook failed. This is so for each onFailure value. onFailure only controls if the sync continues. |
Clean up#
To run hooks when you delete a Release, add preDelete and postDelete hooks. This example
shows the two phases:
apiVersion: core.convoy.kumodo.io/v1alpha1
kind: Release
metadata:
name: podinfo-in-cluster-helm-with-deletion-lifecycle
namespace: default
lifecycle:
preDelete:
- name: pre-sleep
type: Job
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"]
postDelete:
- name: post-sleep
type: Job
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
Delete the Release.
kubectl delete releases podinfo-in-cluster-helm-with-lifecycleList the hook workloads that stay on the cluster.
kubectl get jobs -n default -l convoy.kumodo.io/release-name=podinfo-in-cluster-helm-with-lifecycleDelete these workloads.
kubectl delete jobs -n default -l convoy.kumodo.io/release-name=podinfo-in-cluster-helm-with-lifecycle
Next steps#
- Read Lifecycle hooks for the
ifcondition, weights and timeouts. - Read Release lifecycle hooks for all fields.
- Order the resources of a Release with sync waves.
- Return to a known render with Roll back a Release.
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.lifecycleConditioninternal/controller/release/hookstatus.go:110internal/cli/release.runLifecycleSetinternal/cli/release/command.go:405api/core.validateLifecycleHookapi/core/validation.go:421