Constructeur de Charts Helm
Workflow en étapes
- Analyser, identifier : type d'app (stateless/stateful), dépendances externes, environnements cibles, besoins ingress/secret/HPA.
- Scaffolder,
helm create mychartpuis nettoyer les exemples inutiles. - Modéliser
values.yaml, définir des defaults qui fonctionnent en dev sans surcharge. Tout ce qui varie par env = exposé en value. - Écrire les templates, utiliser
_helpers.tplpour les labels/noms ; ajouterchecksum/configpour forcer le rollout sur changement de ConfigMap. - Valider localement,
helm lint,helm template,helm diff(plugin) avant tout push. - Déployer par env,
helm upgrade --installavec-f values-prod.yamlet--set image.tag=$TAG. - Opérations post-deploy, vérifier
helm status, inspecter les logs, prévoirhelm rollbacksi nécessaire.
Structure type
mychart/
├── Chart.yaml # Métadonnées + dépendances
├── values.yaml # Defaults (dev fonctionnel sans override)
├── values-staging.yaml
├── values-prod.yaml
├── templates/
│ ├── _helpers.tpl # include réutilisables (labels, fullname…)
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── configmap.yaml
│ ├── secret.yaml # ou ExternalSecret si ESO
│ ├── serviceaccount.yaml
│ └── NOTES.txt # affiché après install
└── charts/ # dépendances téléchargées
Chart.yaml
apiVersion: v2
name: payment-api
description: API de gestion des paiements
type: application # ou "library" pour un chart utilitaire
version: 1.3.0 # SemVer du chart (indépendant de l'app)
appVersion: "3.2.0" # version de l'image applicative
dependencies:
- name: postgresql
version: "15.x.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled # désactivable via values
Critère : incrémenter
versionà chaque changement de template ; incrémenterappVersionà chaque release applicative.
_helpers.tpl, base minimale
{{- define "mychart.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Deployment, template de référence
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
serviceAccountName: {{ include "mychart.fullname" . }}
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
envFrom:
- configMapRef:
name: {{ include "mychart.fullname" . }}
{{- if .Values.secret.enabled }}
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "mychart.fullname" . }}
key: db-password
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: http
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: http
initialDelaySeconds: 5
periodSeconds: 10
values.yaml, defaults complets
replicaCount: 1 # override à 2+ en prod
image:
repository: myregistry.azurecr.io/payment-api
tag: "" # vide = Chart.AppVersion
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: false # activé par values-prod.yaml
className: nginx
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
hosts:
- host: api.company.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
probes:
liveness:
path: /health/live
readiness:
path: /health/ready
secret:
enabled: false
postgresql:
enabled: false # activer localement si nécessaire
Commandes essentielles
# Scaffolding
helm create mychart
# Validation locale (toujours avant push)
helm lint mychart
helm template myrelease mychart -f values-prod.yaml | kubectl apply --dry-run=client -f -
# Déploiement
helm upgrade --install myrelease ./mychart \
-f values-prod.yaml \
--set image.tag=v3.2.0 \
--namespace prod \
--create-namespace \
--atomic \ # rollback auto si échec
--timeout 5m
# Différences avant upgrade (plugin helm-diff requis)
helm diff upgrade myrelease ./mychart -f values-prod.yaml --set image.tag=v3.2.0
# Rollback
helm rollback myrelease 1 # révision 1
# Dépendances
helm dependency update mychart
# Inspecter une release
helm status myrelease -n prod
helm get values myrelease -n prod
helm history myrelease -n prod
# OCI registry (Helm 3.8+)
helm push mychart-1.3.0.tgz oci://myregistry.azurecr.io/charts
helm install myrelease oci://myregistry.azurecr.io/charts/mychart --version 1.3.0
Critères de décision
| Besoin | Solution recommandée |
|---|---|
| Secret sensible en prod | ExternalSecret (ESO) ou Vault Agent Injector, pas kind: Secret en clair |
| Multi-environnements | values-<env>.yaml + -f à l'install, pas de Helm templating conditionnel excessif |
| Dépendance DB locale en dev | postgresql.enabled: true dans values-dev.yaml |
| App stateful (DB, Kafka…) | StatefulSet + PVC dans le template, pas Deployment |
| Chart réutilisable entre équipes | Chart de type library dans un registry OCI partagé |
| Rollout zero-downtime | strategy.type: RollingUpdate + minReadySeconds + probes correctes |
Anti-patterns / pièges
image.tag: latest, non reproductible. Toujours passer--set image.tag=$CI_SHA.- Secrets en clair dans values.yaml, ne jamais committer des credentials ; utiliser ESO, Vault ou
--set secret.password=$VARdepuis CI. helm installsans--atomic, laisse une release en étatFAILED; préférer--atomicen CI/CD.- Omettre
checksum/config, le pod ne redémarre pas quand la ConfigMap change sans cette annotation. - Oublier
helm dependency update, dossiercharts/vide → install échoue silencieusement. - Versioning mal séparé, ne pas synchroniser
version(chart) etappVersion(image) : les deux bougent indépendamment. - Templates trop conditionnels,
{{- if .Values.featureX }}…{{- end }}partout rend le chart illisible ; préférer des charts séparés ou des overlays Kustomize pour des variantes majeures. - Pas de
NOTES.txt, priver les utilisateurs du mode d'emploi post-install.
Bonnes pratiques 2026
- Publier dans un registry OCI (ACR, ECR, GHCR) plutôt qu'un chart repo HTTP classique.
- Utiliser
helm diffen CI pour générer un résumé lisible dans la PR avant merge. - Coupler avec
ct(chart-testing) pour le lint et les tests d'intégration automatisés. - Activer
NetworkPolicypar défaut dans le chart pour limiter le blast radius. - Générer la documentation des values avec
helm-docs(annotations# -- description). - Préférer
--atomic --timeouten CD pour garantir un rollback automatique en cas d'échec de rollout.