TP Helm bis : découvertes sympathiques avec podinfo

Durée estimée : 1 h à 1 h 30 Niveau : débutant, après le TP « Helm : déployer Supabase » Prérequis : minikube, kubectl et Helm installés

Objectifs

Ce petit TP est plus léger que le précédent. On y découvre, de façon visuelle et rapide :

  1. une application de démonstration qu'on personnalise en direct avec des values ;
  2. la répartition de charge et l'auto-réparation vues depuis le navigateur ;
  3. les tests Helm qui réussissent... puis qui échouent ;
  4. le retour arrière automatique d'une mise à jour ratée (--atomic) ;
  5. son propre chart en 10 minutes, avec un message d'accueil en ASCII art et un mot de passe généré qui survit aux mises à jour.

L'application support est podinfo, une petite application web écrite pour les démonstrations Kubernetes. Elle affiche le nom du Pod qui répond, et on peut lui demander de tomber en panne, de ralentir ou de ne plus être prête. Idéal pour observer Kubernetes réagir.

Sommaire


Partie 0 : Préparation

Un petit cluster suffit. On publie le NodePort 30990 sur votre machine pour avoir une adresse stable sous Linux, macOS et Windows :

minikube start -p helm-fun --driver=docker --cpus=2 --memory=2048 --ports=30990:30990
kubectl create namespace fun
kubectl config set-context --current --namespace=fun
mkdir -p ~/tp-helm-fun && cd ~/tp-helm-fun

Ajoutez le dépôt de podinfo :

helm repo add podinfo https://stefanprodan.github.io/podinfo
helm repo update
helm search repo podinfo
Ce TP a été écrit avec la version 6.15.0 du chart. On la fixe dans toutes les commandes.

Partie 1 : Installer en une commande

Installez podinfo, exposé en NodePort, avec un message personnalisé, sans aucun fichier :

helm install fun podinfo/podinfo --version 6.15.0 -n fun \
  --set service.type=NodePort \
  --set service.nodePort=30990 \
  --set ui.message="Bonjour depuis Helm"
kubectl get pods -w          # Ctrl+C quand le Pod est Running

Ouvrez http://localhost:30990 : votre message s'affiche, avec le nom du Pod et la version de l'application.

À comparer avec le TP Supabase : dans le chart Supabase, impossible de fixer le nodePort car le template ne l'exposait pas. Le chart podinfo, lui, le prévoit. Vérifiez-le :

La qualité d'un chart se juge aussi à ce qu'il permet de configurer.

Interrogez la même application en ligne de commande : podinfo répond en JSON quand ce n'est pas un navigateur qui l'appelle.

curl -s http://localhost:30990

Partie 2 : Personnaliser en direct

2.1 Passer à un fichier de values

Les --set deviennent vite illisibles. Créez values-fun.yaml :

replicaCount: 3

service:
  type: NodePort
  nodePort: 30990

ui:
  color: "#8e44ad"                       # couleur de fond, au format hexadécimal
  message: "Formation Kubernetes : tout va bien"

Appliquez avec upgrade --install, qui installe si la release n'existe pas et met à jour sinon. C'est la commande qu'on utilise dans les scripts et les pipelines CI/CD, car elle fonctionne dans les deux cas :

helm upgrade --install fun podinfo/podinfo --version 6.15.0 -n fun -f values-fun.yaml
kubectl get pods

Rechargez la page : la couleur et le message ont changé.

À vous : choisissez votre propre couleur et votre propre message, puis réappliquez. Quelques couleurs pour essayer : #e67e22, #16a085, #c0392b, #2c3e50.

2.2 Voir la répartition de charge

Avec 3 réplicas, chaque requête peut être servie par un Pod différent :

for i in $(seq 1 10); do curl -s http://localhost:30990 | grep hostname; done

Les noms de Pods alternent : le Service répartit les requêtes.

Dans le navigateur, le nom change moins souvent : le navigateur réutilise la même connexion TCP, qui reste attachée au même Pod. Ouvrez une fenêtre de navigation privée pour forcer une nouvelle connexion.

2.3 L'historique en un coup d'œil

helm history fun -n fun
helm get values fun -n fun

✅ Point de contrôle 2

  1. Quelle différence entre helm install et helm upgrade --install ?
  2. Pourquoi les noms de Pods alternent-ils avec curl ?

Partie 3 : Jouer avec les pannes

podinfo expose des adresses spéciales pour simuler des incidents. Gardez un second terminal ouvert qui surveille les Pods :

kubectl get pods -w

3.1 Faire planter un Pod

curl -s http://localhost:30990/panic

Dans le terminal de surveillance, un Pod passe en Error puis redémarre : la colonne RESTARTS augmente. Le kubelet a relancé le conteneur. Si vous rechargez la page, le site répond toujours grâce aux deux autres Pods.

3.2 Retirer un Pod du Service sans le tuer

Demandez à un Pod de se déclarer « pas prêt » :

curl -s -X POST http://localhost:30990/readyz/disable
kubectl get pods

Un Pod affiche 0/1 dans la colonne READY, mais il est toujours Running. Vérifiez qu'il ne reçoit plus de trafic :

kubectl get endpointslices -l kubernetes.io/service-name=fun-podinfo -o jsonpath='{range .items[*].endpoints[*]}{.targetRef.name}{" ready="}{.conditions.ready}{"\n"}{end}'
for i in $(seq 1 10); do curl -s http://localhost:30990 | grep hostname; done

Le Pod « pas prêt » n'apparaît plus dans les réponses. C'est le rôle de la readinessProbe : un Pod qui démarre, qui est surchargé ou en maintenance est retiré de la répartition de charge sans être redémarré.

Différence entre les deux sondes :

Remettez le Pod en service. Comme on ne sait pas quel Pod recevra la requête, le plus simple est de le supprimer : le Deployment en recrée un neuf.

kubectl delete pod $(kubectl get pods -l app.kubernetes.io/name=fun-podinfo -o jsonpath='{range .items[?(@.status.containerStatuses[0].ready==false)]}{.metadata.name}{" "}{end}')
kubectl get pods

3.3 Quelques autres adresses amusantes

curl -s http://localhost:30990/version                 # version de l'application
curl -s http://localhost:30990/env | head -n 20        # variables d'environnement du Pod
curl -s http://localhost:30990/headers                 # en-têtes HTTP reçus
time curl -s http://localhost:30990/delay/3 > /dev/null   # réponse volontairement lente
curl -si http://localhost:30990/status/418 | head -n 1    # renvoie le code HTTP demandé

Dans /env, repérez les variables KUBERNETES_SERVICE_HOST et FUN_PODINFO_SERVICE_PORT : Kubernetes injecte automatiquement dans chaque Pod l'adresse des Services du namespace.


Partie 4 : Les tests Helm

Le chart podinfo contient des tests. Lancez-les :

helm test fun -n fun

Chaque test affiche Succeeded. Un test Helm est un Pod lancé à la demande, qui vérifie que l'application répond correctement.

Voyons un test échouer. Le chart permet de simuler cet échec :

helm upgrade fun podinfo/podinfo --version 6.15.0 -n fun -f values-fun.yaml \
  --set faults.testFail=true
helm test fun -n fun

Au moins un test est en Failed. Consultez ses logs :

helm test fun -n fun --logs 2>&1 | tail -n 20

Revenez à l'état normal :

helm upgrade fun podinfo/podinfo --version 6.15.0 -n fun -f values-fun.yaml
helm test fun -n fun
Utilité : dans une chaîne CI/CD, on enchaîne helm upgrade puis helm test. Si un test échoue, la chaîne s'arrête et alerte l'équipe avant que les utilisateurs ne s'en aperçoivent.

Partie 5 : La mise à jour qui s'annule toute seule

5.1 Une mise à jour ratée, sans filet

Simulons une nouvelle version qui ne devient jamais prête :

helm upgrade fun podinfo/podinfo --version 6.15.0 -n fun -f values-fun.yaml \
  --set faults.unready=true
helm list -n fun
kubectl get pods

Helm annonce deployed alors que les nouveaux Pods restent à 0/1 ! Par défaut, Helm n'attend pas que l'application fonctionne : il envoie les manifestes à Kubernetes et rend la main. Le site reste accessible grâce aux anciens Pods que Kubernetes garde en vie, mais la mise à jour est bloquée à moitié.

Réparez à la main :

helm rollback fun -n fun
kubectl get pods
Sans numéro de révision, helm rollback revient à la révision précédente.

5.2 La même, avec filet

Relancez la même mise à jour ratée avec deux options :

  • --wait : attendre que les Pods soient prêts ;
  • --atomic : si ce n'est pas le cas avant le --timeout, revenir automatiquement en arrière.
helm upgrade fun podinfo/podinfo --version 6.15.0 -n fun -f values-fun.yaml \
  --set faults.unready=true --atomic --timeout 60s

Patientez une minute. Helm affiche une erreur puis annule lui-même la mise à jour :

helm history fun -n fun
kubectl get pods

L'historique contient une révision failed suivie d'un Rollback to ..., et les Pods sont revenus à l'état sain.

--atomic implique --wait. Avec Helm 4, cette option s'appelle --rollback-on-failure.

Ce que --atomic ne sait pas faire : annuler une modification de données (schéma de base de données, fichiers). Il ne revient que sur les manifestes Kubernetes.

✅ Point de contrôle 5

  1. Pourquoi Helm affichait-il deployed alors que les Pods n'étaient pas prêts ?
  2. Que fait --atomic exactement ?
  3. Pourquoi le site est-il resté accessible pendant la mise à jour ratée ?

Partie 6 : Son propre chart en 10 minutes

6.1 Générer un chart

helm create coucou
find coucou -type f | sort

helm create produit un chart complet et fonctionnel (Deployment nginx, Service, Ingress désactivé, tests...). C'est le meilleur point de départ pour écrire ses propres charts.

6.2 Un message d'accueil personnalisé

Remplacez le contenu de coucou/templates/NOTES.txt par :

   ____
  / ___|___  _   _  ___ ___  _   _
 | |   / _ \| | | |/ __/ _ \| | | |
 | |__| (_) | |_| | (_| (_) | |_| |
  \____\___/ \__,_|\___\___/ \__,_|

Release    : {{ .Release.Name }} (révision {{ .Release.Revision }})
Namespace  : {{ .Release.Namespace }}
Chart      : {{ .Chart.Name }} {{ .Chart.Version }}
Déployé le : {{ now | date "02/01/2006 à 15:04" }}
Réplicas   : {{ .Values.replicaCount }}

Mot de passe généré :
  kubectl get secret {{ include "coucou.fullname" . }}-mdp -n {{ .Release.Namespace }} -o jsonpath='{.data.password}' | base64 -d; echo
Objets disponibles dans les templates :

now | date "..." est un pipeline : la sortie de now est passée à la fonction date. Helm fournit plus de 70 fonctions (bibliothèque Sprig) : upper, b64enc, randAlphaNum, sha256sum, default...

6.3 Un mot de passe généré automatiquement

Créez coucou/templates/secret.yaml :

{{- $nom := printf "%s-mdp" (include "coucou.fullname" .) }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ $nom }}
type: Opaque
data:
  password: {{ randAlphaNum 20 | b64enc }}

Installez :

helm lint coucou
helm install coucou ./coucou -n fun

Le message d'accueil s'affiche. Récupérez le mot de passe avec la commande indiquée, notez-le, puis faites une mise à jour anodine :

kubectl get secret coucou-mdp -o jsonpath='{.data.password}' | base64 -d; echo
helm upgrade coucou ./coucou -n fun --set replicaCount=2
kubectl get secret coucou-mdp -o jsonpath='{.data.password}' | base64 -d; echo

Le mot de passe a changé ! randAlphaNum est exécuté à chaque rendu du template, donc à chaque upgrade. Pour une base de données, ce serait une panne.

6.4 Garder le mot de passe existant avec lookup

La fonction lookup permet à un template d'interroger le cluster pendant l'installation. On l'utilise pour réutiliser le mot de passe s'il existe déjà. Remplacez coucou/templates/secret.yaml par :

{{- $nom := printf "%s-mdp" (include "coucou.fullname" .) }}
{{- $existant := lookup "v1" "Secret" .Release.Namespace $nom }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ $nom }}
type: Opaque
data:
  {{- if $existant }}
  password: {{ index $existant.data "password" }}
  {{- else }}
  password: {{ randAlphaNum 20 | b64enc }}
  {{- end }}
helm upgrade coucou ./coucou -n fun --set replicaCount=3
kubectl get secret coucou-mdp -o jsonpath='{.data.password}' | base64 -d; echo
helm upgrade coucou ./coucou -n fun --set replicaCount=1
kubectl get secret coucou-mdp -o jsonpath='{.data.password}' | base64 -d; echo

Le mot de passe ne change plus.

Limite de lookup : helm template et --dry-run sans accès au cluster ne peuvent rien interroger. lookup y renvoie un résultat vide : le rendu affichera donc toujours un nouveau mot de passe, ce qui ne reflète pas ce que ferait la vraie mise à jour. Vérifiez :

6.5 Petits défis (facultatifs)

  1. Ajoutez dans values.yaml une clé auteur et affichez-la en majuscules dans le NOTES.txt (fonction upper).
  2. Affichez dans le NOTES.txt la version de Kubernetes du cluster (objet .Capabilities).
  3. N'affichez la ligne « Mot de passe généré » qu'à la première installation (indice : .Release.IsInstall).
  4. Empaquetez votre chart dans une archive .tgz avec helm package coucou. Où pourriez-vous la publier ?

Partie 7 : Nettoyage

helm uninstall fun coucou -n fun
kubectl delete namespace fun
minikube delete -p helm-fun
kubectl config use-context minikube

Annexe : Corrigés

Point de contrôle 2

  1. helm install échoue si la release existe déjà. helm upgrade --install installe la release si elle n'existe pas et la met à jour sinon : la même commande fonctionne à chaque exécution.
  2. curl ouvre une nouvelle connexion à chaque appel, et le Service répartit les connexions entre les Pods prêts.

Point de contrôle 5

  1. Sans --wait, Helm considère la mise à jour réussie dès que Kubernetes a accepté les manifestes, sans attendre que les Pods soient prêts.
  2. Il attend que les ressources soient prêtes. Si le délai --timeout est dépassé, il marque la révision en échec et revient automatiquement à la dernière révision réussie.
  3. La stratégie RollingUpdate ne supprime les anciens Pods que lorsque les nouveaux sont prêts. Les nouveaux ne l'étant jamais, les anciens ont continué à servir le trafic.

Défis 6.5

1. Dans coucou/values.yaml :

auteur: "votre prénom"

Dans NOTES.txt :

Auteur     : {{ .Values.auteur | upper }}

2.

Kubernetes : {{ .Capabilities.KubeVersion.Version }}

3.

{{- if .Release.IsInstall }}
Mot de passe généré :
  kubectl get secret {{ include "coucou.fullname" . }}-mdp -n {{ .Release.Namespace }} -o jsonpath='{.data.password}' | base64 -d; echo
{{- end }}

4.

helm package coucou
ls coucou-*.tgz

L'archive peut être publiée sur un serveur web statique avec un fichier index.yaml généré par helm repo index (c'est ainsi que fonctionne le dépôt podinfo, hébergé sur GitHub Pages), ou poussée dans un registre OCI (Harbor, GitLab, GitHub Container Registry) avec helm push.