Lab Guide
Time estimate: 45–60 minutes · Goals: Create hook Jobs, control execution order, write and run Helm tests
Lab Goals
- Add a pre-upgrade migration hook Job to an existing chart
- Use hook weights to order a backup Job before the migration Job
- Write a
helm testpod that validates connectivity - Run
helm testand interpret the results
Prerequisites
- A running Kubernetes cluster (minikube, kind, or similar)
- Helm 3 installed
kubectlconfigured to talk to your cluster
Exercise 1: Create a Pre-Upgrade Migration Hook
Step 1.1 — Scaffold a Chart
Create a fresh chart to work with:
helm create hookdemo
cd hookdemo
Step 1.2 — Create the Hook Template
Create a new file templates/hooks/db-migrate.yaml:
mkdir -p templates/hooks
Add the following content to templates/hooks/db-migrate.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "hookdemo.fullname" . }}-db-migrate
labels:
{{- include "hookdemo.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
backoffLimit: 1
template:
metadata:
labels:
{{- include "hookdemo.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
containers:
- name: migrate
image: busybox:1.36
command:
- sh
- -c
- |
echo "Running database migration..."
echo "Creating table users..."
sleep 2
echo "Migration complete!"
exit 0
Step 1.3 — Install the Chart
cd ..
helm install hookdemo ./hookdemo
Step 1.4 — Verify the Hook Ran
# Check that the migration Job completed
kubectl get jobs | grep migrate
# View the migration logs
kubectl logs job/hookdemo-db-migrate
✓ Check: The Job shows 1/1 completions and the logs print "Migration complete!"
Exercise 2: Add a Backup Hook with Weight Ordering
Step 2.1 — Create the Backup Hook
Create hookdemo/templates/hooks/backup.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "hookdemo.fullname" . }}-backup
labels:
{{- include "hookdemo.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
backoffLimit: 0
template:
metadata:
labels:
{{- include "hookdemo.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
containers:
- name: backup
image: busybox:1.36
command:
- sh
- -c
- |
echo "Starting database backup..."
sleep 3
echo "Backup saved to /backups/pre-upgrade-$(date +%s).sql"
echo "Backup complete!"
exit 0
Step 2.2 — Trigger an Upgrade
Change a value to trigger an upgrade (e.g., bump the replica count):
helm upgrade hookdemo ./hookdemo --set replicaCount=2
Step 2.3 — Verify Execution Order
# List all jobs to see both backup and migrate
kubectl get jobs --sort-by=.metadata.creationTimestamp | grep hookdemo
# Check backup logs
kubectl logs job/hookdemo-backup
# Check migration logs
kubectl logs job/hookdemo-db-migrate
✓ Check: The backup Job was created before the migration Job (backup has weight -5, migration has weight 0). Both show as completed.
Observing Weight Order
Compare the creationTimestamp of both Jobs. The backup Job (weight -5) should have an earlier timestamp than the migration Job (weight 0). Helm creates and waits for each hook to complete in weight order before proceeding to the next.
Exercise 3: Write and Run Helm Tests
Step 3.1 — Create the Test Directory
mkdir -p hookdemo/templates/tests
Step 3.2 — Create a Connectivity Test
Create hookdemo/templates/tests/test-connection.yaml:
apiVersion: v1
kind: Pod
metadata:
name: {{ include "hookdemo.fullname" . }}-test-connection
labels:
{{- include "hookdemo.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox:1.36
command:
- wget
- --spider
- --timeout=5
- http://{{ include "hookdemo.fullname" . }}:{{ .Values.service.port }}
Step 3.3 — Create a DNS Resolution Test
Create hookdemo/templates/tests/test-dns.yaml:
apiVersion: v1
kind: Pod
metadata:
name: {{ include "hookdemo.fullname" . }}-test-dns
labels:
{{- include "hookdemo.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
containers:
- name: dns-check
image: busybox:1.36
command:
- nslookup
- {{ include "hookdemo.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local
Step 3.4 — Run the Tests
# Run all tests for the release
helm test hookdemo
# If a test fails, re-run with logs to see what went wrong
helm test hookdemo --logs
Expected output on success:
NAME: hookdemo
LAST DEPLOYED: ...
NAMESPACE: default
STATUS: deployed
TEST SUITE: hookdemo-test-dns
Last Started: ...
Last Completed: ...
Phase: Succeeded
TEST SUITE: hookdemo-test-connection
Last Started: ...
Last Completed: ...
Phase: Succeeded
✓ Check: Both tests show Phase: Succeeded. The DNS test (weight -5) ran before the connection test (default weight 0).
Step 3.5 — Verify with kubectl
# Check the test pods
kubectl get pods | grep test
# View logs from the connection test
kubectl logs hookdemo-test-connection
# View logs from the DNS test
kubectl logs hookdemo-test-dns
Troubleshooting
- Test pod stuck in Pending — check if the cluster has available resources with
kubectl describe pod <pod-name>. - "already exists" error on hooks — you are missing
helm.sh/hook-delete-policy: before-hook-creation. Add it and upgrade again. - Connection test fails — ensure the Service is up:
kubectl get svc hookdemo. The defaulthelm createchart uses port 80. - Hook Job keeps failing — inspect logs with
kubectl logs job/<job-name>and check the exit code.
Cleanup
helm uninstall hookdemo
Self-Check
- ✓ You created a pre-install/pre-upgrade migration hook Job
- ✓ You added a backup hook with a lower weight that runs before the migration
- ✓ You verified execution order via creation timestamps
- ✓ You wrote two test pods (DNS and HTTP connectivity)
- ✓
helm test hookdemopassed with both tests showing Succeeded - ✓ You can explain the difference between
before-hook-creationandhook-succeededdelete policies