Aller au contenu principal

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

HookMoment d'exécution
pre-installAvant l'installation des ressources
post-installAprès l'installation
pre-deleteAvant la suppression
post-deleteAprès la suppression
pre-upgradeAvant un upgrade
post-upgradeAprès un upgrade
pre-rollbackAvant un rollback
post-rollbackAprès un rollback
testLors 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

PolitiqueDescription
before-hook-creationSupprime l'ancien hook avant de créer le nouveau
hook-succeededSupprime le hook si succès
hook-failedSupprime 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


← Retour à la table des matières