Ce guide présente les 10 patterns essentiels pour transformer un chart Helm basique en chart production-ready. Vous apprendrez à ajouter les health probes, les limites de ressources, la sécurité des conteneurs, l'autoscaling, l'Ingress avec TLS, et les hooks. À la fin, votre chart respectera les standards Kubernetes pour la production.
Prérequis
Section intitulée « Prérequis »Ces patterns s'appliquent à un chart existant : ils ajoutent des blocs aux templates et des clés aux values, ils ne créent pas la structure. Le chart de référence est celui du module précédent, mais n'importe quel chart généré par helm create convient.
- Avoir créé un chart basique (voir module H2-01)
- Connaître les concepts Kubernetes : Deployment, Service, ConfigMap
- Comprendre les values et le templating Helm
Vue d'ensemble des patterns
Section intitulée « Vue d'ensemble des patterns »Voici les 10 patterns que tout chart production-ready doit implémenter :
| # | Pattern | Problème résolu | Impact production |
|---|---|---|---|
| 1 | Health Probes | Détection des conteneurs défaillants | Haute dispo, auto-healing |
| 2 | Resources | Conteneurs gourmands, éviction | Stabilité, QoS |
| 3 | SecurityContext | Conteneurs root, failles | Sécurité, compliance |
| 4 | ServiceAccount | Permissions excessives | Principe moindre privilège |
| 5 | ConfigMap | Config en dur dans l'image | Flexibilité, 12-factor |
| 6 | Ingress | Exposition HTTP sans TLS | Accès sécurisé |
| 7 | Autoscaling (HPA) | Charge variable | Élasticité, coûts |
| 8 | Scheduling | Répartition non optimale | HA, performance |
| 9 | PodAnnotations | Intégrations manquantes | Observabilité |
| 10 | Hooks | Migrations, initialisations | Orchestration |
Pattern 1, Health Probes configurables
Section intitulée « Pattern 1, Health Probes configurables »Sans probes, Kubernetes considère qu'un conteneur démarré est un conteneur en bonne santé, y compris quand l'application est figée. Ce pattern rend les trois sondes configurables depuis les values et ajoute un drapeau enabled, indispensable pour installer le chart avec une application qui n'expose aucun endpoint de santé.
Pourquoi des probes ?
Section intitulée « Pourquoi des probes ? »Les probes (sondes) permettent à Kubernetes de vérifier l'état de vos conteneurs :
| Probe | Question posée | Conséquence si échec |
|---|---|---|
livenessProbe | "Le conteneur est-il vivant ?" | Kubernetes le redémarre |
readinessProbe | "Le conteneur est-il prêt à recevoir du trafic ?" | Retiré du Service (plus de trafic) |
startupProbe | "Le conteneur a-t-il fini de démarrer ?" | Les autres probes attendent |
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Chaque sonde porte son propre bloc, drapeau enabled en tête. Notez la startupProbe désactivée par défaut : son failureThreshold: 30 combiné à periodSeconds: 5 laisse jusqu'à 150 secondes à une application lente avant que la liveness ne commence à la redémarrer.
livenessProbe: enabled: true httpGet: path: /healthz port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3
readinessProbe: enabled: true httpGet: path: /readyz port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3
startupProbe: enabled: false httpGet: path: /healthz port: http initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 30Décryptage des paramètres :
| Paramètre | Signification | Valeur typique |
|---|---|---|
initialDelaySeconds | Attente avant première vérification | 5-30s selon l'app |
periodSeconds | Intervalle entre vérifications | 10s |
timeoutSeconds | Timeout de la requête | 3s |
failureThreshold | Échecs consécutifs avant action | 3 |
Template avec flag enabled
Section intitulée « Template avec flag enabled »Le bloc if conditionne le rendu de la sonde entière : à false, aucun champ livenessProbe n'apparaît dans le Deployment. Chaque paramètre est repris explicitement, ce qui interdit d'ajouter une option sans modifier le template.
{{- if .Values.livenessProbe.enabled }}livenessProbe: httpGet: path: {{ .Values.livenessProbe.httpGet.path }} port: {{ .Values.livenessProbe.httpGet.port }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} failureThreshold: {{ .Values.livenessProbe.failureThreshold }}{{- end }}Rendu obtenu
Section intitulée « Rendu obtenu »--show-only restreint la sortie au fichier demandé plutôt que de rendre tout le chart. Le point à contrôler est que les deux sondes visent bien des chemins distincts.
helm template test-release mon-api --show-only templates/deployment.yamlExtrait du résultat :
livenessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3readinessProbe: httpGet: path: /readyz port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3Pattern 2, Resources (CPU/Memory)
Section intitulée « Pattern 2, Resources (CPU/Memory) »Un conteneur sans plafond peut absorber toute la mémoire de son nœud et provoquer l'éviction de ses voisins. Ce pattern expose un bloc resources unique, recopié tel quel dans le Deployment, avec un mécanisme qui l'omet entièrement quand l'utilisateur le laisse vide.
Pourquoi définir les resources ?
Section intitulée « Pourquoi définir les resources ? »Sans limites, un conteneur peut consommer toutes les ressources du nœud et impacter les autres applications. Kubernetes utilise les resources pour :
- Requests : réserver des ressources minimales (scheduling)
- Limits : plafonner la consommation (protection)
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Les valeurs proposées correspondent à une petite API : un dixième de cœur réservé, un plafond au double. La requests sert au scheduler pour placer le pod, la limits au kubelet pour le contraindre une fois lancé.
resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128MiNotation des ressources :
| Notation | Signification |
|---|---|
100m | 100 millicores = 0.1 CPU |
1 | 1 CPU complet |
128Mi | 128 Mébioctets |
1Gi | 1 Gibioctet |
Template avec pattern "with"
Section intitulée « Template avec pattern "with" »with remplace le contexte courant par la valeur qu'il teste, d'où le point seul passé à toYaml. C'est aussi la source d'erreur habituelle : à l'intérieur du bloc, .Values n'est plus accessible sans repasser par $.
{{- with .Values.resources }}resources: {{- toYaml . | nindent 2 }}{{- end }}Pourquoi ce pattern ?
- Si
resources: {}(vide), le bloc n'est pas rendu toYamlconvertit l'objet YAML proprementnindent 2ajoute une nouvelle ligne + 2 espaces d'indentation
Rendu obtenu
Section intitulée « Rendu obtenu »toYaml recopie le bloc des values sans en vérifier le contenu. Une unité mal écrite comme 256Mib traverserait donc le rendu sans erreur et ne serait rejetée qu'à l'apply, par l'API server.
resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128MiPattern 3, SecurityContext
Section intitulée « Pattern 3, SecurityContext »Par défaut, un conteneur démarre en root avec les capabilities Linux que lui laisse le runtime. Ce pattern expose deux blocs distincts, l'un au niveau du pod, l'autre au niveau du conteneur, et applique le moindre privilège sans rien changer à l'image.
Pourquoi sécuriser les conteneurs ?
Section intitulée « Pourquoi sécuriser les conteneurs ? »Par défaut, un conteneur peut s'exécuter en root et avoir des privilèges excessifs. Le securityContext applique le principe du moindre privilège.
Deux niveaux de sécurité
Section intitulée « Deux niveaux de sécurité »Les deux blocs portent le même nom dans le manifest Kubernetes mais ne se placent pas au même endroit : podSecurityContext sous spec, securityContext sous chaque conteneur. Certains champs existent des deux côtés, et la valeur définie au niveau du conteneur l'emporte alors sur celle du pod.
| Niveau | Scope | Exemple d'usage |
|---|---|---|
podSecurityContext | Tous les conteneurs du pod | fsGroup, runAsNonRoot |
securityContext | Un conteneur spécifique | capabilities, readOnlyRootFilesystem |
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »L'UID 65534 correspond au compte nobody présent dans la plupart des images de base. fsGroup s'applique aux volumes montés, ce qui évite qu'un conteneur non root se retrouve devant des fichiers qu'il n'a pas le droit de lire.
podSecurityContext: fsGroup: 65534 runAsNonRoot: true
securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 65534 runAsGroup: 65534Décryptage :
| Paramètre | Signification |
|---|---|
runAsNonRoot: true | Refuse de démarrer si le conteneur tente de tourner en root |
runAsUser: 65534 | UID nobody (utilisateur sans privilèges) |
allowPrivilegeEscalation: false | Empêche d'obtenir plus de droits qu'au départ |
capabilities.drop: [ALL] | Supprime toutes les capabilities Linux |
readOnlyRootFilesystem: true | Système de fichiers en lecture seule |
Template
Section intitulée « Template »Les deux blocs with sont indépendants : l'un peut rester vide sans empêcher l'autre de se rendre. L'indentation diffère selon la profondeur, nindent 4 sous spec et nindent 8 sous le conteneur, et une erreur ici produit un YAML invalide.
spec: {{- with .Values.podSecurityContext }} securityContext: {{- toYaml . | nindent 4 }} {{- end }} containers: - name: {{ .Chart.Name }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }}Rendu obtenu
Section intitulée « Rendu obtenu »Les champs ressortent triés par ordre alphabétique, toYaml ne conservant pas l'ordre du fichier de values. Vérifiez la présence de drop: [ALL] et de readOnlyRootFilesystem, les deux réglages qui empêchent le plus souvent une image non préparée de démarrer.
spec: securityContext: fsGroup: 65534 runAsNonRoot: true containers: - name: mon-api securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsGroup: 65534 runAsNonRoot: true runAsUser: 65534Pattern 4, ServiceAccount conditionnel
Section intitulée « Pattern 4, ServiceAccount conditionnel »Tout pod s'exécute sous un ServiceAccount, celui du namespace par défaut s'il n'en désigne aucun. Ce pattern crée un compte dédié au chart, coupe le montage automatique de son token, et laisse malgré tout la possibilité de réutiliser un compte géré ailleurs.
Pourquoi un ServiceAccount dédié ?
Section intitulée « Pourquoi un ServiceAccount dédié ? »Chaque pod Kubernetes a un ServiceAccount qui définit ses permissions d'accès à l'API Kubernetes. Le compte default est partagé par tous les pods sans SA explicite.
Problème : si vous montez un token dans un pod compromis, l'attaquant hérite des permissions du SA.
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Un champ name laissé vide déclenche la génération d'un nom à partir de celui de la release. Les annotations servent aux intégrations d'identité cloud, où l'annotation porte le rôle IAM que le compte est autorisé à endosser.
serviceAccount: create: true automount: false annotations: {} name: ""| Paramètre | Signification |
|---|---|
create | Créer un SA dédié (vs utiliser default) |
automount | Monter automatiquement le token ? (false recommandé) |
annotations | Pour IAM (AWS IRSA, GCP Workload Identity) |
name | Nom personnalisé (sinon généré automatiquement) |
Helper serviceAccountName
Section intitulée « Helper serviceAccountName »Ce helper centralise la logique en un seul endroit, appelé à la fois par le Deployment et par le template du ServiceAccount. Sans lui, les deux fichiers dupliqueraient la même condition et finiraient par diverger au premier changement.
{{/*Nom du ServiceAccount à utiliser*/}}{{- define "mon-api.serviceAccountName" -}}{{- if .Values.serviceAccount.create }}{{- default (include "mon-api.fullname" .) .Values.serviceAccount.name }}{{- else }}{{- default "default" .Values.serviceAccount.name }}{{- end }}{{- end }}Logique :
- Si
create: true→ utilise le nom personnalisé ou génère un nom - Si
create: false→ utilise le nom fourni oudefault
Template serviceaccount.yaml
Section intitulée « Template serviceaccount.yaml »Le if englobe le fichier entier : avec create: false, Helm produit un document vide, simplement ignoré à l'installation. automountServiceAccountToken est ici piloté depuis les values, alors que ce champ peut aussi être posé au niveau du pod.
{{- if .Values.serviceAccount.create -}}apiVersion: v1kind: ServiceAccountmetadata: name: {{ include "mon-api.serviceAccountName" . }} labels: {{- include "mon-api.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }}automountServiceAccountToken: {{ .Values.serviceAccount.automount }}{{- end }}Rendu obtenu
Section intitulée « Rendu obtenu »Avec serviceAccount.create: true :
apiVersion: v1kind: ServiceAccountmetadata: name: test-release-mon-api labels: helm.sh/chart: mon-api-0.2.0 app.kubernetes.io/name: mon-api app.kubernetes.io/instance: test-release app.kubernetes.io/version: "1.0.0" app.kubernetes.io/managed-by: HelmautomountServiceAccountToken: falseAvec serviceAccount.create: false :
helm template test-release mon-api --set serviceAccount.create=false \ --show-only templates/deployment.yaml | grep serviceAccountName serviceAccountName: defaultPattern 5, ConfigMap dynamique
Section intitulée « Pattern 5, ConfigMap dynamique »Une configuration gravée dans l'image impose de reconstruire celle-ci à chaque changement de valeur. Ce pattern génère une ConfigMap à partir d'un dictionnaire libre placé dans les values, puis l'injecte en bloc dans le conteneur avec envFrom.
Pourquoi externaliser la configuration ?
Section intitulée « Pourquoi externaliser la configuration ? »Le 12-factor app recommande de stocker la configuration dans l'environnement, pas dans le code. Les ConfigMaps permettent de modifier la config sans rebuilder l'image.
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Le sous-bloc data accepte n'importe quelle clé, le template les parcourant sans en connaître la liste. Les valeurs numériques comme API_TIMEOUT sont écrites entre guillemets, une ConfigMap ne stockant que des chaînes de caractères.
config: enabled: true data: LOG_LEVEL: "info" APP_ENV: "production" API_TIMEOUT: "30"Template configmap.yaml
Section intitulée « Template configmap.yaml »range sur un dictionnaire renvoie les clés triées, ce qui rend le manifest stable d'un rendu à l'autre. Le filtre quote n'est pas décoratif : sans lui, une valeur comme on ou yes serait interprétée comme un booléen par le parseur YAML.
{{- if .Values.config.enabled }}apiVersion: v1kind: ConfigMapmetadata: name: {{ include "mon-api.fullname" . }}-config labels: {{- include "mon-api.labels" . | nindent 4 }}data: {{- range $key, $value := .Values.config.data }} {{ $key }}: {{ $value | quote }} {{- end }}{{- end }}Le pattern range expliqué :
range $key, $value := .Values.config.dataparcourt chaque paire clé-valeur{{ $key }}et{{ $value }}sont des variables locales| quotemet la valeur entre guillemets (sécurité YAML)
Injection dans le Deployment
Section intitulée « Injection dans le Deployment »envFrom importe toutes les clés de la ConfigMap comme variables d'environnement, sans avoir à les déclarer une par une. En contrepartie, une clé dont le nom n'est pas un identifiant valide est ignorée sans message.
{{- if .Values.config.enabled }}envFrom: - configMapRef: name: {{ include "mon-api.fullname" . }}-config{{- end }}Rendu obtenu
Section intitulée « Rendu obtenu »Le rendu ne contient que les clés déclarées dans les values. Piège classique de ce pattern : mettre à jour la ConfigMap ne redémarre pas les pods, les variables d'environnement restant celles chargées au démarrage du conteneur.
apiVersion: v1kind: ConfigMapmetadata: name: test-release-mon-api-config labels: helm.sh/chart: mon-api-0.2.0 app.kubernetes.io/name: mon-api app.kubernetes.io/instance: test-release app.kubernetes.io/version: "1.0.0" app.kubernetes.io/managed-by: Helmdata: APP_ENV: "production" LOG_LEVEL: "info"Pattern 6, Ingress avec TLS optionnel
Section intitulée « Pattern 6, Ingress avec TLS optionnel »Un Service ne sort pas du cluster de lui-même : il faut un Ingress et un controller pour l'exposer en HTTP. Ce pattern rend l'objet entièrement optionnel, avec des règles de routage et des certificats TLS pilotés depuis les values.
Pourquoi un Ingress ?
Section intitulée « Pourquoi un Ingress ? »L'Ingress expose vos Services HTTP/HTTPS à l'extérieur du cluster avec :
- Routage basé sur le hostname ou le path
- Terminaison TLS
- Annotations pour le controller (rate limiting, auth, etc.)
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »L'Ingress est désactivé par défaut, choix habituel pour un chart réutilisable. La structure hosts est une liste de listes, chaque hôte portant ses propres chemins, et tls reste vide tant qu'aucun Secret de certificat n'existe dans le namespace.
ingress: enabled: false className: nginx annotations: {} hosts: - host: api.example.local paths: - path: / pathType: Prefix tls: []Template ingress.yaml
Section intitulée « Template ingress.yaml »Les boucles imbriquées sont le point délicat : à l'intérieur d'un range, le point ne désigne plus la racine des valeurs mais l'élément courant. D'où le $ devant .Values.service.port et devant l'appel au helper fullname, sans lequel le rendu échoue.
{{- if .Values.ingress.enabled -}}apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: {{ include "mon-api.fullname" . }} labels: {{- include "mon-api.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }}spec: {{- if .Values.ingress.className }} ingressClassName: {{ .Values.ingress.className }} {{- end }} {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ .path }} pathType: {{ .pathType }} backend: service: name: {{ include "mon-api.fullname" $ }} port: number: {{ $.Values.service.port }} {{- end }} {{- end }}{{- end }}Points clés du template :
{{- if .Values.ingress.enabled -}}: tout le fichier est conditionnel$pour accéder au contexte global dans les boucles imbriquées- Double
range: une boucle pour les hosts, une pour les paths
Rendu avec TLS
Section intitulée « Rendu avec TLS »L'échappement des points est obligatoire dans --set : sans les antislashs, Helm lirait nginx.ingress.kubernetes.io/rewrite-target comme une arborescence de clés imbriquées et non comme un nom d'annotation.
helm template test-release mon-api \ --set ingress.enabled=true \ --set 'ingress.annotations.nginx\.ingress\.kubernetes\.io/rewrite-target=/' \ --set 'ingress.tls[0].secretName=api-tls' \ --set 'ingress.tls[0].hosts[0]=api.example.local'Résultat :
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: test-release-mon-api labels: helm.sh/chart: mon-api-0.2.0 app.kubernetes.io/name: mon-api app.kubernetes.io/instance: test-release app.kubernetes.io/version: "1.0.0" app.kubernetes.io/managed-by: Helm annotations: nginx.ingress.kubernetes.io/rewrite-target: /spec: ingressClassName: nginx tls: - hosts: - "api.example.local" secretName: api-tls rules: - host: "api.example.local" http: paths: - path: / pathType: Prefix backend: service: name: test-release-mon-api port: number: 9898Pattern 7, Autoscaling (HPA)
Section intitulée « Pattern 7, Autoscaling (HPA) »Le HPA ajuste le nombre de réplicas selon les métriques observées. Le point sensible se situe ailleurs que dans l'objet lui-même : dès que l'autoscaling est actif, le champ replicas du Deployment doit disparaître, sans quoi Helm et le HPA se contredisent à chaque déploiement.
Pourquoi l'autoscaling ?
Section intitulée « Pourquoi l'autoscaling ? »L'HPA (Horizontal Pod Autoscaler) ajuste automatiquement le nombre de réplicas selon la charge. Avantages :
- Absorber les pics de trafic
- Réduire les coûts en période creuse
- Maintenir la performance
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Les deux seuils sont facultatifs et traités séparément dans le template : n'en renseigner qu'un produit un HPA à une seule métrique. Le minReplicas: 2 empêche l'application de retomber à un pod unique pendant les périodes creuses.
autoscaling: enabled: false minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 80Interaction avec le Deployment
Section intitulée « Interaction avec le Deployment »Quand l'HPA est actif, c'est lui qui gère le nombre de réplicas. Le Deployment ne doit pas définir replicas :
spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }}Template hpa.yaml
Section intitulée « Template hpa.yaml »L'apiVersion autoscaling/v2 est celle qui accepte plusieurs métriques dans un même objet. scaleTargetRef doit désigner le Deployment par son nom rendu, d'où l'appel au même helper fullname que dans le template du Deployment.
{{- if .Values.autoscaling.enabled }}apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: {{ include "mon-api.fullname" . }} labels: {{- include "mon-api.labels" . | nindent 4 }}spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: {{ include "mon-api.fullname" . }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} metrics: {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - type: Resource resource: name: cpu target: type: Utilization averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} {{- end }} {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - type: Resource resource: name: memory target: type: Utilization averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} {{- end }}{{- end }}Rendu obtenu
Section intitulée « Rendu obtenu »Le contrôle est négatif du côté du Deployment : l'extrait rendu ne doit plus contenir de champ replicas. Le HPA produit à la suite reprend les deux métriques déclarées dans les values.
helm template test-release mon-api --set autoscaling.enabled=trueExtrait deployment.yaml (sans replicas) :
spec: selector: matchLabels: app.kubernetes.io/name: mon-apihpa.yaml :
apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: test-release-mon-apispec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: test-release-mon-api minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80Pattern 8, Scheduling (affinity, tolerations, nodeSelector)
Section intitulée « Pattern 8, Scheduling (affinity, tolerations, nodeSelector) »Trois champs distincts pilotent le placement des pods et se combinent dans le même Deployment. Le chart les expose vides, ce qui les fait disparaître du manifest rendu : c'est à l'installation, via un fichier de values dédié, qu'on impose une répartition.
Pourquoi contrôler le scheduling ?
Section intitulée « Pourquoi contrôler le scheduling ? »Par défaut, Kubernetes place les pods sur n'importe quel nœud disponible. En production, vous voulez :
| Besoin | Solution |
|---|---|
| Pods sur des nœuds spécifiques (SSD, GPU) | nodeSelector |
| Pods sur des nœuds taintés | tolerations |
| Pods répartis sur plusieurs zones | podAntiAffinity |
| Pods co-localisés avec d'autres | podAffinity |
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Ces trois clés sont déclarées vides pour documenter leur existence : l'utilisateur du chart sait ainsi qu'il peut les remplir sans avoir à lire les templates.
nodeSelector: {}
tolerations: []
affinity: {}Template (dans le Deployment)
Section intitulée « Template (dans le Deployment) »Les trois blocs with suivent le même modèle et partagent la même indentation nindent 8, puisqu'ils se placent tous sous spec.template.spec du Deployment.
{{- with .Values.nodeSelector }}nodeSelector: {{- toYaml . | nindent 8 }}{{- end }}{{- with .Values.affinity }}affinity: {{- toYaml . | nindent 8 }}{{- end }}{{- with .Values.tolerations }}tolerations: {{- toYaml . | nindent 8 }}{{- end }}Exemple : haute disponibilité avec podAntiAffinity
Section intitulée « Exemple : haute disponibilité avec podAntiAffinity »Créez un fichier values-ha.yaml :
replicaCount: 3
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app.kubernetes.io/name: mon-api topologyKey: kubernetes.io/hostnameRendu :
helm template test-release mon-api -f values-ha.yaml --show-only templates/deployment.yaml | tail -15 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: labelSelector: matchLabels: app.kubernetes.io/name: mon-api topologyKey: kubernetes.io/hostname weight: 100Exemple : nœuds dédiés avec tolerations
Section intitulée « Exemple : nœuds dédiés avec tolerations »Une toleration n'attire pas le pod vers un nœud, elle l'autorise seulement à y aller malgré son taint. C'est le nodeSelector qui l'y envoie, d'où l'emploi des deux ensemble dans cet exemple.
helm template test-release mon-api \ --set 'nodeSelector.disktype=ssd' \ --set 'tolerations[0].key=dedicated' \ --set 'tolerations[0].operator=Equal' \ --set 'tolerations[0].value=api' \ --set 'tolerations[0].effect=NoSchedule' nodeSelector: disktype: ssd tolerations: - effect: NoSchedule key: dedicated operator: Equal value: apiPattern 9, PodAnnotations et PodLabels
Section intitulée « Pattern 9, PodAnnotations et PodLabels »Ces métadonnées ne changent rien au fonctionnement de l'application, mais elles commandent le comportement des outils qui l'entourent : collecte de métriques, injection de secrets, service mesh. Un chart qui ne les expose pas oblige à modifier ses templates pour chaque nouvelle intégration.
Pourquoi des annotations sur les pods ?
Section intitulée « Pourquoi des annotations sur les pods ? »Les annotations permettent d'intégrer vos pods avec d'autres outils :
| Outil | Annotation | Usage |
|---|---|---|
| Prometheus | prometheus.io/scrape: "true" | Découverte des métriques |
| Vault | vault.hashicorp.com/agent-inject: "true" | Injection de secrets |
| Istio | sidecar.istio.io/inject: "true" | Service mesh |
| Datadog | ad.datadoghq.com/... | APM et logs |
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Les deux dictionnaires restent vides par défaut : leur contenu dépend entièrement des outils déployés dans le cluster de destination.
podAnnotations: {}podLabels: {}Template
Section intitulée « Template »L'ordre compte dans le bloc labels : les selectorLabels sont rendus en premier, les podLabels de l'utilisateur ensuite. Un label personnalisé portant le même nom écraserait un label de sélection et détacherait le pod de son Deployment.
template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "mon-api.selectorLabels" . | nindent 8 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }}Exemple : scraping Prometheus
Section intitulée « Exemple : scraping Prometheus »Les points des clés d'annotation s'échappent comme pour l'Ingress. Le rendu montre que 9898 ressort entre guillemets : une annotation Kubernetes ne peut contenir qu'une chaîne de caractères.
helm template test-release mon-api \ --set 'podAnnotations.prometheus\.io/scrape=true' \ --set 'podAnnotations.prometheus\.io/port=9898' \ --set 'podAnnotations.prometheus\.io/path=/metrics' metadata: annotations: prometheus.io/path: /metrics prometheus.io/port: "9898" prometheus.io/scrape: "true"Pattern 10, Hooks Helm
Section intitulée « Pattern 10, Hooks Helm »Un hook est un objet Kubernetes ordinaire, marqué par des annotations qui indiquent à Helm de le créer à un moment précis, en dehors du cycle de vie normal de la release. C'est ce qui permet d'exécuter une migration de base de données avant que les nouveaux pods ne démarrent.
Quand utiliser les hooks ?
Section intitulée « Quand utiliser les hooks ? »Les hooks exécutent des Jobs à des moments précis du cycle de vie :
| Hook | Moment d'exécution | Cas d'usage |
|---|---|---|
pre-install | Avant l'installation | Vérifications préalables |
post-install | Après l'installation | Seed de données, notifications |
pre-upgrade | Avant la mise à jour | Migrations de BDD |
post-upgrade | Après la mise à jour | Tests de smoke |
pre-delete | Avant la suppression | Backup, nettoyage |
post-delete | Après la suppression | Notification |
Configuration dans values.yaml
Section intitulée « Configuration dans values.yaml »Le hook est désactivé par défaut, la commande servant de gabarit. L'image est pinnée sur une version précise : un hook lancé depuis un tag flottant peut changer de comportement entre deux déploiements sans qu'aucun fichier du chart n'ait bougé.
hooks: preInstall: enabled: false image: busybox:1.36@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 command: "echo 'Running pre-install hook'"Template hook-pre-install.yaml
Section intitulée « Template hook-pre-install.yaml »Les trois annotations pilotent tout : le moment d'exécution, l'ordre relatif par un poids, et le sort du Job une fois terminé. Sans hook-delete-policy, les Jobs s'accumulent à chaque déploiement et finissent par bloquer le suivant sur un conflit de nom.
{{- if .Values.hooks.preInstall.enabled }}apiVersion: batch/v1kind: Jobmetadata: name: {{ include "mon-api.fullname" . }}-pre-install labels: {{- include "mon-api.labels" . | nindent 4 }} annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": hook-succeededspec: template: spec: restartPolicy: Never containers: - name: pre-install image: {{ .Values.hooks.preInstall.image | default "busybox:1.36" }} command: ["sh", "-c", {{ .Values.hooks.preInstall.command | quote }}]{{- end }}Annotations expliquées :
| Annotation | Signification |
|---|---|
helm.sh/hook | Quand exécuter le Job |
helm.sh/hook-weight | Ordre d'exécution (négatif = plus tôt) |
helm.sh/hook-delete-policy | Quand supprimer le Job (hook-succeeded, before-hook-creation, hook-failed) |
Rendu obtenu
Section intitulée « Rendu obtenu »helm template produit le Job comme n'importe quel manifest : ce n'est qu'à l'installation que Helm lit les annotations et décale son exécution. Un hook ne se valide donc jamais complètement par un simple rendu.
helm template test-release mon-api \ --set hooks.preInstall.enabled=true \ --set 'hooks.preInstall.command=echo "Running migrations"'apiVersion: batch/v1kind: Jobmetadata: name: test-release-mon-api-pre-install annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": hook-succeededspec: template: spec: restartPolicy: Never containers: - name: pre-install image: busybox:1.36@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 command: ["sh", "-c", "echo \"Running migrations\""]Dépannage
Section intitulée « Dépannage »Ces symptômes viennent presque tous d'un décalage entre le chart et son environnement : une probe trop stricte pour le temps de démarrage réel, un securityContext incompatible avec l'image, ou un composant de cluster absent. Le rendu est correct dans tous ces cas, l'erreur n'apparaît qu'à l'exécution.
| Symptôme | Cause probable | Solution |
|---|---|---|
Pod en CrashLoopBackOff | Probe échoue, app pas prête | Augmenter initialDelaySeconds |
Pod OOMKilled | Limite memory dépassée | Augmenter limits.memory |
| Pod ne démarre pas (security) | Image incompatible securityContext | Vérifier UID de l'image |
HPA <unknown> | metrics-server absent | Installer metrics-server |
| Ingress sans effet | IngressController absent | Installer nginx-ingress ou traefik |
| Hook ne s'exécute pas | Mauvaise annotation | Vérifier helm.sh/hook |
Lab B2, Rendre un chart production-ready
Section intitulée « Lab B2, Rendre un chart production-ready »L'exercice reprend les patterns un par un sur une copie du chart précédent, avec helm lint --strict et helm template comme garde-fous avant toute installation. La vérification finale lit le securityContext réellement appliqué au conteneur en cours d'exécution, seule preuve que le réglage a traversé le rendu puis l'admission du cluster.
-
Partir du chart du Lab B1
Fenêtre de terminal cd /tmp && cp -r mon-api mon-api-prodcd mon-api-prod -
Mettre à jour values.yaml avec les patterns
Ajoutez les sections : probes, resources, securityContext, serviceAccount, autoscaling.
-
Valider avec helm lint
Fenêtre de terminal helm lint . --strict -
Prévisualiser le rendu complet
Fenêtre de terminal helm template my-release . > rendered.yamlcat rendered.yaml -
Tester avec autoscaling activé
Fenêtre de terminal helm template my-release . --set autoscaling.enabled=true | grep -A5 "kind: HorizontalPodAutoscaler" -
Tester avec Ingress et TLS
Fenêtre de terminal helm template my-release . \--set ingress.enabled=true \--set 'ingress.tls[0].secretName=my-tls' \--set 'ingress.tls[0].hosts[0]=api.example.local' -
Installer et vérifier sur le cluster
Fenêtre de terminal kubectl create namespace lab-b2helm install my-api . -n lab-b2kubectl get all,sa,cm -n lab-b2 -
Vérifier le securityContext du pod
Fenêtre de terminal kubectl get pod -n lab-b2 -o jsonpath='{.items[0].spec.containers[0].securityContext}' | jq -
Nettoyer
Fenêtre de terminal helm uninstall my-api -n lab-b2kubectl delete namespace lab-b2
Critères de réussite :
- Probes configurées et activées
- Resources limits/requests présents
- SecurityContext appliqué (runAsNonRoot, drop ALL capabilities)
- ServiceAccount créé avec automount: false
- HPA généré quand autoscaling.enabled=true
- Ingress avec TLS quand activé
- Pod Running avec securityContext vérifié
À retenir
Section intitulée « À retenir »Les six premiers points portent sur les objets Kubernetes, les deux derniers sur le templating lui-même. Ce sont ces deux-là qui séparent un chart robuste d'un chart approximatif : with évite de rendre des blocs vides, et $ sauve les boucles imbriquées.
- Les probes garantissent que Kubernetes détecte les conteneurs défaillants
- Les resources protègent le cluster contre les conteneurs gourmands
- Le securityContext applique le principe du moindre privilège
- L'Ingress expose vos services avec TLS et routage intelligent
- Le HPA adapte automatiquement le nombre de réplicas à la charge
- Les hooks permettent d'orchestrer des actions au bon moment
- Le pattern
{{- with .Values.xxx }}évite de rendre des blocs vides - Utilisez
$pour accéder au contexte global dans les bouclesrange
Prochaines étapes
Section intitulée « Prochaines étapes »Un chart production-ready reste un chart isolé. La suite porte sur son assemblage avec des subcharts, sur sa qualité via values.schema.json et helm-docs, puis sur sa publication dans un registry OCI.