Docker et Kubernetes : un guide pratique pour les developpeurs modernes
Un guide sans fioritures sur la containerisation, les bonnes pratiques Dockerfile, les fondamentaux de Kubernetes et savoir quand vous avez reellement besoin d'un orchestrateur.
Tout developpeur finit par se heurter au meme mur. Vous ecrivez du code sur votre machine, cela fonctionne parfaitement, vous le poussez en staging, et cela explose avec une erreur cryptique concernant une dependance systeme manquante ou une version de bibliotheque différente. Le probleme classique "ca marche sur ma machine" a hante le developpement logiciel pendant des decennies, et Docker ne l'a pas seulement resolu - il a rendu la solution si simple qu'il n'y a desormais plus d'excuse pour ne pas l'utiliser.
Mais Docker ne resout que le probleme d'empaquetage. Une fois votre application containerisee, vous devez encore l'executer en production - potentiellement sur plusieurs serveurs, avec equilibrage de charge, deploiements sans temps d'arret, verifications de sante et recuperation automatique. C'est la que Kubernetes entre en jeu. Et c'est aussi la que la plupart des developpeurs se perdent dans la complexite, car Kubernetes introduit un vocabulaire entierement nouveau d'abstractions qui prend du temps a internaliser.
Ce guide coupe a travers le bruit. Il couvre ce que sont reellement les conteneurs sous le capot, comment ecrire des Dockerfiles efficaces et securises, les concepts Kubernetes que vous devez connaitre pour deployer des applications reelles, les compromis entre Docker Compose et Kubernetes pour le developpement local, et - plus important encore - quand utiliser chaque outil et quand le laisser de cote.
Ce que sont reellement les conteneurs
Un conteneur n'est pas une machine virtuelle legere. C'est l'idee recue la plus courante, et elle conduit a des modeles mentaux incorrects. Une machine virtuelle execute un systeme d'exploitation invite complet sur un hyperviseur, avec son propre noyau, sa propre allocation memoire et ses propres pilotes de peripheriques. Un conteneur partage le noyau hote et s'execute comme un processus espace utilisateur isole. L'isolation est fournie par des fonctionnalites du noyau Linux - les namespaces pour l'isolation des processus, les cgroups pour les limites de ressources et les overlay filesystems pour des couches d'image efficaces.
Cette distinction est importante car elle explique le comportement que vous observerez. Les conteneurs demarrent en millisecondes car il n'y a pas de noyau a demarrer. Ils utilisent moins de memoire car il n'y a pas de noyau duplique ni de processus systeme redondants. Mais cela signifie aussi qu'un conteneur fonctionnant sur Linux ne peut pas executer une version de noyau différente de celle de l'hote, et qu'un conteneur Windows necessite un hote Windows (ou une VM Linux Hyper-V sur les versions plus anciennes). Sur macOS, Docker Desktop execute les conteneurs Linux dans une VM legere precisement pour cette raison.
Une image est le modele en lecture seule - un instantane d'un systeme de fichiers plus des metadonnees. Un conteneur est une instance en cours d'execution de cette image, avec une couche inscriptible par-dessus. Vous pouvez construire une image une fois et executer des dizaines de conteneurs a partir d'elle. C'est l'unite fondamentale de fonctionnement dans le monde Docker, et la comprendre clairement rend tout le reste plus facile.
Bonnes pratiques Dockerfile
Un Dockerfile est une recette pour construire une image. Chaque instruction cree une nouvelle couche, et les couches sont mises en cache. Cela signifie que l'ordre des instructions affecte directement la vitesse de construction, la taille de l'image et la securite. Voici les principes qui comptent le plus dans les projets reels.
Ordonner les couches par frequence de changement
Docker met en cache chaque couche apres sa construction. Si une couche n'a pas change depuis la derniere construction, Docker reutilise la version en cache. Cela signifie que vous devez placer les instructions qui changent rarement en haut et celles qui changent frequemment en bas. Les dependances systeme (apt-get, apk add) ne changent presque jamais. Les dependances d'application (npm install, pip install) changent quand vous mettez a jour votre lockfile. Le code source de l'application change a chaque commit.
# Mauvais : code source avant les dependances
FROM node:20-alpine
WORKDIR /app
COPY . . # invalide le cache pour tout ce qui suit
RUN npm ci # s'execute a chaque build, meme si package.json n'a pas change
EXPOSE 3000
CMD ["node", "dist/index.js"]
# Bon : ordre des couches stable d'abord
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci # mis en cache sauf si package.json change
COPY . . # seul le code source change cette couche
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]La difference est dramatique. Le mauvais Dockerfile reconstruit toutes les dependances a chaque commit. Le bon ne reconstruit les dependances que quand le lockfile change, ce qui arrive generalement une fois par pull request plutot qu'a chaque commit. Sur un projet Node.js avec 500 dependances, cela peut economiser deux minutes par build.
Constructions multi-etapes
Les constructions multi-etapes vous permettent d'utiliser un seul Dockerfile pour construire votre application et produire une image d'execution minimale. L'etape de construction contient les compilateurs, les dependances de developpement et les outils de construction. L'etape d'execution ne copie que le resultat compile. Cela maintient les images de production petites et reduit la surface d'attaque.
# Etape de construction
FROM node:20-alpine AS builder
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Etape d'execution — demarre a partir d'une base fraiche et minimale
FROM node:20-alpine AS runner
WORKDIR /app
# Seulement ce qui est necessaire pour executer
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/package.json ./
COPY --from=builder /build/node_modules ./node_modules
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]L'etape d'execution n'inclut pas le compilateur TypeScript, les fichiers source ni les dependances de developpement. Pour une application typique, cela reduit l'image de 800 Mo a moins de 200 Mo. La syntaxe COPY --from=builder est l'astuce cle - elle extrait les fichiers d'une etape precedente sans reporter les couches de cette etape.
Executer en tant qu'utilisateur non-root
Les conteneurs s'executent en tant que root par defaut. C'est un risque de securite : si un attaquant exploite votre application, il a un acces root dans le conteneur. La correction est une seule ligne dans votre Dockerfile qui bascule vers un utilisateur non-root. La plupart des images de base sont livrees avec un utilisateur node ou nobody a cet effet.
Au-dela de la securite, les constructions multi-etapes et un ordonnancement correct des couches ameliorent aussi la vitesse du pipeline CI/CD. Chaque minute gagnee sur une construction d'image est une minute que vos developpeurs ne passent pas a attendre un deploiement. Sur une equipe de dix developpeurs deployant cinq fois par jour, economiser deux minutes par build recupere plus de soixante heures de temps de developpement par an.
Fondamentaux de Kubernetes
Kubernetes est un orchestrateur de conteneurs. Il prend un cluster de machines (noeuds), planifie les conteneurs sur ceux-ci, les maintient en fonctionnement, gere la mise en reseau et fournit une API declarative pour decrire l'etat desire de votre systeme. Vous dites a Kubernetes ce que vous voulez - trois repliques de votre serveur API, port 8080 expose, strategie de mise a jour progressive - et il le realise.
La courbe d'apprentissage est reelle car Kubernetes introduit un ensemble d'abstractions en couches. Les trois avec lesquelles vous interagirez le plus sont les Pods, les Deployments et les Services.
Pods
Un Pod est la plus petite unite deployable dans Kubernetes. Il represente un ou plusieurs conteneurs qui partagent un espace de noms reseau et des volumes de stockage. En pratique, la plupart des Pods executent un seul conteneur. Les modeles sidecar (un conteneur principal plus un conteneur de journalisation ou proxy) utilisent des Pods multi-conteneurs, mais pour le deploiement d'application quotidien, vous utiliserez un conteneur par Pod.
Vous creez rarement des Pods directement. Les Pods sont ephemeres - ils peuvent etre termines et replanifies a tout moment. Si vous creez un Pod manuellement et que le noeud sur lequel il s'execute tombe en panne, le Pod est perdu pour toujours. C'est la que les Deployments entrent en jeu.
Deployments
Un Deployment gere un ensemble de Pods identiques (un ReplicaSet). Il gere les mises a jour progressives, la mise a l'echelle, l'auto-guerison et les retours en arriere. C'est la ressource que vous utiliserez pour deployer des applications sans etat.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
labels:
app: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: myregistry/api-server:v1.2.3
ports:
- containerPort: 3000
resources:
requests:
cpu: 250m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /healthz
port: 3000
readinessProbe:
httpGet:
path: /ready
port: 3000Ce Deployment declare trois repliques du serveur API. Kubernetes garantira que trois Pods sont toujours en cours d'execution. Si un Pod plante, Kubernetes cree un remplacement. Lors d'une mise a jour progressive (changement de l'image tag), Kubernetes remplace les Pods un par un, garantissant zero temps d'arret. La sonde de vivacite (liveness probe) indique a Kubernetes quand un Pod est en bonne sante ; la sonde de disponibilite (readiness probe) lui indique quand un Pod est pret a recevoir du trafic.
Services
Les Pods ont des adresses IP dynamiques. Chaque fois qu'un Pod est recree, il obtient une nouvelle IP. Un Service fournit un point de terminaison reseau stable qui equilibre la charge du trafic entre les Pods correspondant a son selecteur. C'est ainsi que les autres parties de votre systeme trouvent et communiquent avec votre application.
apiVersion: v1
kind: Service
metadata:
name: api-server
spec:
selector:
app: api-server
ports:
- port: 80
targetPort: 3000
type: ClusterIPCe Service mappe le port 80 sur une IP stable interne au cluster vers le port 3000 sur les Pods avec le label app: api-server. Les autres services a l'interieur du cluster peuvent l'atteindre par le nom DNS api-server. Pour le trafic externe, vous utiliseriez une ressource LoadBalancer ou Ingress par-dessus le Service.
Kubernetes n'est pas une plateforme sur laquelle vous deployez. C'est une plateforme a laquelle vous decrivez votre deploiement. La difference entre imperatif et declaratif est le changement mental le plus important que vous puissiez faire.
Developpement local : Docker Compose vs Kubernetes
La plus grande erreur que les equipes commettent est de supposer qu'elles ont besoin de Kubernetes pour le developpement local parce qu'elles l'utilisent en production. Docker Compose et Kubernetes servent des objectifs differents, et choisir le mauvais pour le travail local cree des frictions inutiles.
Docker Compose est concu pour le developpement local. Il fonctionne sur une seule machine, demarre les conteneurs en quelques secondes et a un format YAML simple qui correspond directement a ce dont vous avez besoin : un serveur web, une base de donnees, une instance Redis et peut-etre un worker de file d'attente. Vous definissez les services, et docker compose up met tout en ligne, avec les logs diffusés dans votre terminal, les ports mappes sur localhost et le rechargement a chaud fonctionnant immediatement.
version: "3.8"
services:
api:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Ce fichier Compose vous donne un environnement de developpement fonctionnel avec rechargement a chaud, une base de donnees locale avec stockage persistant et une mise en reseau appropriee entre les services. Il prend environ trente lignes de YAML et demarre en moins de dix secondes.
Minikube, Kind et k3s peuvent executer Kubernetes localement, mais ils ajoutent une surcharge significative. Ils necessitent plus de memoire, mettent plus de temps a demarrer et introduisent de la complexite (controleurs d'entree, maillage de services, classes de stockage) dont vous n'avez tout simplement pas besoin quand vous iterez sur une seule fonctionnalite. Executer Kubernetes localement est utile pour tester un comportement specifique a Kubernetes - comme les politiques d'eviction de pods, l'autoscaling horizontal de pods ou les definitions de ressources personnalisees - mais ce n'est pas un remplacement de Compose dans le developpement quotidien.
- Utilisez Docker Compose pour le developpement local. C'est rapide, simple et correspond directement aux conteneurs que vous executez.
- Utilisez Kubernetes (via Minikube ou Kind) pour les tests d'integration quand votre infrastructure de production utilise des fonctionnalites Kubernetes comme ConfigMaps, Secrets ou des controleurs personnalises.
- Utilisez un cluster de developpement distant uniquement quand vous avez besoin d'acces GPU, de materiel specialise ou d'un environnement de staging partage qui reflete exactement la production.
- N'executez pas deux clusters Kubernetes localement juste parce que vous avez deux environnements. Compose gere cela avec un seul flag --profile.
- Si votre equipe passe plus de temps a deboguer des configurations Kubernetes qu'a ecrire du code applicatif, vous avez depasse vos limites. Revenez a Compose et n'ajoutez de la complexite que quand la douleur de ne pas l'avoir depasse celle de la maintenir.
Pieges courants et comment les eviter
Meme apres avoir compris les concepts, certaines erreurs reviennent dans toutes les equipes. Voici celles qui valent la peine d'etre memorisees pour que vous puissiez eviter les sessions de debogage de deux jours.
Le chaos des tags d'image est le probleme de production le plus courant. Utiliser latest comme tag d'image dans un Deployment Kubernetes signifie que vous ne pouvez pas dire quelle version est executee sur un noeud donne. Kubernetes ne telecharge une image que si elle n'est pas presente sur le noeud, donc latest sur un noeud peut etre une version différente de latest sur un autre. Utilisez toujours des tags de version semantique ou des SHAs de commit. Mieux encore, utilisez un digest d'image completement qualifie - c'est la seule chose garantie d'etre immuable.
Les requetes et limites de ressources sont frequemment omises ou fixees arbitrairement. Si vous ne fixez pas de requests, Kubernetes ne peut pas planifier vos Pods intelligemment, et les noeuds deviennent surcharges. Si vous ne fixez pas de limits, une fuite memoire dans un Pod peut faire planter d'autres Pods sur le meme noeud. Fixez les deux, et utilisez des outils comme le Vertical Pod Autoscaler en mode recommandation pour les ajuster en fonction de l'utilisation reelle.
Les ConfigMaps et Secrets sont montes comme variables d'environnement ou fichiers. Les variables d'environnement sont pratiques, mais tout changement necessite un redemarrage du Pod pour prendre effet. Les montages bases sur les fichiers peuvent etre mis a jour sans redemarrage (le Pod lit le nouveau contenu quand le fichier est accede), mais de nombreuses applications mettent en cache la configuration au demarrage. Sachez quel modele votre application utilise et concevez votre approche de configuration en conséquence.
Les volumes persistants dans Kubernetes ne sont pas magiques. Un PersistentVolumeClaim demande du stockage, mais la classe de stockage sous-jacente doit etre configuree pour votre fournisseur cloud. Les classes de stockage par defaut peuvent utiliser du stockage attache au reseau qui a des caracteristiques de performance différentes de celles des SSD locaux. Si les performances de votre base de donnees sont importantes, testez votre classe de stockage avant de passer en production.
La journalisation et le debogage dans Kubernetes sont plus difficiles que sur un seul serveur. Les Pods sont ephemeres, donc les logs disparaissent quand un Pod est supprime. Utilisez kubectl logs --tail=50 -f pod-name pour le suivi en direct, mais pour le debogage en production, vous avez besoin d'une solution de journalisation centralisee (Loki, Elasticsearch ou un service de journalisation cloud). De meme, exec dans un Pod avec kubectl exec -it pod-name -- sh pour inspecter un conteneur en cours d'execution, mais rappelez-vous que toute modification que vous apportez dans un conteneur en cours d'execution est perdue au redemarrage.
Avez-vous vraiment besoin de Kubernetes ?
C'est la question que personne ne veut poser car Kubernetes fait bien sur un CV d'ingenieur et signale une maturite operationnelle. Mais Kubernetes est une solution a un probleme specifique : executer plusieurs services containerises sur plusieurs machines avec recuperation automatique, mise a l'echelle et deploiements progressifs. Si vous avez un ou deux services fonctionnant sur un seul serveur, Kubernetes est excessif.
Voici un cadre de decision simple. Si vous deployez une seule application qui sert moins de dix mille requetes par seconde, un seul serveur avec Docker Compose en production (oui, Compose fonctionne bien en production pour de nombreuses charges de travail) vous servira bien. Ajoutez un proxy inverse comme Caddy ou Nginx, configurez des sauvegardes automatisees, et vous avez un systeme de production qu'un seul developpeur peut comprendre et maintenir dans son integralite.
Passez a Kubernetes quand vous avez plusieurs services qui doivent etre deployes independamment, quand vous avez besoin d'une mise a l'echelle par service (votre API a besoin de dix repliques mais votre worker n'en a besoin que de deux), quand vous avez besoin de deploiements sans temps d'arret comme operation courante, ou quand votre equipe a au moins une personne dont la responsabilite principale est l'infrastructure. Avant que ces conditions ne soient remplies, le cout operationnel de Kubernetes - a la fois en gestion de cluster et en charge cognitive pour le developpeur - est un cout net negatif.
De nombreuses equipes beneficient d'un terrain d'entente. Utilisez Docker Compose pour le developpement local et une plateforme de conteneurs gerée comme AWS App Runner, Google Cloud Run ou Fly.io pour la production. Ces plateformes vous offrent le deploiement de conteneurs, le HTTPS automatique et la mise a l'echelle sans vous obliger a gerer un plan de controle Kubernetes. Vous obtenez la plupart des avantages de la containerisation sans aucune courbe d'apprentissage Kubernetes.
La meilleure strategie d'infrastructure est celle qui permet a votre equipe de livrer des fonctionnalites. Docker et Kubernetes sont des outils, pas des identites. Utilisez-les quand ils aident, et ignorez-les quand ils ne le font pas.
