Hooks et Tests
1 - Introduction aux Hooks
Les hooks permettent d'exécuter des actions à des moments spécifiques du cycle de vie d'une release.
2 - Types de Hooks
| Hook | Moment d'exécution |
|---|---|
pre-install | Avant l'installation des ressources |
post-install | Après l'installation |
pre-delete | Avant la suppression |
post-delete | Après la suppression |
pre-upgrade | Avant un upgrade |
post-upgrade | Après un upgrade |
pre-rollback | Avant un rollback |
post-rollback | Après un rollback |
test | Lors de helm test |
3 - Créer un Hook
3.1 Structure de base
Un hook est un template Kubernetes standard avec des annotations spéciales.
# templates/hooks/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pre-install"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
# Définir le type de hook
"helm.sh/hook": pre-install
# Ordre d'exécution (si plusieurs hooks)
"helm.sh/hook-weight": "5"
# Politique de suppression
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: pre-install
image: busybox
command: ['sh', '-c', 'echo Pre-install hook running']
restartPolicy: Never
backoffLimit: 1
3.2 Annotations des hooks
annotations:
# Type de hook (peut être multiple, séparé par virgules)
"helm.sh/hook": pre-install,pre-upgrade
# Poids (ordre d'exécution, plus bas = premier)
"helm.sh/hook-weight": "-5" # Exécuté avant les autres
"helm.sh/hook-weight": "0" # Par défaut
"helm.sh/hook-weight": "10" # Exécuté après
# Politique de suppression
"helm.sh/hook-delete-policy": before-hook-creation
"helm.sh/hook-delete-policy": hook-succeeded
"helm.sh/hook-delete-policy": hook-failed
3.3 Politiques de suppression
| Politique | Description |
|---|---|
before-hook-creation | Supprime l'ancien hook avant de créer le nouveau |
hook-succeeded | Supprime le hook si succès |
hook-failed | Supprime le hook si échec |
4 - Cas d'usage courants
4.1 Migration de base de données
# templates/hooks/db-migrate.yaml
{{- if .Values.migrations.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-db-migrate"
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate.sh"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db-secret
key: url
restartPolicy: Never
backoffLimit: 3
{{- end }}
4.2 Backup avant upgrade
# templates/hooks/pre-upgrade-backup.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-backup"
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: backup
image: postgres:15
command:
- /bin/bash
- -c
- |
BACKUP_FILE="/backups/backup-$(date +%Y%m%d-%H%M%S).sql"
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > $BACKUP_FILE
echo "Backup created: $BACKUP_FILE"
env:
- name: DB_HOST
value: "{{ .Values.postgresql.host }}"
- name: DB_USER
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db
key: password
volumeMounts:
- name: backups
mountPath: /backups
volumes:
- name: backups
persistentVolumeClaim:
claimName: {{ .Release.Name }}-backups
restartPolicy: Never
4.3 Nettoyage post-delete
# templates/hooks/post-delete-cleanup.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-cleanup"
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
spec:
template:
spec:
serviceAccountName: {{ .Release.Name }}-cleanup-sa
containers:
- name: cleanup
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
# Supprimer les PVCs orphelins
kubectl delete pvc -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
# Supprimer les secrets générés
kubectl delete secret -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
restartPolicy: Never
4.4 Notification Slack
# templates/hooks/notify-slack.yaml
{{- if .Values.notifications.slack.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-notify"
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "100"
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
spec:
template:
spec:
containers:
- name: notify
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"✅ Release {{ .Release.Name }} deployed successfully ({{ .Chart.Version }})"}' \
{{ .Values.notifications.slack.webhookUrl }}
restartPolicy: Never
{{- end }}
5 - Tests Helm
5.1 Introduction aux tests
Les tests Helm vérifient que la release fonctionne correctement après déploiement.
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-test-connection"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
5.2 Exécuter les tests
# Exécuter les tests
helm test my-release
# Avec timeout
helm test my-release --timeout 5m
# Voir les logs des tests
helm test my-release --logs
5.3 Exemples de tests
Test HTTP :
# templates/tests/test-http.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-http"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
# Test endpoint principal
curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/
# Test health endpoint
curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health
# Test avec assertion
RESPONSE=$(curl -s http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/api/version)
echo "Response: $RESPONSE"
echo "$RESPONSE" | grep -q "{{ .Chart.AppVersion }}"
restartPolicy: Never
Test base de données :
# templates/tests/test-db.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-db"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: postgres
image: postgres:15
command:
- /bin/sh
- -c
- |
pg_isready -h {{ .Values.postgresql.host }} -p 5432 -U {{ .Values.postgresql.username }}
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db
key: password
restartPolicy: Never
Test Redis :
# templates/tests/test-redis.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-redis"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: redis
image: redis:7
command:
- /bin/sh
- -c
- |
redis-cli -h {{ .Release.Name }}-redis ping | grep -q PONG
restartPolicy: Never
5.4 Tests avec plusieurs étapes
# templates/tests/test-integration.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-integration"
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "10"
spec:
containers:
- name: test
image: python:3.11-slim
command:
- /bin/bash
- -c
- |
pip install requests pytest
cat > /tmp/test_app.py << 'EOF'
import requests
import pytest
BASE_URL = "http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
def test_health():
response = requests.get(f"{BASE_URL}/health")
assert response.status_code == 200
assert response.json()["status"] == "healthy"
def test_api_version():
response = requests.get(f"{BASE_URL}/api/version")
assert response.status_code == 200
assert "{{ .Chart.AppVersion }}" in response.text
def test_create_resource():
response = requests.post(f"{BASE_URL}/api/items", json={"name": "test"})
assert response.status_code == 201
if __name__ == "__main__":
pytest.main([__file__, "-v"])
EOF
python /tmp/test_app.py
restartPolicy: Never
6 - Bonnes pratiques
6.1 Organisation des hooks
templates/
├── hooks/
│ ├── pre-install/
│ │ ├── create-namespace.yaml
│ │ └── init-secrets.yaml
│ ├── pre-upgrade/
│ │ ├── backup.yaml
│ │ └── db-migrate.yaml
│ ├── post-install/
│ │ └── notify.yaml
│ └── post-delete/
│ └── cleanup.yaml
└── tests/
├─ ─ test-connection.yaml
├── test-db.yaml
└── test-integration.yaml
6.2 Gestion des erreurs
# Hook avec gestion d'erreur
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-migrate"
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-delete-policy": before-hook-creation
spec:
# Timeout du Job
activeDeadlineSeconds: 300
# Nombre de tentatives
backoffLimit: 3
template:
spec:
containers:
- name: migrate
image: myapp:{{ .Values.image.tag }}
command:
- /bin/bash
- -c
- |
set -e # Exit on error
echo "Starting migration..."
if ! ./migrate.sh; then
echo "Migration failed!"
exit 1
fi
echo "Migration completed successfully"
restartPolicy: Never
6.3 Timeout et ressources
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-hook"
annotations:
"helm.sh/hook": pre-install
spec:
activeDeadlineSeconds: 600
backoffLimit: 2
template:
spec:
containers:
- name: hook
image: myimage
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
restartPolicy: Never
Résumé
Dans ce chapitre, nous avons appris :
- Les différents types de hooks et leur moment d'exécution
- Comment créer des hooks avec les annotations appropriées
- Les cas d'usage courants (migrations, backups, notifications)
- La création et exécution de tests Helm
- Les bonnes pratiques pour les hooks et tests
Prochaine étape
Dans le prochain chapitre, nous verrons les Bonnes pratiques globales pour Helm.
→ Chapitre suivant : Bonnes pratiques