Migration d’une stack Docker Compose : passer à un nouvel hôte sans mythes d’indisponibilité

Cet article vous a aidé ?

Vous avez une stack Docker Compose qui « fonctionne depuis des années », et maintenant il faut la transférer vers un nouvel hôte parce que l’ancien montre des signes de faiblesse, n’est plus sous garantie, manque d’espace ou de patience.
Le métier demande « zéro indisponibilité », et quelqu’un propose « Il suffit de rsync les volumes et de relancer là-bas. » C’est ainsi que naissent les incidents.

Compose peut tout à fait être migré proprement. Mais si vous voulez que les utilisateurs voient « zéro indisponibilité », vous devez définir ce que cela signifie (minutes ? secondes ? quelques erreurs ?) et choisir une technique de migration adaptée à vos données et à vos profils de trafic.
Vos conteneurs web sont faciles. Vos bases de données sont la partie qui vous fera voir un thérapeute.

Le mythe du « zéro indisponibilité » : ce que vous pouvez (ou non) promettre

« Zéro indisponibilité » n’est pas binaire. C’est un contrat. Il faut décider quel est ce contrat, puis construire une migration qui y répond.
Pour les migrations Docker Compose, le facteur limitant est presque toujours les données d’état : bases de données, files d’attente et tout volume inscriptible qui compte.
Si votre stack est purement sans état, vous pouvez faire un basculement blue/green propre avec un impact utilisateur quasi nul.
Si votre stack écrit sur le disque, vous devez soit :

  • Répliquer l’état (réplication de base de données, réplication d’objets, écriture double), puis basculer, ou
  • Geler les écritures (fenêtre de maintenance, mode lecture seule) et copier des données cohérentes, ou
  • Accepter une indisponibilité et la rendre courte et prévisible.

La réponse honnête d’un SRE est : si vous copiez des volumes en cours d’écriture, vous n’avez pas « zéro indisponibilité ». Vous avez « corruption éventuelle avec une touche de déni ».
La version acceptable de « zéro indisponibilité » est généralement « pas de fenêtre de maintenance planifiée », ce qui peut quand même tolérer quelques secondes de perturbation durant des changements DNS ou de load balancer.

Voici la définition pratique que j’aime : zéro indisponibilité pour les utilisateurs, mais budget d’erreur bref autorisé.
Par exemple : un pic de 30 secondes de 502 au moment du basculement est tolérable si vous prévenez les gens et que vos clients retentent.
Une incohérence silencieuse de données pendant deux heures parce que vous avez rsync un volume de base de données en live n’est pas acceptable.

Petite blague #1 : « On le fait sans aucune indisponibilité » veut souvent dire « nous n’avons pas encore regardé la base de données. »

Quelques faits et éléments d’histoire (pour arrêter de croire à la magie)

Vous n’avez pas besoin de devenir archéologue de conteneurs pour migrer une stack Compose. Mais un peu de contexte aide à prévoir les modes de panne.
Voici quelques faits concrets qui comptent dans les vraies migrations :

  1. Les volumes Docker ont été conçus pour la persistance locale, pas pour la portabilité. Les volumes nommés vivent sous la racine des données de Docker et ne sont pas intrinsèquement « déplaçables » sans copier le système de fichiers sous-jacent.
  2. Docker Compose a commencé comme un outil Python externe. Il est devenu plus tard un plugin CLI Docker (Compose V2), ce qui a changé certains comportements et formats de sortie au point de perturber des runbooks.
  3. Le réseau overlay est pour Swarm/Kubernetes, pas pour Compose. Le réseau de Compose est du bridge mono-hôte sauf si vous fournissez votre propre tissu réseau.
  4. Les adresses IP à l’intérieur des réseaux Docker ne sont pas des contrats stables. Si une application dépend des IP des conteneurs, c’est déjà un incident en attente d’une invitation de calendrier.
  5. Rsync d’un répertoire de base de données en live n’est pas une sauvegarde. La plupart des bases de données nécessitent soit des snapshots, un gel du système de fichiers, soit des outils de sauvegarde natifs pour obtenir une copie cohérente.
  6. Les healthchecks existent parce que « container démarré » n’est pas la même chose que « service prêt ». Utilisez-les lors des basculements ou préparez-vous à des 502 mystérieux.
  7. Les TTL DNS sont indicatifs, pas absolus. Certains résolveurs gardent en cache plus longtemps que demandé. Concevez les basculements en supposant des rémanences.
  8. Compose n’est pas un orchestrateur. Il ne relancera pas votre conteneur échoué sur un autre hôte. Si vous avez besoin de cela, vous êtes en territoire Swarm/Kubernetes (ou vous écrivez votre propre automatisation, ce qui est… un hobby).
  9. La propriété des fichiers et le mapping UID/GID sont des tueurs silencieux. Les changements d’image et les différences d’hôte cassent les permissions sur les bind mounts et les volumes d’une manière qui ressemble à un « bug applicatif ».

Une citation, parce qu’elle tient toujours : L’espoir n’est pas une stratégie.

Modèles de migration qui fonctionnent réellement

Modèle A : blue/green sans état (le cas rêvé)

Si votre stack n’a pas d’écritures persistantes sur l’hôte (ou si toutes les écritures vont vers des services managés), procédez ainsi :
déployez le même projet Compose sur le nouvel hôte, vérifiez les healthchecks, puis basculez le trafic via DNS, reverse proxy ou load balancer.
Laissez l’ancien hôte en fonctionnement comme plan de secours jusqu’à ce que vous ayez confiance en le nouveau.

L’impact visible pour l’utilisateur peut être proche de zéro si :
les sessions ne sont pas liées à un hôte, ou si vous utilisez un stockage de session partagé (Redis, sessions en base de données, JWT),
et que votre méthode de basculement n’abandonne pas la moitié du trafic sur des points de terminaison obsolètes.

Modèle B : état avec réplication (l’approche mature)

Pour PostgreSQL/MySQL/Redis, la réplication est l’approche la plus propre pour viser « zéro indisponibilité ».
Vous exécutez la nouvelle base de données en tant que réplica, la laissez rattraper son retard, puis la promouvez.
Cela fonctionne bien si vous pouvez tolérer un bref gel des écritures au moment de la promotion ou si votre application supporte un basculement rapide.

Les migrations basées sur la réplication déplacent le risque de « correction de copie de données » vers « exactitude de la réplication et chorégraphie du basculement ».
C’est un bon compromis : les outils de réplication sont conçus pour cela ; votre script rsync, lui, est conçu pour rassurer.

Modèle C : snapshot + court gel (le milieu réaliste)

Si la réplication est trop lourde (applications legacy, manque de temps, pas d’expertise), visez un snapshot cohérent :
stoppez les écritures, prenez un snapshot (LVM/ZFS/btrfs ou sauvegarde native de la base), transférez-le, redémarrez sur le nouvel hôte, basculez.
L’indisponibilité est l’intervalle « stop des écritures » plus le temps de basculement.

Modèle D : « Juste copier /var/lib/docker » (le piège)

Cela peut fonctionner dans des conditions très limitées : même version Docker, même driver de stockage, mêmes sémantiques de système de fichiers, pas d’écritures en cours, et vous êtes prêt à déboguer les internals Docker à 3 h du matin.
Si vous migrez parce que l’ancien hôte est fragile, persister avec des techniques fragiles est un choix esthétique.

Pré-vol : quoi inventorier avant de toucher à quoi que ce soit

Les échecs de migration ne viennent généralement pas de l’évidence. Ils proviennent des couplages invisibles entre votre fichier Compose et votre hôte :
chemins de système de fichiers, réglages du noyau, règles de pare-feu, et cron « temporaire » devenu production.

Avant de construire le nouvel hôte, inventoriez :

  • Version de Compose et version du moteur Docker sur l’ancien hôte (matcher ou décider d’une mise à jour intentionnelle).
  • Volumes : volumes nommés vs bind mounts ; quels services écrivent ; volume des données.
  • Secrets/config : fichiers .env, répertoires de config montés, certificats TLS, clés API.
  • Ingress : reverse proxy, ports publiés, pare-feu, règles NAT éventuelles.
  • Dépendances externes : enregistrements DNS, listes d’autorisation, webhooks en amont, relais SMTP.
  • Observabilité : emplacement des logs, endpoints métriques, intégrations d’alerte.
  • Stratégie de sauvegarde/restore que vous pouvez tester sans jouer avec la patience de l’entreprise.

Tâches pratiques avec commandes (et comment les interpréter)

Cette section est volontairement pratique : commandes à exécuter aujourd’hui, sorties attendues et décision à prendre à partir de chacune.
Utilisez-les d’abord sur l’ancien hôte, puis répétez-les sur le nouvel hôte comme validation.

Task 1: Identify Docker and Compose versions (compat risk)

cr0x@server:~$ docker version --format 'Engine={{.Server.Version}} StorageDriver={{.Server.Driver}}'
Engine=26.1.4 StorageDriver=overlay2

Sens : Vous avez Docker Engine 26.x utilisant overlay2. C’est courant et acceptable.
Décision : Sur le nouvel hôte, alignez la version majeure (ou testez la mise à jour). Assurez-vous aussi que overlay2 est supporté (noyau + système de fichiers).

cr0x@server:~$ docker compose version
Docker Compose version v2.27.1

Sens : Plugin Compose V2, pas l’ancien docker-compose V1.
Décision : Utilisez la même majeure/minor si vous souhaitez moins de surprises sur les noms de réseau, les noms de projet et le format CLI.

Task 2: See what’s actually running (and under what project name)

cr0x@server:~$ docker compose ls
NAME            STATUS              CONFIG FILES
payments        running(7)          /srv/payments/compose.yaml

Sens : Votre projet Compose s’appelle payments, pas ce que vous avez supposé en regardant le dossier.
Décision : Gardez le nom de projet stable entre les hôtes (utilisez --project-name ou name: dans Compose) pour éviter des surprises dans les noms de réseau/volume.

Task 3: Enumerate services, images, and ports (ingress impact)

cr0x@server:~$ docker compose -p payments ps
NAME                 IMAGE                        COMMAND                  SERVICE   STATUS    PORTS
payments-web-1       nginx:1.27-alpine            "/docker-entrypoint.…"   web       running   0.0.0.0:443->443/tcp
payments-api-1       ghcr.io/acme/api:3.14.2      "/app/start"             api       running   0.0.0.0:8080->8080/tcp
payments-db-1        postgres:15                  "docker-entrypoint.s…"   db        running   5432/tcp

Sens : Seuls web et api publient des ports sur l’hôte. La base de données est interne uniquement (bien).
Décision : Votre surface de basculement est 443 et 8080, pas 5432. Cela change la planification du pare-feu et du load balancer.

Task 4: List volumes and identify what’s stateful

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     payments_dbdata
local     payments_redisdata
local     payments_prometheus

Sens : Des volumes nommés existent. Attendez-les sous la racine de données Docker sauf si vous avez utilisé driver_opts.
Décision : Traitez dbdata et redisdata comme stateful et exigeant des garanties de cohérence.

Task 5: Determine where Docker stores data (so you don’t copy the wrong disk)

cr0x@server:~$ docker info --format 'DockerRootDir={{.DockerRootDir}}'
DockerRootDir=/var/lib/docker

Sens : Racine Docker par défaut.
Décision : Si vous planifiez des snapshots de système de fichiers, ce chemin doit être sur un système de fichiers capable de snapshots, et vous devez connaître son périphérique/pool sous-jacent.

Task 6: Inspect mounts used by a container (named volume vs bind mount truth)

cr0x@server:~$ docker inspect payments-db-1 --format '{{json .Mounts}}'
[{"Type":"volume","Name":"payments_dbdata","Source":"/var/lib/docker/volumes/payments_dbdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

Sens : La base de données écrit dans un volume nommé sous /var/lib/docker/volumes.
Décision : Si vous le copiez, vous avez besoin d’une méthode cohérente (snapshot ou sauvegarde native de la base), pas d’une copie de fichiers au hasard pendant que c’est en marche.

Task 7: Check volume size and growth (transfer time and risk)

cr0x@server:~$ sudo du -sh /var/lib/docker/volumes/payments_dbdata/_data
58G	/var/lib/docker/volumes/payments_dbdata/_data

Sens : Vous déplacez ~58 Go de données de base, plus WAL, plus overhead.
Décision : Planifiez le temps de transfert selon le débit réel. Si vous ne pouvez pas répliquer et ne pouvez pas vous permettre une longue fenêtre de copie, choisissez un autre modèle.

Task 8: Find hidden bind mounts referenced in Compose

cr0x@server:~$ grep -nE '^\s*-\s*/' /srv/payments/compose.yaml
42:      - /srv/payments/nginx/conf.d:/etc/nginx/conf.d:ro
43:      - /srv/payments/tls:/etc/nginx/tls:ro
77:      - /srv/payments/uploads:/var/lib/app/uploads

Sens : Vous utilisez des bind mounts pour la config, TLS et les uploads. Les uploads sont stateful et hors des volumes Docker.
Décision : Ajoutez ces répertoires à votre plan de migration, y compris la propriété et les permissions.

Task 9: Validate image availability (prevent cutover-day “pull denied”)

cr0x@server:~$ docker compose -p payments config --images
ghcr.io/acme/api:3.14.2
nginx:1.27-alpine
postgres:15
redis:7-alpine

Sens : Ces tags d’image exacts sont ceux que vous exécuterez.
Décision : Sur le nouvel hôte, pré-pull les images. Si vous utilisez un registre privé, confirmez que l’auth fonctionne avant le basculement.

Task 10: Check container health and readiness gates (cutover correctness)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}' | head
NAMES             STATUS
payments-web-1    Up 5 days
payments-api-1    Up 5 days (healthy)
payments-db-1     Up 5 days

Sens : L’API indique healthy. Web et DB peuvent ne pas avoir de healthchecks configurés (très courant).
Décision : Ajoutez des healthchecks pour tout service dont dépend le basculement (surtout reverse proxy et API). Les healthchecks ne sont pas des décorations.

Task 11: Confirm open ports and what’s bound (avoid stealth conflicts)

cr0x@server:~$ sudo ss -lntp | grep -E ':(443|8080)\s'
LISTEN 0      4096        0.0.0.0:443       0.0.0.0:*    users:(("docker-proxy",pid=15422,fd=4))
LISTEN 0      4096        0.0.0.0:8080      0.0.0.0:*    users:(("docker-proxy",pid=15457,fd=4))

Sens : Les ports sont publiés via docker-proxy. Sur certains systèmes vous verrez plutôt du DNAT iptables, mais le résultat final est identique : ces ports doivent être libres sur le nouvel hôte.
Décision : Réservez ces ports. Si quelque chose d’autre (comme un ancien Nginx sur l’hôte) les occupe, vous échouerez au démarrage.

Task 12: Measure disk and inode headroom (migrations fail on boring math)

cr0x@server:~$ df -h /var/lib/docker
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p3  500G  410G   65G  87% /

Sens : Vous manquez déjà d’espace.
Décision : Le nouvel hôte doit offrir une marge confortable. Migrer vers « la même taille mais neuve » ne résout que temporairement le problème.

Task 13: Check filesystem type and mount options (snapshot and performance implications)

cr0x@server:~$ findmnt -no FSTYPE,OPTIONS /var/lib/docker
ext4	rw,relatime,errors=remount-ro

Sens : ext4, pas de snapshots natifs.
Décision : Si vous voulez des snapshots cohérents sans arrêter les services, vous aurez besoin d’un LVM en dessous, ou de déplacer la racine Docker sur ZFS/btrfs sur le nouvel hôte, ou d’utiliser des sauvegardes natives de base de données.

Task 14: Validate DNS and TTL behavior you actually get

cr0x@server:~$ dig +noall +answer app.example.internal
app.example.internal.  300  IN  A  10.20.30.40

Sens : TTL à 300 secondes (5 minutes).
Décision : Prévoyez plusieurs minutes de trafic mixte sauf si vous utilisez un load balancer/VIP pour le basculement. Réduisez le TTL à l’avance si vous comptez sur le DNS.

Task 15: Test application-level write freeze readiness (if you need it)

cr0x@server:~$ curl -sS -o /dev/null -w '%{http_code}\n' https://app.example.internal/health
200

Sens : Le endpoint health est joignable.
Décision : Si vous avez besoin d’un mode maintenance, implémentez-le et testez-le maintenant. Une migration est un très mauvais moment pour découvrir que votre appli ne peut pas passer en lecture seule proprement.

Stockage et état : la partie qu’on ne peut pas écarter d’un revers de main

Les migrations Compose sont souvent présentées comme « déplacer des conteneurs ». C’est mignon. Vous déplacez des données.
Les conteneurs sont du bétail ; vos volumes sont des animaux de compagnie avec personnalité juridique.

Volumes nommés vs bind mounts : implications pour la migration

Les volumes nommés sont sous la gouvernance du plan de contrôle Docker. C’est pratique mais rend les migrations opaques :
il faut localiser le chemin des données du volume, le copier en sécurité et préserver la propriété.
Les bind mounts sont explicites : vous voyez le chemin dans Compose, vous pouvez le sauvegarder avec des outils standards et appliquer des pratiques de système de fichiers.
L’inconvénient : les bind mounts sont couplés à l’hôte. La structure de vos répertoires devient un contrat API.

Règles de cohérence (version directe)

  • Si c’est une base de données : utilisez la réplication ou les outils de sauvegarde natifs ; considérez les copies de fichiers comme suspectes à moins d’avoir des snapshots ou d’arrêter proprement la BD.
  • Si c’est un répertoire d’objets (uploads) : rsync est acceptable, mais il faut gérer les écritures concurrentes (synchronisation en deux phases : copie initiale, puis sync final après gel des écritures).
  • Si c’est Redis : décidez si c’est un cache (reconstruire) ou un datastore (répliquer ou snapshotter).
  • Si c’est Prometheus : vous pouvez copier, mais attendez-vous à du churn WAL. Le snapshot fonctionne ; le rsync en live est instable à moins d’arrêter Prometheus.

Deux patterns fiables pour déplacer des données stateful

Pattern 1 : sauvegarde/restauration native à la base (prévisible, souvent la plus rapide)

Pour PostgreSQL : utilisez pg_basebackup (réplication) ou pg_dump/pg_restore (sauvegarde logique).
Pour MySQL : utilisez la réplication ou un dump cohérent avec les bons flags.
L’avantage clé : vous déplacez des données dans un format que la base comprend, et non des fichiers que la base modifie activement.

Pattern 2 : snapshot + send (rapide pour gros jeux de données)

Si votre racine Docker ou vos répertoires de volume se trouvent sur ZFS ou LVM, les snapshots permettent de prendre une copie point-in-time au niveau système de fichiers.
Ensuite vous transférez le snapshot vers le nouvel hôte et le montez comme source du volume.
C’est extrêmement rapide pour de grands ensembles de données et réduit l’indisponibilité. Cela exige aussi que vous ayez planifié votre agencement de stockage sérieusement.

Petite blague #2 : Le stockage est le seul endroit où « ça marche sur ma machine » signifie « ta machine est maintenant mon problème. »

Réseau et basculement : DNS, VIP et reverse proxies

La plupart des stacks Compose ont une des formes d’Ingress suivantes :

  • Ports publiés directement (par ex. 443 sur l’hôte) et les clients se connectent à l’IP/DNS de l’hôte.
  • Un conteneur reverse proxy (nginx/Traefik/Caddy) publiant 80/443 et routant vers des services internes.
  • Un load balancer externe en frontal, qui forward vers l(es) hôte(s).

Pour les migrations, le levier de basculement le plus efficace est celui qui offre le rollback le plus rapide :
un changement de pool de load balancer, un déplacement de VIP, ou une mise à jour DNS réversible rapidement.
Publier des ports directement sur un hôte unique avec un DNS codé en dur est simple jusqu’au moment du déplacement.

Basculement DNS : ok, mais prévoyez les rémanences

Le basculement DNS est courant parce qu’il est accessible. Il est aussi chaotique :
les caches ignorent les TTL, les clients réutilisent des connexions TCP, et certains logiciels « fixent » une IP jusqu’au redémarrage.
Si vous utilisez DNS, réduisez les TTL au moins un jour à l’avance (pas cinq minutes avant).
Ensuite gardez l’ancien service en fonctionnement jusqu’à ce que vous soyez sûr d’avoir drainé.

Basculement VIP/keepalived : propre si vous pouvez l’exécuter

Un IP virtuel déplacé entre hôtes peut être quasi instantané et facile à rollback.
Mais cela nécessite le support réseau et la volonté d’exécuter VRRP/keepalived correctement.
Dans des réseaux d’entreprise avec contrôle strict des changements, cela peut être plus difficile qu’il ne devrait.

Basculement via reverse proxy : placez l’interrupteur à l’endroit approprié

Si vous avez déjà un reverse proxy, envisagez de le rendre externe aux hôtes applicatifs.
Une petite couche de proxy dédiée peut router vers l’ancien ou le nouvel backend via des changements de config.
C’est essentiellement un blue/green sans demander à Compose d’être un scheduler.

Playbook de diagnostic rapide

Quand une migration « fonctionne » mais que les performances dégringolent ou que les erreurs montent, il vous faut un chemin court vers le goulot d’étranglement.
Ne débattez pas d’architecture dans le canal d’incident. Vérifiez les bases dans l’ordre.

Premier : le trafic atteint-il le bon endroit ?

  • Vérifiez la résolution DNS depuis plusieurs réseaux (réseau corp, VPN et depuis le nouvel hôte lui-même).
  • Confirmez que le nouvel hôte reçoit réellement des connexions sur les ports attendus (ss, compteurs pare-feu).
  • Vérifiez les targets en amont du reverse proxy et leur état de santé.

Deuxième : l’appli est-elle saine ou juste « up » ?

  • Vérifiez l’état de santé des conteneurs, pas seulement leur uptime.
  • Suivez les logs à l’edge (proxy) et au cœur (API) en même temps ; corrélez les horodatages.
  • Cherchez l’épuisement des pools de connexions, des timeouts et des « permission denied » sur les mounts.

Troisième : le stockage est-il le goulot ?

  • Vérifiez la latence et la saturation disque (iostat, nvme smart stats, dmesg pour resets).
  • Confirmez que la base est sur le disque/pool attendu et pas sur un volume de démarrage lent.
  • Validez les options système de fichiers et l’espace libre ; les disques presque pleins se comportent mal.

Quatrième : le chemin réseau est-il différent de ce que vous pensez ?

  • Vérifiez les mismatches MTU (surtout sur VPNs et VLANs).
  • Confirmez les règles de pare-feu et les limites de conntrack sur le nouvel hôte.
  • Regardez les retransmissions SYN et les resets TCP (stats ss, logs du proxy).

Erreurs communes : symptôme → cause racine → correction

1) L’API renvoie 502/504 juste après le basculement

Symptôme : Le reverse proxy est actif, mais les appels en amont timeout.

Cause racine : Les healthchecks manquaient ou étaient insuffisants ; le proxy a commencé à router avant que l’API soit chaude ou que les migrations soient terminées.

Correction : Ajoutez de vrais healthchecks (connectivité DB, pas juste processus vivant). Bloquez le routing du proxy sur la base de la santé. Envisagez l’ordre de démarrage avec depends_on plus conditions de santé.

2) La base démarre puis plante avec erreurs de corruption

Symptôme : Postgres se plaint de checkpoints invalides ou de segments WAL ; MySQL signale des erreurs InnoDB.

Cause racine : Copie au niveau système de fichiers d’un répertoire de base en live (rsync pendant l’exécution) a produit un jeu de données incohérent.

Correction : Restaurer depuis une sauvegarde cohérente ou un snapshot. Re-migrer en utilisant la réplication ou l’outil natif de la base. Arrêter la BD si vous devez faire des copies de fichiers.

3) Tout est « up », mais les écritures échouent avec permission denied

Symptôme : Les logs montrent des EACCES sur des chemins montés ; les uploads échouent ; la base ne peut pas écrire.

Cause racine : Mismatch UID/GID entre ancien et nouvel hôte, ou propriété du répertoire de bind mount modifiée.

Correction : Alignez la propriété sur l’utilisateur du conteneur, pas sur votre compte admin. Confirmez avec stat et les UID du runtime. Utilisez user: explicite dans Compose si approprié.

4) Les performances sont à la moitié de ce qu’elles étaient

Symptôme : Pics de latence après migration ; CPU ok ; base lente.

Cause racine : Le stockage est passé d’un SSD à un disque plus lent (ou un RAID mal configuré), options de FS différentes, ou différences d’ordonnanceur I/O.

Correction : Benchmarkez le disque sur le nouvel hôte. Placez le volume DB sur le bon device. Corrigez les options de montage. Vérifiez que vous n’avez pas une encryption/compression accidentelle qui ajoute de la latence.

5) Les clients atteignent parfois l’ancien hôte pendant des heures

Symptôme : Logs mixtes, comportements mixtes ; certains utilisateurs voient l’ancienne version.

Cause racine : Cache DNS au-delà du TTL, résolveurs codés en dur, connexions long-lived, ou IPs épinglées dans les clients.

Correction : Utilisez un load balancer/VIP si possible. Si vous dépendez du DNS, réduisez le TTL bien à l’avance et gardez l’ancien hôte en service jusqu’au drain complet.

6) Compose up échoue : « port is already allocated »

Symptôme : Le nouvel hôte refuse de démarrer le container web/proxy.

Cause racine : Un autre processus utilise le port (souvent un nginx host-level, apache, ou un conteneur résiduel).

Correction : Identifiez l’écoute avec ss -lntp. Arrêtez/désactivez le service en conflit, ou ajustez les ports publiés et le routage en amont.

7) Perte de données inattendue dans les uploads ou fichiers

Symptôme : Le nouvel hôte manque des uploads récents.

Cause racine : Copie en un seul passage ; des écritures ont continué pendant le transfert ; pas de sync final.

Correction : Rsync en deux phases : synchronisation initiale en live, puis gel des écritures et rsync final, puis basculement.

Checklists / plans pas à pas

Plan 1 : indisponibilité quasi nulle pour stacks principalement sans état (blue/green + DNS/LB)

  1. Aligner l’environnement : Installez Docker Engine et le plugin Compose sur le nouvel hôte. Gardez les versions proches pour réduire les surprises.
  2. Provisionner le stockage : Créez les répertoires pour les bind mounts ; planifiez l’emplacement des volumes. Assurez-vous d’avoir assez de disque et d’IOPS.
  3. Pré-puller les images : Pull toutes les images requises sur le nouvel hôte pour éviter des pannes de registre le jour du basculement.
  4. Déployer en parallèle : Montez la stack sur le nouvel hôte sur des ports alternatifs ou derrière un groupe de cibles LB séparé.
  5. Valider : Endpoints de santé, connectivité DB, jobs background et tâches planifiées.
  6. Shadow traffic (optionnel) : Miroiter du trafic en lecture seule ou exécuter des checks synthétiques vers la nouvelle stack.
  7. Basculement : Changez les targets du LB ou l’enregistrement DNS vers le nouvel hôte.
  8. Surveiller : Surveillez taux d’erreur, latence et logs. Gardez l’ancien hôte prêt pour rollback.
  9. Plan de rollback : Si vous brûlez le budget d’erreur, revenez vite en arrière. N’essayez pas de « déboguer en production » sauf ultime recours.
  10. Désaffectation ultérieure : Après une période stable, arrêtez l’ancien hôte et archivez configs/sauvegardes.

Plan 2 : courte indisponibilité avec snapshots cohérents (gel + snapshot + restore)

  1. Préparer le nouvel hôte : Mêmes images, même config, mêmes secrets, même structure de répertoires pour les bind mounts.
  2. Abaisser le TTL : Si vous utilisez DNS pour basculer, réduisez le TTL au moins 24 heures à l’avance.
  3. Sync initial : Pour les uploads et autres arbres de fichiers, faites un rsync pendant que l’appli est en service.
  4. Geler les écritures : Mettez l’appli en mode maintenance ou arrêtez les services générant des écritures. Confirmez qu’il n’y a plus d’écritures.
  5. Prendre snapshot/sauvegarde : Utilisez la sauvegarde native de la BD ou un snapshot système si disponible.
  6. Sync final : Rsync à nouveau pour capturer les dernières modifications.
  7. Restaurer sur le nouvel hôte : Importez la base, placez les fichiers, vérifiez la propriété.
  8. Démarrer la stack : Montez Compose et attendez que les healthchecks passent.
  9. Basculement du trafic : Changement DNS ou LB. Laissez l’ancien hôte arrêté ou en lecture seule pour éviter le split-brain.
  10. Dégeler : Sortez du mode maintenance et surveillez.

Plan 3 : état « no downtime-ish » avec réplication (migration centrée sur la base)

  1. Monter la nouvelle BD : Configurez-la en réplica de l’ancienne. Vérifiez que le lag de réplication reste faible sous charge normale.
  2. Déployer l’appli sur le nouvel hôte : Pointez-la sur le réplica pour vérification en lecture seule si possible, ou laissez-la inactive.
  3. Planifier la promotion : Décidez de la minute exacte du basculement et de la stratégie d’écritures (gel bref ou basculement applicatif).
  4. Basculement : Geler brièvement les écritures, laisser le réplica rattraper, promouvoir le nouveau DB en primaire.
  5. Basculer le trafic appli : Changez LB/DNS vers le nouvel hôte et le nouvel endpoint DB.
  6. Conserver l’ancienne BD : Laissez-la en réplica (si supporté) pour la fenêtre de rollback, mais n’autorisez pas les écritures.

Trois mini-récits d’entreprise issus du terrain

Mini-récit 1 : l’incident causé par une mauvaise hypothèse (rsync en tant que « sauvegarde »)

Une entreprise SaaS de taille moyenne a décidé de déplacer une stack Compose d’une VM « temporaire » vers un nouvel hôte avec plus de CPU.
La stack était typique : Nginx, API, PostgreSQL, Redis et un worker. L’équipe avait déjà fait des basculements sans état, donc le plan de migration semblait familier :
monter le nouvel hôte, rsync /var/lib/docker/volumes, démarrer Compose, basculer le DNS.

Ils ont lancé rsync pendant que tout servait encore du trafic. Ça a pris du temps. Ils l’ont relancé « pour rattraper », se sont félicités de la petite taille du delta, et ont basculé.
La nouvelle stack est montée et semblait OK. Les utilisateurs se sont connectés. Les requêtes passaient. L’incident n’a pas commencé par des alertes ; il a commencé par des tickets support.

Le premier symptôme était subtil : un petit pourcentage d’utilisateurs voyait des données anciennes après des mises à jour. Puis quelques jobs background ont échoué avec des violations de contrainte unique.
Finalement Postgres a commencé à émettre des plaintes liées aux WAL, pas immédiatement fatales mais de plus en plus problématiques. L’équipe a tenté de redémarrer le conteneur de la base. C’est là que tout s’est effondré.

La mauvaise hypothèse était que rsync du répertoire de données Postgres est « suffisamment proche » si on le fait deux fois.
Ce n’est pas le cas. Postgres attend un checkpoint cohérent et une séquence WAL ; copier des fichiers en cours d’écriture peut créer un jeu de données qui démarre mais contient des mines.
Ils ont dû restaurer depuis une sauvegarde logique plus ancienne, puis réparer manuellement l’écart avec des logs applicatifs et une série de SQL prudents.

La leçon retenue : si votre plan de migration n’inclut pas un mécanisme explicite de cohérence pour la base, ce n’est pas un plan.
C’est de l’optimisme avec une ligne de commande.

Mini-récit 2 : l’optimisation qui s’est retournée contre eux (stockage plus rapide… sur le papier)

Une grande équipe d’entreprise a migré une plateforme interne hébergée via Compose vers du nouveau matériel. L’« optimisation » a été de consolider le stockage :
mettre la racine Docker, les volumes de BD et les uploads applicatifs sur un seul grand RAID.
Le raisonnement sonnait bien en réunion : moins de points de montage, sauvegardes plus simples, et « RAID c’est rapide ».

Après le basculement, tout fonctionnait, mais la latence a doublé aux heures de pointe. L’API n’était pas CPU-bound. Le réseau était OK.
La base avait des pauses périodiques. Les workers mettaient plus de temps pour finir les jobs. Les ingénieurs ont commencé à blâmer le noyau du nouvel hôte, la version de Docker, et même « l’overhead conteneur ».

Il s’est avéré que le contrôleur RAID était configuré pour privilégier la capacité avec une politique de cache en écriture sûre mais lente pour leur charge.
Sur l’ancien hôte, la base vivait sur des SSD à faible latence. Sur le nouvel hôte, elle partageait des disques avec de grosses écritures séquentielles issues des uploads et des jobs de sauvegarde.
L’agencement consolidé a créé une contention I/O qui n’apparaissait pas dans les benchmarks synthétiques mais qui s’est manifestée immédiatement en production.

La correction a été ennuyeuse : séparer la base sur du stockage bas-latence et garder le stockage volumineux ailleurs, avec des limites de bande passante explicites pour les copies en arrière-plan.
Ils ont aussi ajouté de la surveillance I/O simple qui aurait rendu la cause racine évidente dès le premier jour.
L’« optimisation » n’était pas malveillante ; elle ne respectait tout simplement pas le fait que les bases de données ne négocient pas avec des disques lents.

Mini-récit 3 : la pratique ennuyeuse mais correcte qui a sauvé la journée (répétition + rollback)

Une autre équipe, plus petite et moins glamour, devait migrer une stack Compose gérant des approbations de paie.
Le système n’avait pas beaucoup de trafic, mais il était critique. Ils ne pouvaient pas se permettre une perte de données, et ils ne pouvaient pas verrouiller tout le monde longtemps.
Personne n’était enthousiaste. En général c’est un bon signe.

Ils ont fait une répétition. Pas théorique — une répétition réelle sur un clone de staging avec un snapshot de données réaliste.
Ils ont documenté chaque commande exécutée, y compris celles qui semblaient évidentes. Ils ont vérifié les procédures de restauration, pas seulement les sauvegardes.
Puis ils ont fait quelque chose encore moins excitant : ils ont écrit un plan de rollback incluant la réversion DNS, l’ordre d’arrêt/démarrage des conteneurs, et un point clair de « stop the world ».

Le jour de la migration, le nouvel hôte est monté, mais un service a échoué à cause d’un bundle CA manquant qui était silencieusement présent sur l’ancien hôte.
Parce qu’ils avaient répété, la panne leur a paru familière. Ils ont corrigé le paquet, relancé la séquence de démarrage, et ont poursuivi.
L’indisponibilité fut courte, prévisible et explicable. Les utilisateurs se sont un peu plaints, puis ont oublié.

La journée a été « réussie » parce qu’ils ont traité ce changement comme ayant des bords tranchants : répétition, validation, rollback.
Rien d’héroïque. Rien de brillant. Juste de la compétence.

FAQ

1) Puis-je migrer une stack Compose avec littéralement zéro indisponibilité ?

Si la stack est sans état, presque oui. Si elle a de l’état, « littéralement zéro » exige généralement de la réplication ou une couche de données externalisée.
La plupart des équipes peuvent atteindre « pas de fenêtre de maintenance » avec un petit pic, mais pas zéro au sens mathématique.

2) Est-il sûr de copier les volumes Docker nommés avec rsync ?

Pour des données statiques ou que vous pouvez mettre en quiescence, oui. Pour des bases en live, non. Si vous devez copier, arrêtez le service ou utilisez des snapshots, ou utilisez la sauvegarde/réplication native de la base.

3) Dois-je copier /var/lib/docker vers le nouvel hôte ?

Évitez-le sauf si vous avez une bonne raison et un environnement de test. Cela vous couple aux détails du driver de stockage, aux internals Docker et à la compatibilité des versions.
Préférez migrer les données applicatives et redéployer les conteneurs proprement.

4) Quel est le mécanisme de basculement le plus sûr : DNS, load balancer ou VIP ?

Les changements de target de load balancer et les mouvements de VIP sont généralement les plus rapides à rollback et les moins dépendants du comportement client.
Le DNS fonctionne, mais vous devez planifier les caches et les connexions long-lived.

5) Comment gérer les certificats TLS pendant la migration ?

Traitez-les comme de l’état de première classe. Inventoryez où ils vivent (bind mounts, fichiers de secrets).
Copiez-les de manière sécurisée, vérifiez les permissions et validez la chaîne complète sur le nouvel hôte avant le basculement.

6) Dois-je conserver les mêmes IPs de conteneurs ou noms de réseau ?

Vous ne devriez dépendre d’aucune IP de conteneur. Utilisez les noms de service sur le réseau Compose.
Les noms de réseau importent seulement si des systèmes externes les référencent (rare). La plupart du temps, un nom de projet stable suffit.

7) Comment migrer les uploads ou autres fichiers mutables ?

Faites une synchronisation en deux phases : copier en live, puis geler brièvement les écritures et faire un rsync final.
Si possible, migrez les uploads vers un stockage d’objets et oubliez les fichiers hôtes pour de bon.

8) Que faire des workers background et tâches planifiées pendant le basculement ?

Mettez-les en pause ou assurez l’idempotence. Pendant le basculement, des workers dupliqués peuvent double-traiter des jobs, et c’est ainsi que les systèmes financiers inventent de nouvelles formes d’excitation.
Si vous ne pouvez pas les pauser, concevez des mécanismes de déduplication et des locks de job.

9) Dois-je mettre à jour Docker/Compose pendant la migration ?

Évitez de combiner une migration d’hôte avec une mise à jour du runtime sauf si vous avez le temps de tester.
Si vous devez upgrader, faites-le intentionnellement avec une répétition et une validation, pas comme effet secondaire d’un « new server build ».

Conclusion : prochaines étapes pratiques

Si vous retenez une chose : les migrations Compose échouent quand les équipes traitent l’état comme un détail d’implémentation.
Définissez ce que « zéro indisponibilité » signifie en termes métiers, puis choisissez un modèle de migration adapté à la réalité de vos données.
Répliquez quand vous le pouvez. Snapshottez quand vous devez. Gélez les écritures quand vous n’avez pas d’autre option. Ne faites pas de rsync de bases en live en l’appelant ingénierie.

Prochaines étapes qui rapportent tout de suite :

  • Exécutez les tâches d’inventaire ci-dessus sur l’ancien hôte et notez ce qui est stateful.
  • Choisissez un mécanisme de basculement avec un levier de rollback de confiance (LB/VIP vaut mieux que DNS-seul).
  • Répétez la migration sur un clone de staging, y compris restore et rollback.
  • Ajoutez des healthchecks et des gates de readiness pour que « up » signifie « prêt ».
  • Rendez explicite le placement du stockage sur le nouvel hôte ; les bases n’appartiennent pas à des disques mystères.

Les migrations ne sont pas glamour. Ce sont le sérum de vérité opérationnel. Faites-les comme si vous vouliez dormir après.

← Précédent
Bases SR-IOV sur Debian 13 : pourquoi ça échoue et comment déboguer la première fois
Suivant →
Remplacer vCenter par Proxmox : gains, pertes et contournements qui fonctionnent vraiment

Laisser un commentaire