Kubernetes pour les débutants : orchestrer des conteneurs à grande échelle
Pods, Deployments, Services, kubectl — les concepts fondamentaux de K8s expliqués depuis zéro, avec des manifestes YAML qui tournent vraiment.
Docker Compose résout le problème de lancer plusieurs conteneurs sur une machine. Kubernetes résout le problème de les faire tourner sur des dizaines de machines, de les répliquer automatiquement, de les redémarrer en cas de crash, et de déployer de nouvelles versions sans interruption de service.
La complexité de Kubernetes est réelle — mais elle existe pour une bonne raison. Ce guide commence par là : comprendre le problème avant l'outil.
Pourquoi Kubernetes existe
Docker Compose sur un seul serveur fonctionne. Jusqu'à ce que ce serveur tombe. Ou que le trafic triple en une heure. Ou qu'une mise à jour d'une de vos images soit défaillante et qu'il faille revenir en arrière en 30 secondes.
Avant Kubernetes, les grandes équipes géraient ça avec des scripts maison, des outils propriétaires (Mesos, Swarm), ou beaucoup de travail manuel. Kubernetes — sorti par Google en 2014, basé sur leur système interne Borg — a standardisé l'orchestration de conteneurs.
Ce que K8s fait automatiquement :
- Scheduling : décider sur quel serveur tourne quel conteneur selon les ressources disponibles
- Self-healing : redémarrer les conteneurs crashés, remplacer les nodes défaillants
- Scaling horizontal : ajouter ou supprimer des réplicas selon la charge
- Rolling updates : déployer une nouvelle version progressivement, rollback automatique si ça échoue
- Service discovery : les conteneurs se trouvent entre eux par nom, sans IP hardcodée
Prérequis : Kubernetes orchestre des conteneurs Docker. Si les bases de Docker — images, conteneurs, Dockerfile ne sont pas acquises, commencez par là.
Architecture : le vocabulaire fondamental
Kubernetes introduit beaucoup de termes. Voici ceux qui comptent au départ.
Cluster
Un cluster est l'ensemble de l'infrastructure Kubernetes. Il contient deux types de composants :
Control Plane — le cerveau. Gère l'état désiré du cluster, schedule les workloads, expose l'API Kubernetes. En production, il tourne sur des machines dédiées (gérées par votre cloud provider avec EKS, GKE, AKS).
Nodes (Worker Nodes) — les muscles. Les machines qui font tourner vos applications. Chaque node exécute kubelet (agent qui comunique avec le control plane) et un container runtime (Docker, containerd).
┌─────────────────────────────────────────────────────────┐
│ CLUSTER │
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ Control Plane │ │ Worker Nodes │ │
│ │ │ │ │ │
│ │ API Server │◄──►│ Node 1 Node 2 Node 3│ │
│ │ Scheduler │ │ ┌─────┐ ┌─────┐ ┌─────┐│ │
│ │ etcd │ │ │ Pod │ │ Pod │ │ Pod ││ │
│ │ Controller Mgr │ │ └─────┘ └─────┘ └─────┘│ │
│ └──────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────┘Pod
Le Pod est l'unité de base de déploiement dans Kubernetes. Un Pod contient un ou plusieurs conteneurs qui partagent le même réseau et le même stockage local.
Dans 95% des cas, un Pod = un conteneur. Les cas multi-conteneurs existent (pattern sidecar — un conteneur principal + un proxy comme Envoy, ou un agent de logs), mais débutez avec un conteneur par Pod.
Point crucial : les Pods sont éphémères. Kubernetes peut les tuer et en créer de nouveaux à tout moment. Ne stockez pas d'état dans un Pod (sauf via des volumes persistants). Ne codez jamais en dur l'IP d'un Pod — elle change à chaque recréation.
Deployment
Un Deployment décrit l'état désiré : "je veux 3 réplicas de ce Pod, avec cette image". Le Controller Manager surveille en permanence que l'état réel correspond à l'état désiré. Si un Pod crash, il en recrée un. Si vous changez l'image, il remplace les Pods progressivement.
Service
Un Service est un point d'accès réseau stable vers un groupe de Pods. Puisque les Pods meurent et renaissent avec des IPs qui changent, le Service fournit une IP et un nom DNS fixes. Il route le trafic vers les Pods vivants correspondants via un sélecteur.
Namespace
Un namespace est une partition logique du cluster. Plusieurs équipes peuvent partager le même cluster avec des namespaces isolés. Par défaut, vos ressources vont dans le namespace default.
Installation locale : minikube ou kind
Pour apprendre K8s sans cloud provider, deux options :
minikube — simule un cluster complet dans une VM ou un conteneur Docker. Simple à installer.
# Installation minikube
# macOS
brew install minikube
# Linux
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Démarrer un cluster local
minikube start
# Vérifier
minikube statuskind (Kubernetes IN Docker) — cluster multi-nodes dans des conteneurs. Plus léger, recommandé pour CI.
# Installation kind
brew install kind # macOS
# ou via Go : go install sigs.k8s.io/kind@latest
kind create cluster --name mon-clusterkubectl — l'interface de commande
kubectl est le CLI pour interagir avec n'importe quel cluster Kubernetes.
# Installation
# macOS
brew install kubectl
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# Vérifier la connexion au cluster
kubectl cluster-info
kubectl get nodesNAME STATUS ROLES AGE VERSION
minikube Ready control-plane 2m v1.30.0Premier déploiement
Tout dans Kubernetes est décrit en YAML — des manifestes qui décrivent l'état désiré. kubectl apply -f fichier.yaml envoie ce manifeste à l'API Server, qui s'occupe de faire correspondre le cluster à cette description.
Déployer une application
apiVersion: apps/v1
kind: Deployment
metadata:
name: mon-app
labels:
app: mon-app
spec:
replicas: 3 # 3 Pods en parallèle
selector:
matchLabels:
app: mon-app # Sélectionner les Pods avec ce label
template:
metadata:
labels:
app: mon-app # Label appliqué aux Pods créés
spec:
containers:
- name: mon-app
image: nginx:1.25 # Image Docker à utiliser
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi" # Mémoire minimale garantie
cpu: "250m" # 250 millicores = 0.25 CPU
limits:
memory: "128Mi" # Maximum autorisé
cpu: "500m"kubectl apply -f deployment.yaml
# Vérifier les Pods créés
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# mon-app-7d9f8b6c5-4kx2p 1/1 Running 0 30s
# mon-app-7d9f8b6c5-9mnt7 1/1 Running 0 30s
# mon-app-7d9f8b6c5-vp8r2 1/1 Running 0 30s
# Détails d'un Pod
kubectl describe pod mon-app-7d9f8b6c5-4kx2p
# Logs d'un conteneur
kubectl logs mon-app-7d9f8b6c5-4kx2p
# Shell dans un Pod (debugging)
kubectl exec -it mon-app-7d9f8b6c5-4kx2p -- shSupprimez un Pod manuellement :
kubectl delete pod mon-app-7d9f8b6c5-4kx2pKubernetes en recrée immédiatement un nouveau. L'état désiré dit "3 réplicas" — le Controller Manager maintient ça.
Exposer l'application avec un Service
apiVersion: v1
kind: Service
metadata:
name: mon-app-service
spec:
selector:
app: mon-app # Route vers les Pods avec ce label
ports:
- protocol: TCP
port: 80 # Port du Service
targetPort: 80 # Port du conteneur
type: ClusterIP # Accessible uniquement dans le clusterkubectl apply -f service.yaml
kubectl get services
# NAME TYPE CLUSTER-IP PORT(S) AGE
# mon-app-service ClusterIP 10.96.128.44 80/TCP 10sTypes de Services
ClusterIP (par défaut) — IP interne au cluster uniquement. Idéal pour la communication entre services.
NodePort — expose le service sur un port de chaque Node (30000–32767). Accessible depuis l'extérieur via <IP-du-node>:<nodePort>. Pratique pour le dev local.
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080 # ou laisser K8s choisirLoadBalancer — crée automatiquement un load balancer cloud (AWS ELB, GCP LB...). En production sur un cloud provider, c'est le type à utiliser pour exposer une API publique.
# Avec minikube, accéder à un NodePort
minikube service mon-app-service --urlConfigMaps et Secrets : gérer la configuration
Hardcoder des valeurs dans l'image Docker est une mauvaise pratique. Kubernetes fournit deux objets pour injecter la configuration.
ConfigMap — données non sensibles
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "postgres-service"
LOG_LEVEL: "info"
APP_ENV: "production"Secret — données sensibles
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
# Valeurs encodées en base64 (pas chiffrées — utiliser Sealed Secrets ou Vault en prod)
DATABASE_PASSWORD: cGFzc3dvcmQxMjM= # echo -n "password123" | base64
JWT_SECRET: bXlzdXBlcnNlY3JldA==# Encoder une valeur
echo -n "ma-valeur-secrete" | base64
# Créer depuis la ligne de commande (sans fichier YAML)
kubectl create secret generic app-secrets \
--from-literal=DATABASE_PASSWORD=password123 \
--from-literal=JWT_SECRET=mysupersecretInjecter dans un Deployment
spec:
containers:
- name: mon-app
image: mon-app:1.0
envFrom:
- configMapRef:
name: app-config # Toutes les clés du ConfigMap
- secretRef:
name: app-secrets # Toutes les clés du Secret
env:
- name: POD_NAME # Variable dynamique depuis les metadata du Pod
valueFrom:
fieldRef:
fieldPath: metadata.nameScaling et Rolling Updates
Scaling horizontal
# Passer à 5 réplicas
kubectl scale deployment mon-app --replicas=5
# Ou modifier replicas dans le YAML et re-apply
kubectl apply -f deployment.yamlEn production, utilisez l'HorizontalPodAutoscaler — il ajuste automatiquement le nombre de réplicas selon l'usage CPU/mémoire.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mon-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mon-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale up si CPU > 70%Rolling Update — zéro downtime
Quand vous mettez à jour l'image dans votre Deployment, Kubernetes effectue un rolling update par défaut :
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Créer 1 Pod supplémentaire pendant la mise à jour
maxUnavailable: 0 # Jamais de Pod unavailable pendant la mise à jour# Mettre à jour l'image
kubectl set image deployment/mon-app mon-app=mon-app:2.0
# Suivre le rollout
kubectl rollout status deployment/mon-app
# Revenir en arrière si problème
kubectl rollout undo deployment/mon-app
# Voir l'historique des rollouts
kubectl rollout history deployment/mon-appKubernetes crée les nouveaux Pods, attend qu'ils soient Ready, puis supprime les anciens. Zéro interruption de service.
Namespaces : organiser le cluster
# Créer des namespaces
kubectl create namespace staging
kubectl create namespace production
# Déployer dans un namespace spécifique
kubectl apply -f deployment.yaml -n staging
# Voir les ressources d'un namespace
kubectl get pods -n staging
kubectl get all -n staging
# Changer le namespace par défaut de votre contexte
kubectl config set-context --current --namespace=stagingEn production, séparez au minimum staging et production. Ajoutez des ResourceQuotas pour limiter les ressources par namespace.
apiVersion: v1
kind: ResourceQuota
metadata:
name: staging-quota
namespace: staging
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"Probes : indiquer à K8s l'état de votre app
K8s doit savoir si votre Pod est prêt à recevoir du trafic et s'il est encore en vie. Sans probes, il envoie du trafic vers des Pods qui démarrent encore.
spec:
containers:
- name: mon-app
image: mon-app:1.0
# Liveness probe : Pod mort → K8s le redémarre
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15 # Attendre 15s avant le premier check
periodSeconds: 10 # Vérifier toutes les 10s
failureThreshold: 3 # 3 échecs → redémarrage
# Readiness probe : Pod pas prêt → K8s ne lui envoie pas de trafic
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2Votre application doit exposer deux endpoints :
// /health — est-ce que l'app tourne ?
app.get('/health', (req, res) => res.json({ status: 'ok' }))
// /ready — est-ce que l'app est prête à recevoir du trafic ?
// (connexion DB établie, cache chaud, etc.)
app.get('/ready', async (req, res) => {
try {
await db.query('SELECT 1')
res.json({ status: 'ready' })
} catch {
res.status(503).json({ status: 'not ready' })
}
})Commandes kubectl du quotidien
# Vue d'ensemble du cluster
kubectl get nodes
kubectl get all # Tout dans le namespace courant
kubectl get pods --all-namespaces # Tous les namespaces
# Debugging
kubectl describe pod <nom> # Événements et config détaillée
kubectl logs <pod> -f # Logs en temps réel
kubectl logs <pod> --previous # Logs du Pod précédent (post-crash)
kubectl exec -it <pod> -- sh # Shell dans le conteneur
# Ressources
kubectl top nodes # CPU/RAM par node
kubectl top pods # CPU/RAM par Pod
# Appliquer et supprimer
kubectl apply -f manifeste.yaml # Créer ou mettre à jour
kubectl delete -f manifeste.yaml # Supprimer tout ce qui est dans le fichier
kubectl delete pod <nom> --force # Suppression immédiate (sans graceful shutdown)
# Contextes — gérer plusieurs clusters
kubectl config get-contexts # Lister les clusters configurés
kubectl config use-context production # Basculer vers le cluster productionIntégration CI/CD
Un pipeline typique avec GitHub Actions :
name: Build & Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build & push image Docker
run: |
docker build -t mon-registry/mon-app:${{ github.sha }} .
docker push mon-registry/mon-app:${{ github.sha }}
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Rolling update
run: |
kubectl set image deployment/mon-app \
mon-app=mon-registry/mon-app:${{ github.sha }} \
--namespace=production
kubectl rollout status deployment/mon-app -n productionChaque push sur main déclenche un rolling update. Si le rollout status échoue (pods qui ne deviennent pas Ready), le workflow échoue et l'équipe est notifiée — le déploiement précédent reste actif.
Ce que vous n'avez pas encore
Kubernetes a une surface vaste. Ce guide couvre le départ. Ce qui vient ensuite :
Ingress — routage HTTP vers différents Services depuis un seul point d'entrée (avec nginx-ingress ou Traefik). Remplace les LoadBalancers multiples.
Persistent Volumes — stockage qui survit à la mort des Pods. Nécessaire pour les bases de données en K8s.
Helm — gestionnaire de packages Kubernetes. Les applications complexes (cert-manager, Prometheus, PostgreSQL) se déploient en une commande avec Helm.
RBAC — contrôle d'accès. Qui peut faire quoi sur quels resources dans quels namespaces.
Monitoring — Prometheus + Grafana est le standard. Kubernetes expose nativement des métriques que Prometheus scrappe.
K8s n'est pas adapté à tous les projets. Un VPS avec Docker Compose et des déploiements via SSH gère parfaitement la majorité des applications. Kubernetes devient pertinent quand vous avez des besoins de scaling automatique, de haute disponibilité, ou plusieurs équipes sur la même infrastructure.
L'investissement dans l'apprentissage est réel. Mais les concepts — état désiré, self-healing, rolling updates — sont des idées qui changent la façon de penser l'infrastructure.