12 — Helm : usage avancé#
Le livre Docker (chapitre 17) a couvert les bases de Helm : installation de charts, helm install / upgrade / rollback, templates, values et secrets basiques. Ce chapitre approfondit les mécanismes avancés qui font de Helm un outil de déploiement fiable à l’échelle : dépendances de charts, hooks de cycle de vie, tests automatisés, registres OCI et outillage complémentaire comme Helmfile.
Dépendances de charts#
Un chart peut déclarer des dépendances vers d’autres charts (sous-charts). Cela permet d’empaqueter une application avec ses services tiers (base de données, cache, broker) dans une unité deployable cohérente.
# Chart.yaml
apiVersion: v2
name: myapp
description: Application principale avec ses dépendances
type: application
version: 1.4.2
appVersion: "2.1.0"
dependencies:
- name: postgresql
version: "~15.2.0" # plage sémantique : 15.2.x
repository: oci://registry-1.docker.io/bitnamicharts
condition: postgresql.enabled # désactivable via values
- name: redis
version: "~19.0.0"
repository: oci://registry-1.docker.io/bitnamicharts
condition: redis.enabled
- name: myapp-common
version: "~1.0.0"
repository: oci://ghcr.io/myorg/charts
alias: common # alias pour éviter les conflits de noms
# Télécharger et verrouiller les dépendances
helm dependency update ./myapp
# Résultat : Chart.lock (fichier de verrouillage des versions exactes)
# et charts/ (dossier contenant les .tgz des sous-charts)
Chart.lock joue le même rôle que package-lock.json ou Cargo.lock : il épingle les versions exactes résolues, garantissant la reproductibilité entre environnements.
Dépendances conditionnelles
Les conditions (condition: postgresql.enabled) permettent de désactiver les sous-charts selon l’environnement. En staging, on peut activer PostgreSQL embarqué ; en prod, le chart se connecte à une instance RDS externe et postgresql.enabled: false supprime le déploiement du sous-chart.
Hooks Helm#
Les hooks Helm permettent d’exécuter des pods à des moments précis du cycle de vie d’une release : avant l’installation, après une mise à jour, avant la suppression, etc.
# templates/job-db-migrate.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-db-migrate"
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5" # ordre parmi les hooks (plus petit = premier)
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-db-secret"
key: url
Hooks disponibles#
Hook |
Moment d’exécution |
|---|---|
|
Avant la création des ressources de la release |
|
Après la création de toutes les ressources |
|
Avant la mise à jour des ressources |
|
Après la mise à jour réussie |
|
Avant un rollback |
|
Après un rollback réussi |
|
Avant la suppression ( |
|
Après la suppression de toutes les ressources |
|
Exécuté par |
La politique de suppression (hook-delete-policy) contrôle quand le Job est nettoyé :
before-hook-creation: supprime l’ancien hook avant d’en créer un nouveauhook-succeeded: supprime après succèshook-failed: supprime après échec (utile pour libérer les resources même en cas d’erreur)
Tests Helm#
Helm intègre un mécanisme de test qui exécute des pods de vérification après le déploiement.
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-connection"
annotations:
"helm.sh/hook": test
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox:1.36
command: ["wget"]
args:
- "--spider"
- "--timeout=5"
- "http://{{ .Release.Name }}-myapp:{{ .Values.service.port }}/healthz"
# Exécuter les tests après le déploiement
helm test myapp-release --namespace production
# Résultat attendu :
# NAME: myapp-release
# LAST DEPLOYED: Thu Mar 26 08:00:00 2026
# NAMESPACE: production
# STATUS: deployed
# TEST SUITE: myapp-release-test-connection
# Last Started: Thu Mar 26 08:01:00 2026
# Last Completed: Thu Mar 26 08:01:05 2026
# Phase: Succeeded
Upgrades robustes#
Les flags --atomic, --wait et --timeout transforment un helm upgrade en opération transactionnelle :
helm upgrade myapp ./myapp \
--namespace production \
--values values-prod.yaml \
--atomic \ # rollback automatique si l'upgrade échoue
--wait \ # attend que tous les pods soient Ready
--timeout 5m \ # timeout global (défaut : 5m)
--cleanup-on-fail \ # supprime les nouvelles ressources créées en cas d'échec
--create-namespace
--wait: Helm attend que tous les Deployments, StatefulSets, DaemonSets et Jobs soient dans l’état souhaité avant de considérer l’upgrade réussi.--atomic: implique--waitet déclenche automatiquement unhelm rollbacksi le timeout est dépassé ou si un pod reste en état d’erreur.
Idempotence des upgrades
helm upgrade --install combine install et upgrade en une seule commande idempotente : si la release n’existe pas, elle est créée ; sinon, elle est mise à jour. C’est la forme recommandée dans les pipelines CI/CD.
Helmfile#
Helmfile est un outil déclaratif qui gère un ensemble de releases Helm dans un seul fichier YAML. Il résout le problème du multi-chart : une application réelle déploie souvent 5 à 20 charts distincts avec des dépendances entre eux.
# helmfile.yaml
environments:
staging:
values:
- environments/staging.yaml
production:
values:
- environments/production.yaml
repositories:
- name: bitnami
url: oci://registry-1.docker.io/bitnamicharts
oci: true
- name: myorg
url: oci://ghcr.io/myorg/charts
oci: true
releases:
- name: postgresql
chart: bitnami/postgresql
version: "~15.2.0"
namespace: databases
values:
- values/postgresql.yaml.gotmpl
installed: {{ eq .Environment.Name "staging" }} # uniquement en staging
- name: redis
chart: bitnami/redis
version: "~19.0.0"
namespace: caches
values:
- values/redis.yaml
- name: myapp
chart: myorg/myapp
version: "~1.4.0"
namespace: production
values:
- values/myapp-common.yaml
- values/myapp-{{ .Environment.Name }}.yaml
needs:
- databases/postgresql # dépendance : postgresql déployé en premier
- caches/redis
# Déployer toutes les releases de l'environnement production
helmfile --environment production sync
# Visualiser les différences avant application
helmfile --environment production diff
# Détruire toutes les releases
helmfile --environment production destroy
Registres OCI pour les charts#
Depuis Helm 3.8, les charts peuvent être distribués via des registres OCI (Open Container Initiative), simplifiant l’infrastructure : plus besoin d’un chart museum séparé.
# Construire et publier un chart
helm package ./myapp # → myapp-1.4.2.tgz
helm push myapp-1.4.2.tgz oci://ghcr.io/myorg/charts
# Installer depuis un registre OCI
helm install myapp oci://ghcr.io/myorg/charts/myapp \
--version 1.4.2 \
--values values-prod.yaml
# Inspecter un chart sans l'installer
helm show values oci://ghcr.io/myorg/charts/myapp --version 1.4.2
Authentification OCI
Les registres OCI utilisent les mêmes mécanismes d’authentification que Docker : helm registry login ghcr.io --username $GITHUB_USER --password $GITHUB_TOKEN. Dans les pipelines CI, utiliser les secrets du workflow pour injecter les credentials.
Kustomize vs Helm#
Le positionnement de ces deux outils génère souvent de la confusion :
Critère |
Helm |
Kustomize |
|---|---|---|
Paradigme |
Templating (Go templates) |
Overlays déclaratifs (patches) |
Courbe d’apprentissage |
Élevée (templating, chart structure) |
Modérée (patches JSON/YAML) |
Packaging |
Oui (chart distributable) |
Non (code source uniquement) |
Gestion des secrets |
Via plugins (helm-secrets, SOPS) |
Via SecretGenerator + KSOPS |
GitOps-friendly |
Oui (Argo CD, Flux) |
Très bien intégré (natif kubectl) |
Cas d’usage idéal |
Distribution de logiciels réutilisables |
Personnalisation d’un chart existant |
Helm + Kustomize : le meilleur des deux mondes#
ArgoCD et Flux supportent nativement la combinaison : Helm génère les manifestes de base, Kustomize applique des patches d’environnement sur le résultat.
# helm template génère les manifestes, kustomize les patch
helm template myapp ./myapp --values values-base.yaml | kubectl kustomize -
Helm Secrets#
Le plugin helm-secrets intègre SOPS dans le workflow Helm : les fichiers secrets.yaml sont chiffrés dans Git et déchiffrés à la volée lors du helm upgrade.
# Installation du plugin
helm plugin install https://github.com/jkroepke/helm-secrets
# Chiffrer un fichier de secrets
helm secrets enc secrets/prod-secrets.yaml
# Upgrade avec déchiffrement automatique
helm secrets upgrade myapp ./myapp \
--values values-prod.yaml \
--values secrets/prod-secrets.yaml
Debugging#
# Générer les manifestes sans déployer (dry-run local)
helm template myapp ./myapp --values values-prod.yaml
# Linter le chart
helm lint ./myapp --values values-prod.yaml --strict
# Dry-run serveur (validation contre l'API server)
helm upgrade myapp ./myapp --values values-prod.yaml --dry-run=server
# Inspecter une release déployée
helm get manifest myapp-release --namespace production
helm get values myapp-release --namespace production
# Historique des révisions
helm history myapp-release --namespace production
Simulations Python#
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# ── Radar chart : Helm vs Kustomize vs Helm+Kustomize ────────────────────────
categories = [
"Templating\n(expressivité)",
"Packaging\n(distribuabilité)",
"GitOps-\nfriendly",
"Courbe\nd'apprentissage\n(inversée)",
"Gestion\ndes secrets",
"Personnalisation\nenv. spécifique",
]
N = len(categories)
scores = {
"Helm seul": [9, 10, 7, 4, 7, 7],
"Kustomize seul": [4, 2, 9, 8, 6, 9],
"Helm + Kustomize": [9, 9, 9, 3, 8, 10],
}
angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist()
angles += angles[:1]
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
ax.set_title("Helm vs Kustomize vs Helm + Kustomize", fontsize=13, fontweight="bold", pad=20)
colors = ["#4C72B0", "#55A868", "#C44E52"]
for (label, vals), color in zip(scores.items(), colors):
vals_plot = vals + vals[:1]
ax.plot(angles, vals_plot, color=color, lw=2.5, label=label)
ax.fill(angles, vals_plot, color=color, alpha=0.12)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, fontsize=9)
ax.set_yticks([2, 4, 6, 8, 10])
ax.set_yticklabels(["2", "4", "6", "8", "10"], fontsize=8)
ax.set_ylim(0, 10)
ax.legend(loc="upper right", bbox_to_anchor=(1.35, 1.15), fontsize=10)
plt.savefig("_static/12_helm_radar.png", dpi=120, bbox_inches="tight")
plt.show()
import matplotlib.pyplot as plt
import networkx as nx
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# ── Structure d'un chart Helm avec dépendances ────────────────────────────────
G = nx.DiGraph()
# Nœuds : chart principal et ses fichiers / sous-charts
nodes = {
"myapp\n(chart)": {"level": 0, "color": "#4C72B0"},
"Chart.yaml": {"level": 1, "color": "#8172B2"},
"values.yaml": {"level": 1, "color": "#8172B2"},
"templates/": {"level": 1, "color": "#DD8452"},
"charts/": {"level": 1, "color": "#55A868"},
"Chart.lock": {"level": 1, "color": "#937860"},
"Deployment.yaml": {"level": 2, "color": "#DD8452"},
"Service.yaml": {"level": 2, "color": "#DD8452"},
"HPA.yaml": {"level": 2, "color": "#DD8452"},
"hooks/migrate.yaml": {"level": 2, "color": "#C44E52"},
"tests/": {"level": 2, "color": "#C44E52"},
"postgresql/": {"level": 2, "color": "#55A868"},
"redis/": {"level": 2, "color": "#55A868"},
}
for node in nodes:
G.add_node(node)
edges = [
("myapp\n(chart)", "Chart.yaml"),
("myapp\n(chart)", "values.yaml"),
("myapp\n(chart)", "templates/"),
("myapp\n(chart)", "charts/"),
("myapp\n(chart)", "Chart.lock"),
("templates/", "Deployment.yaml"),
("templates/", "Service.yaml"),
("templates/", "HPA.yaml"),
("templates/", "hooks/migrate.yaml"),
("templates/", "tests/"),
("charts/", "postgresql/"),
("charts/", "redis/"),
]
G.add_edges_from(edges)
# Positionnement manuel pour une mise en page lisible
pos = {
"myapp\n(chart)": (5.0, 4.0),
"Chart.yaml": (1.5, 2.5),
"values.yaml": (3.0, 2.5),
"templates/": (5.5, 2.5),
"charts/": (7.5, 2.5),
"Chart.lock": (9.5, 2.5),
"Deployment.yaml": (4.0, 1.0),
"Service.yaml": (5.2, 1.0),
"HPA.yaml": (6.4, 1.0),
"hooks/migrate.yaml": (7.6, 1.0),
"tests/": (8.8, 1.0),
"postgresql/": (7.0, 0.2),
"redis/": (8.5, 0.2),
}
node_colors = [nodes[n]["color"] for n in G.nodes()]
fig, ax = plt.subplots(figsize=(13, 6))
ax.set_title("Structure d'un chart Helm avec dépendances", fontsize=13, fontweight="bold")
nx.draw(G, pos, ax=ax, with_labels=True,
node_color=node_colors,
node_size=2200,
font_size=7.5,
font_color="white",
font_weight="bold",
edge_color="#666666",
arrows=True,
arrowstyle="-|>",
arrowsize=18,
width=1.8)
# Légende
legend_items = [
mpatches.Patch(color="#4C72B0", label="Chart racine"),
mpatches.Patch(color="#8172B2", label="Métadonnées"),
mpatches.Patch(color="#DD8452", label="Templates"),
mpatches.Patch(color="#C44E52", label="Hooks / Tests"),
mpatches.Patch(color="#55A868", label="Sous-charts"),
mpatches.Patch(color="#937860", label="Lockfile"),
]
ax.legend(handles=legend_items, loc="upper left", fontsize=9, framealpha=0.9)
plt.savefig("_static/12_helm_structure.png", dpi=120, bbox_inches="tight")
plt.show()
Résumé#
Les dépendances de charts (
Chart.yaml+helm dependency update) permettent d’empaqueter une application avec ses services tiers ;Chart.lockgarantit la reproductibilité exacte des versions résolues.Les hooks Helm (
pre-install,post-upgrade,pre-delete…) permettent d’exécuter des pods de migration ou de vérification à des moments précis du cycle de vie d’une release.helm testexécute des pods de vérification post-déploiement ; c’est le mécanisme natif pour valider que la release fonctionne correctement après un upgrade.Les flags
--atomic,--waitet--timeoutrendent les upgrades transactionnels : Helm revient automatiquement à la révision précédente si le déploiement échoue.Helmfile déclare l’ensemble des releases d’un projet dans un fichier versionnable, gère les dépendances entre charts et supporte plusieurs environnements via des values contextuelles.
Les registres OCI simplifient la distribution des charts en réutilisant l’infrastructure existante des registres Docker ; plus besoin d’un chart museum séparé.
Helm et Kustomize sont complémentaires : Helm excelle pour le packaging et la distribution, Kustomize pour la personnalisation d’overlays par environnement — leur combinaison est supportée nativement par Argo CD et Flux.
Le plugin helm-secrets intègre SOPS dans le workflow Helm pour chiffrer les fichiers de valeurs sensibles tout en les versionnant dans Git.
La commande
helm templateest l’outil de debug fondamental : elle génère les manifestes sans les appliquer, permettant d’inspecter le rendu des templates et de détecter les erreurs avant tout déploiement.La combinaison
helm lint --strict+helm template+--dry-run=serverconstitue un pipeline de validation complet qui détecte les erreurs de templating, de schéma et de validation API avant tout contact avec le cluster de production.