Lesson 15 Hooks & Tests
Theme:

Lab Guide

Time estimate: 45–60 minutes  ·  Goals: Create hook Jobs, control execution order, write and run Helm tests

Lab Goals

Prerequisites

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

Cleanup

helm uninstall hookdemo

Self-Check