Kubernetes orchestre vos conteneurs à l'échelle de production. Dans ce tutoriel complet, nous explorons les concepts clés, déployons une API Node.js sur un cluster local (Minikube), exposons l'application via un Service, et réalisons un rolling update sans interruption de service. Vous comprendrez le "pourquoi" derrière chaque décision.
Un cluster est un ensemble de machines (physiques ou virtuelles) orchestrées par Kubernetes. Chaque machine est appelée un nœud. Le cluster exécute vos conteneurs (pods), distribue le trafic, gère les mises à jour et redémarre automatiquement les applications qui tombent en panne.
Pourquoi c'est utile : Au lieu de gérer chaque serveur manuellement, vous décrivez l'état souhaité (« je veux 3 instances de mon API »), et Kubernetes maintient cet état automatiquement.
Minikube est un cluster Kubernetes minimaliste qui s'exécute localement sur votre machine (dans une VM ou un conteneur). Il simule un vrai cluster Kubernetes pour le développement.
Pourquoi l'utiliser : Au lieu de louer un cluster cloud (GKE, EKS) pendant le développement, Minikube vous permet de tester gratuitement sur votre laptop. Les concepts sont identiques, seule l'infrastructure change.
Avant de commencer, assurez-vous d'avoir :
brew install minikube (Mac) ou consulter docs.minikube.sigs.k8s.iobrew install kubectl (l'outil CLI pour parler à Kubernetes)Nous allons créer une API Express simple avec trois endpoints : la racine (pour tester), /health (pour les liveness probes) et /ready (pour les readiness probes). Ces endpoints permettront à Kubernetes de vérifier que l'application est saine.
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const VERSION = process.env.APP_VERSION || 'v1';
// Route principale : teste que l'API fonctionne et retourne des infos
app.get('/', (req, res) => {
res.json({
message: 'API Node.js opérationnelle',
version: VERSION, // Permet de voir quelle version tournée pendant les updates
hostname: require('os').hostname(), // Nom du pod Kubernetes (utile pour observer la distribution du trafic)
timestamp: new Date().toISOString() // Timestamp de la réponse
});
});
// Liveness probe : Kubernetes utilise cette route pour vérifier si le pod est vivant
// Si elle retourne un code != 200, le pod sera redémarré
app.get('/health', (req, res) => {
res.json({ status: 'healthy', version: VERSION });
});
// Readiness probe : Kubernetes utilise cette route pour vérifier si le pod peut recevoir du trafic
// Ici on simule une vérification : évidemment, on vérifie aussi les dépendances (DB, cache)
app.get('/ready', (req, res) => {
// En production, vérifier ici que les DB et caches sont connectés
res.json({ ready: true });
});
// Démarrer le serveur sur le port spécifié
app.listen(PORT, () => {
console.log(`Serveur v${VERSION} démarré sur le port ${PORT}`);
});
APP_VERSION et PORT depuis les variables d'environnement, qui seront passées par Kubernetes dans le Deployment.
Liveness Probe (/health) : vérifie que le processus est toujours vivant. Si elle échoue 3 fois d'affilée, Kubernetes redémarre le pod. C'est pour détecter les deadlocks ou les fuites mémoire qui congelaient l'app.
Readiness Probe (/ready) : vérifie que le pod peut traiter du trafic. Si elle échoue, Kubernetes retire le pod du Service (pas de redémarrage). C'est pour éviter de envoyer du trafic à un pod qui initialise une connexion DB ou qui subit une maintenance.
Cas d'usage : Une app qui démarre met 10 secondes à se connecter à la DB. Pendant ces 10 secondes, readiness = false, mais liveness = true (le processus est vivant). Kubernetes n'envoie pas de trafic, mais ne redémarre pas non plus.
{
"name": "api-k8s",
"version": "1.0.0",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.19.2"
}
}
package.json standard déclarant Express comme dépendance. Le script start lance l'application.
# Image de base : Node.js 20 sur Alpine Linux
# Alpine est petit (30 MB) et sécurisé — avantage pour Kubernetes
FROM node:20-alpine
# Créer le répertoire de travail
WORKDIR /app
# Copier package.json et package-lock.json si existant
# On utilise package*.json (wildcard) : copy les deux fichiers s'ils existent
COPY package*.json ./
# Installer les dépendances en mode production uniquement
# npm ci = "ci" = "clean install" = reproduction exacte des versions de package-lock.json
# --only=production = ne pas installer les devDependencies
RUN npm ci --only=production
# Copier le reste du code source
COPY . .
# Créer un groupe et un utilisateur non-root pour la sécurité
# Ne JAMAIS faire tourner Node en tant que root — vulnérabilité de sécurité
RUN addgroup -g 1001 -S nodejs && adduser -S nodeapp -u 1001
USER nodeapp
# Déclarer le port écouté (informatif, ne lie pas le port)
EXPOSE 3000
# Commande au démarrage du conteneur
CMD ["node", "app.js"]
package-lock.json contient les versions exactes de toutes les dépendances. npm ci respecte ce fichier, garantissant que le conteneur construit aujourd'hui fonctionnera comme celui construit hier. npm install pourrait upgrader des versions mineures, introduisant des bugs imprévisibles.
# Étape 1 : Démarrer Minikube (si pas déjà lancé)
minikube start
# Étape 2 : Pointer Docker vers le daemon Minikube
# eval charge les variables d'environnement Docker de Minikube dans votre shell
eval $(minikube docker-env)
# Étape 3 : Construire l'image directement dans le Docker de Minikube
# (pas besoin de pousser sur Docker Hub)
docker build -t api-nodejs:v1 .
# Étape 4 : Vérifier que l'image existe dans Minikube
docker images | grep api-nodejs
# output: api-nodejs v1 abc123def456 100MB 2 minutes ago
minikube start lance une VM ou un conteneur Minikube avec Kuberneteseval $(minikube docker-env) configure votre CLI Docker pour parler au daemon Docker à l'intérieur de Minikube, pas à celui de votre machinedocker build -t api-nodejs:v1 . construit l'image dans Minikubedocker images liste les images présentes dans MinikubeUne registry est un serveur qui stocke des images Docker. Exemples : Docker Hub (public), Google Container Registry (GCR), Amazon ECR. En production, vous poussez votre image sur une registry, puis Kubernetes la télécharge de là.
Pourquoi construire dans Minikube ? Minikube a sa propre registry locale. En exécutant eval $(minikube docker-env), vous construisez directement dedans, sans besoin d'une registry externe. C'est la manière développement idéale : rapide et sans frais.
Impérative : « Démarrer un conteneur, puis attacher un réseau, puis configurer les logs... » (commandes step-by-step)
Déclarative : « Voici le fichier YAML décrivant l'état souhaité. Kubernetes, fais en sorte que la réalité corresponde. »
Kubernetes fonctionne déclarativement. Vous décrivez l'état souhaité dans des fichiers YAML (Deployment, Service, etc.), puis Kubernetes garantit que l'état réel converge vers cet état souhaité — même si vous relancez les fichiers 10 fois.
# Quelle version de l'API Kubernetes utiliser
# apps/v1 est la version stable pour les Deployments
apiVersion: apps/v1
# Le type de ressource : Deployment
kind: Deployment
# Métadonnées de la ressource
metadata:
# Nom unique du Deployment dans le cluster
name: api-nodejs
# Labels pour trier/chercher les ressources (optionnels mais bonne pratique)
labels:
app: api-nodejs
tier: api
# Spécification : l'état souhaité
spec:
# Nombre de pods à maintenir (3 copies de l'app, pour haute disponibilité)
replicas: 3
# Sélecteur : quels pods sont gérés par ce Deployment ?
# Les pods ayant le label app:api-nodejs
selector:
matchLabels:
app: api-nodejs
# Stratégie de mise à jour : RollingUpdate = zéro downtime
strategy:
type: RollingUpdate
rollingUpdate:
# maxSurge : créer 1 pod SUPPLÉMENTAIRE avant de supprimer l'ancien
# = à un moment donné, 4 pods (3 + 1 surplus) au lieu de 3
# Permet aux nouveaux pods d'être prêts avant suppression des anciens
maxSurge: 1
# maxUnavailable : ne jamais avoir < 3 pods disponibles
# = 0 = garder toujours au moins le nombre de replicas actifs
# Garant : le service n'est jamais interrompu
maxUnavailable: 0
# Modèle de pod : description du conteneur
template:
metadata:
labels:
# Ce label correspondra au selector matchLabels
app: api-nodejs
spec:
containers:
# Conteneur de l'application
- name: api-nodejs
# Image Docker à utiliser (construite plus haut)
image: api-nodejs:v1
# imagePullPolicy: Never = utiliser seulement l'image locale Minikube
# (ne pas essayer de télécharger depuis une registry)
imagePullPolicy: Never
# Ports exposés par le conteneur
ports:
- containerPort: 3000 # Le port du conteneur
# Variables d'environnement passées au conteneur
env:
- name: APP_VERSION
value: "v1"
- name: PORT
value: "3000"
# Ressources CPU/mémoire : demandes et limites
resources:
# Ressources MINIMALES garanties par Kubernetes
# Kubernetes ne planifiera ce pod que sur un nœud avec assez de ressources libres
requests:
cpu: "100m" # 100 milliCPU = 0.1 CPU = 10% d'un CPU
memory: "128Mi" # 128 mégabytes
# Ressources MAXIMALES autorisées
# Si le pod dépasse les limites, Kubernetes le tue et le redémarre
limits:
cpu: "250m" # 0.25 CPU = 25% d'un CPU
memory: "256Mi" # 256 mégabytes
# ── LIVENESS PROBE : Redémarre le pod si l'API ne répond plus ──
livenessProbe:
httpGet:
path: /health # Appeler GET /health
port: 3000
# Attendre 15 secondes avant la première vérification (temps de démarrage)
initialDelaySeconds: 15
# Vérifier toutes les 20 secondes
periodSeconds: 20
# Redémarrer après 3 échecs consécutifs
failureThreshold: 3
# ── READINESS PROBE : Retire le pod du Service si pas prêt ──
readinessProbe:
httpGet:
path: /ready # Appeler GET /ready
port: 3000
# Attendre 5 secondes avant la première vérification (initialisation rapide)
initialDelaySeconds: 5
# Vérifier toutes les 10 secondes
periodSeconds: 10
apiVersion: v1
kind: Service
metadata:
name: api-nodejs-service
labels:
app: api-nodejs
spec:
# Type de Service : NodePort
# Expose le service sur un port statique (30000-32767) sur chaque nœud du cluster
# Utile pour le développement ; en production, on utilise LoadBalancer
type: NodePort
# Sélecteur : quels pods derrière ce service ?
# Tous les pods avec le label app:api-nodejs (produits par le Deployment)
selector:
app: api-nodejs
# Mappages de ports
ports:
- protocol: TCP
# Port du Service dans le cluster (endpoint interne)
port: 80
# Port du conteneur (ce qu'on expose depuis le pod)
targetPort: 3000
# Port publié sur chaque nœud (accessible depuis l'extérieur du cluster)
# L'utilisateur se connecte à noeud_ip:30080
nodePort: 30080
# Appliquer les manifestes (en ordre importe peu, mais Service d'abord c'est plus logique)
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Vérifier le Deployment
kubectl get deployments
# output:
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-nodejs 3/3 3 3 1m
# Voir tous les pods en cours d'exécution
kubectl get pods
# output:
# NAME READY STATUS RESTARTS AGE
# api-nodejs-7d9f8b5c9-abc12 1/1 Running 0 2m
# api-nodejs-7d9f8b5c9-def34 1/1 Running 0 2m
# api-nodejs-7d9f8b5c9-ghi56 1/1 Running 0 2m
# Voir les services créés
kubectl get services
# output:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# api-nodejs-service NodePort 10.96.201.45 nodes:30080
# Obtenir l'URL d'accès via Minikube
minikube service api-nodejs-service --url
# output: http://192.168.49.2:30080
# Obtenir l'URL du service
URL=$(minikube service api-nodejs-service --url)
# Tester la racine
curl $URL
# output: {"message":"API Node.js opérationnelle","version":"v1","hostname":"api-nodejs-7d9f8b5c9-abc12","timestamp":"2026-04-18T10:45:00.123Z"}
# Tester la probe de santé
curl $URL/health
# output: {"status":"healthy","version":"v1"}
# Tester la probe de disponibilité
curl $URL/ready
# output: {"ready":true}
L'atout majeur de Kubernetes est le rolling update : mettre à jour l'application sans que les utilisateurs ne remarquent l'interruption. Les anciennes instances sont remplacées progressivement par les nouvelles.
# (Supposons qu'on ajoute un endpoint /metrics à app.js)
# Puis relancer le build
eval $(minikube docker-env)
docker build -t api-nodejs:v2 .
# Vérifier que v2 existe
docker images | grep api-nodejs
Ouvrez un terminal séparé et lancez une boucle qui simule du trafic continu :
URL=$(minikube service api-nodejs-service --url)
# Boucle infinie : appels toutes les 500ms
while true; do
curl -s $URL | python3 -m json.tool | grep -E "version|hostname"
sleep 0.5
done
# output:
# "version": "v1",
# "hostname": "api-nodejs-7d9f8b5c9-abc12",
# "version": "v1",
# "hostname": "api-nodejs-7d9f8b5c9-def34",
# Option 1 : Mise à jour directe via kubectl set image
kubectl set image deployment/api-nodejs api-nodejs=api-nodejs:v2
# Option 2 : Éditer deployment.yaml (image: api-nodejs:v2) et relancer
kubectl apply -f deployment.yaml
# Suivre le rollout en temps réel
kubectl rollout status deployment/api-nodejs
# output:
# Waiting for deployment "api-nodejs" rollout to finish: 1 out of 3 new replicas updated...
# Waiting for deployment "api-nodejs" rollout to finish: 2 out of 3 new replicas updated...
# Waiting for deployment "api-nodejs" rollout to finish: 1 old replicas pending termination...
# deployment "api-nodejs" successfully rolled out
Dans le Terminal 1 (trafic), vous verrez la transition :
# Avant : seulement v1
# "version": "v1",
# "version": "v1",
# Pendant : mélange v1 et v2
# "version": "v1",
# "version": "v2",
# "version": "v1",
# "version": "v2",
# Après : seulement v2
# "version": "v2",
# "version": "v2",
maxSurge: 1 et maxUnavailable: 0, Kubernetes garantit qu'on a toujours au moins 3 pods prêts. Le Service envoie le trafic uniquement aux pods prêts (readiness probe). Aucune requête n'est jamais perdue.
Si la v2 présente un problème critique, Kubernetes permet d'annuler instantanément :
# Voir l'historique des déploiements
kubectl rollout history deployment/api-nodejs
# output:
# REVISION CHANGE-CAUSE
# 1 kubectl apply --filename=deployment.yaml
# 2 kubectl set image deployment/api-nodejs api-nodejs=api-nodejs:v2
# Revenir à la révision précédente (v1)
kubectl rollout undo deployment/api-nodejs
# Ou revenir à une révision spécifique
kubectl rollout undo deployment/api-nodejs --to-revision=1
# Vérifier que le rollback est en cours
kubectl rollout status deployment/api-nodejs
undo relance simplement le rolling update en sens inverse : réinstancier les pods v1 et arrêter les pods v2. C'est aussi rapide et transparent qu'une mise à jour normale.
# Scaler manuellement à 5 réplicas (augmente de 3 à 5)
kubectl scale deployment api-nodejs --replicas=5
# Vérifier que les 2 nouveaux pods démarrent
kubectl get pods
# output: 5 pods listés (3 anciens + 2 nouveaux)
# Réduire à 2 réplicas
kubectl scale deployment api-nodejs --replicas=2
# Vérifier que 3 pods sont arrêtés
kubectl get pods
# output: 2 pods listés
kubectl scale modifie dynamiquement le nombre de réplicas. Kubernetes ajoute ou supprime des pods immédiatement pour atteindre le nombre souhaité. C'est utile pour réagir rapidement à une charge qui augmente.
Le HPA augmente ou diminue automatiquement le nombre de réplicas en fonction des métriques observées (utilisation CPU, mémoire, ou custom metrics).
Exemple : Vous définissez « maintenir le CPU moyen à 70%. Si elle dépasse 70%, ajouter des pods. Si elle descend sous 50%, en retirer. »
Cas d'usage : Pendant une pic de trafic (Black Friday), le HPA ajoute automatiquement des pods. Quand le trafic baisse, il en retire. Zéro intervention manuelle.
# Créer un HPA : scaler automatiquement entre 2 et 10 pods basé sur le CPU
kubectl autoscale deployment api-nodejs \
--min=2 \
--max=10 \
--cpu-percent=70
# Vérifier le HPA
kubectl get hpa
# output:
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
# api-nodejs Deployment/api-nodejs 15%/70% 2 10 3 1m
# Le TARGETS indique l'utilisation actuelle (15%) vs la cible (70%)
# Supprimer le HPA
kubectl delete hpa api-nodejs
Voici un résumé des commandes les plus utiles pour gérer Kubernetes au quotidien :
# ─ Inspection ─
# Lister tous les pods du cluster
kubectl get pods
# Lister tous les déploiements
kubectl get deployments
# Lister tous les services
kubectl get services
# Lister toutes les ressources (pods, déploiements, services, etc.)
kubectl get all
# Obtenir des infos détaillées sur un pod spécifique
kubectl describe pod <nom-du-pod>
# ─ Logs et débogage ─
# Voir les logs d'un pod (les 50 dernières lignes)
kubectl logs <nom-du-pod> --tail=50
# Suivre les logs en temps réel (tail -f)
kubectl logs <nom-du-pod> -f
# Entrer dans un pod (ouvrir un shell interactif)
kubectl exec -it <nom-du-pod> -- sh
# Exécuter une commande dans un pod
kubectl exec <nom-du-pod> -- ps aux
# ─ Mises à jour et rollouts ─
# Appliquer/mises à jour les manifestes YAML
kubectl apply -f deployment.yaml
# Mettre à jour l'image d'un déploiement
kubectl set image deployment/api-nodejs api-nodejs=api-nodejs:v3
# Voir le statut du rollout
kubectl rollout status deployment/api-nodejs
# Voir l'historique des déploiements
kubectl rollout history deployment/api-nodejs
# Annuler une mise à jour (revenir à la version précédente)
kubectl rollout undo deployment/api-nodejs
# ─ Scaling ─
# Scaler à un nombre de réplicas donné
kubectl scale deployment api-nodejs --replicas=5
# Créer un autoscaler
kubectl autoscale deployment api-nodejs --min=2 --max=10 --cpu-percent=70
# ─ Suppression ─
# Supprimer un deployment (arrête tous les pods associés)
kubectl delete deployment api-nodejs
# Supprimer un service
kubectl delete service api-nodejs-service
# Supprimer via les fichiers YAML
kubectl delete -f deployment.yaml -f service.yaml
# ─ Utilitaires ─
# Afficher les informations du cluster
kubectl cluster-info
# Voir les ressources utilisées (CPU, mémoire) par les pods
kubectl top pods
# Voir les ressources utilisées par les nœuds
kubectl top nodes
# Nettoyage complet : arrêter et supprimer Minikube
minikube delete
get pour lister, describe pour les détails, logs pour débogage, apply pour déployer, delete pour nettoyer.
eval $(minikube docker-env) && docker build -t api-nodejs:v1 .kubectl apply -f deployment.yaml service.yamlcurl $(minikube service api-nodejs-service --url)kubectl set image deployment/api-nodejs api-nodejs=api-nodejs:v2kubectl rollout status deployment/api-nodejskubectl scale deployment api-nodejs --replicas=5 ou kubectl autoscale ...kubectl logs et kubectl describe pod sont vos meilleurs amis pour le débogage.