La douleur : vous avez une seule machine Linux, une seule IP publique et une application qui ne peut pas tomber. « Il suffit de redéployer » se transforme en une panne partielle de cinq minutes, un rollback qui n’en est pas un, et un fil Slack exécutif qui ressemble à un rapport de scène de crime.
Le blue/green sur un seul hôte n’est pas glamour. Ce n’est pas Kubernetes. Ce n’est pas un service mesh. C’est un petit ensemble de gestes disciplinés qui vous donnent deux versions en parallèle, et une bascule que vous pouvez inverser sans prière.
Ce que signifie le blue/green sur un hôte (et ce que ce n’est pas)
Le déploiement blue/green signifie exécuter deux versions complètes de votre service en parallèle : celle qui sert actuellement (« blue ») et la candidate (« green »). Vous validez green pendant que blue continue à servir le trafic. Ensuite vous basculez le trafic vers green. Si quelque chose sent mauvais, vous repassez à blue.
Sur un seul hôte, les contraintes sont brutales :
- Vous avez un seul noyau, une seule interface réseau, un seul système de disque. Votre « redondance » est surtout procédurale.
- Vous ne pouvez pas supposer qu’un mauvais déploiement ne puisse pas nuire à l’hôte (logs incontrôlables, fuites de mémoire, remplissage du disque).
- Votre bascule doit se faire au niveau L7 (proxy inverse) ou via la réaffectation de ports locaux. Le DNS est trop lent, et « changer le port côté client » est une comédie.
Donc l’objectif n’est pas la « haute disponibilité » au sens classique. L’objectif est le changement sûr : fenêtres d’indisponibilité plus courtes, rollback prévisible, et moins de postmortems « on ne sait pas ce qui s’est passé ».
Voici une opinion : si vous êtes sur un seul hôte, faites du blue/green avec un conteneur proxy inverse (Nginx ou HAProxy), gardez vos conteneurs d’application « stupides », et rendez explicites les changements d’état. Si vous essayez d’être malin avec des tours d’iptables et des scripts ad hoc, vous réussirez jusqu’au jour où vous échouez.
Une plaisanterie, parce qu’on le mérite : déployer sur un seul hôte, c’est comme porter deux ceintures de sécurité dans une voiture. Ça aide, mais ça ne transforme pas la voiture en avion.
Faits intéressants et courte histoire (pour que vous cessiez de répéter de vieilles erreurs)
- Le blue/green est antérieur aux conteneurs. Le modèle vient de l’ingénierie de mise en production bien avant Docker — deux environnements identiques et un flip du routeur était la plus simple histoire de « zéro downtime » disponible.
- « Déploiement atomique » signifiait autrefois basculement de symlink. De nombreux systèmes pré-containers déployaient dans des répertoires versionnés et changeaient un symlink pour le rollback instantané. Le changement d’upstream d’un reverse-proxy en est le successeur spirituel.
- Le networking initial de Docker était plus rugueux que votre mémoire ne l’admet. Le bridge par défaut a évolué ; les pratiques comme « publier juste un port et considérer que c’est fini » viennent d’une époque où moins de gens faisaient du routage multi-service sérieux sur un hôte.
- Les vérifications de santé n’ont pas toujours été de première classe. Docker Compose a normalisé progressivement les health checks explicites ; les stacks plus anciennes utilisaient des scripts fragiles « sleep 10 » en espérant que l’app soit prête.
- Nginx est un cheval de travail des releases depuis deux décennies. Ses sémantiques de reload (rechargement gracieux sans couper les connexions) en ont fait un commutateur naturel même avant que « cloud-native » ne soit un mot.
- HAProxy a popularisé la santé explicite des backends et le comportement de circuit. Beaucoup d’équipes SRE ont appris que « l’upstream écoute » n’est pas la même chose que « le service est sain ».
- Les déploiements sur un seul hôte sont toujours fréquents. Beaucoup d’applications internes rentables et de services en périphérie tournent sur des machines uniques parce que l’entreprise privilégie la simplicité à la résilience théorique.
- La métaphore « pets vs cattle » ne s’appliquait jamais à votre base financière. Même dans le monde des conteneurs, les services stateful restent spéciaux. Votre stratégie de déploiement doit en tenir compte.
Le design le plus simple qui fonctionne vraiment
Nous construisons quatre éléments mobiles :
- proxy : un conteneur proxy inverse qui possède les ports publics (80/443). Il route vers l’upstream « blue » ou « green ».
- app-blue : conteneur de l’application en production actuelle.
- app-green : conteneur candidat.
- sidecars optionnels : runner de migrations, tests smoke one-shot, ou un petit endpoint « whoami » pour valider le routage.
Les règles qui gardent cela sain :
- Seul le proxy lie des ports publics. Blue et green restent sur un réseau Docker interne sans ports publiés. Cela évite l’exposition accidentelle et les conflits de ports.
- Garder la config du proxy commutable et rechargée. Un choix d’« upstream actif » dans un seul fichier est plus simple que de templater cinquante lignes en pleine panne.
- Gater la bascule sur une vraie vérification de santé. Si votre app n’a pas d’endpoint /health, ajoutez-en un. Vous pouvez livrer des fonctionnalités plus tard ; vous ne pouvez pas livrer sans savoir si c’est vivant.
- Faire du rollback une commande de première classe. Si le rollback nécessite « se souvenir de ce que nous avons changé », vous n’avez pas de rollback. Vous avez du théâtre d’improvisation.
- Les changements d’état sont la partie difficile. Le blue/green fonctionne très bien pour le code sans état. Pour les changements de schéma de base, vous avez besoin d’une stratégie de compatibilité ou d’une étape de maintenance délibérée.
Une idée paraphrasée de John Allspaw : « En exploitation, l’échec est normal ; la résilience vient de la préparation, pas de faire comme si ça n’allait pas arriver. »
Agencement de l’hôte : ports, réseaux, volumes et la seule chose à ne pas partager
Ports
Public :
- proxy:80 et proxy:443 sont publiés vers l’hôte.
Interne :
- app-blue écoute sur 8080 à l’intérieur du conteneur.
- app-green écoute sur 8080 à l’intérieur du conteneur.
Nginx route vers app-blue:8080 ou app-green:8080 sur un réseau interne partagé.
Réseaux
Créez un réseau dédié, par ex. bg-net. N’utilisez pas le réseau par défaut si vous aimez votre futur vous.
Volumes
Voici le piège d’un hôte unique : le stockage inscriptible partagé entre blue et green peut vous gâcher la journée.
- OK à partager : actifs en lecture seule, config de confiance, certificats TLS, et peut‑être un cache si sa perte est tolérable.
- À partager prudemment : répertoires d’uploads. Deux versions d’app peuvent écrire des formats, permissions ou chemins différents.
- Ne pas partager à l’aveugle : fichiers SQLite, bases intégrées, ou tout ce que deux processus peuvent écrire concurremment sans coordination.
Si votre app écrit dans un répertoire disque local, privilégiez une de ces options :
- Externaliser l’état (stockage d’objets, base de données, etc.).
- Répertoires d’état versionnés par couleur, puis une étape de migration contrôlée et une bascule contrôlée.
- Un plan de schéma « partagé mais compatible » où les deux versions peuvent fonctionner contre la même BD et tolérer un trafic mixte pendant la bascule.
Journalisation
Sur un seul hôte, la croissance des logs est un tueur de déploiement. Utilisez les options de rotation des logs de Docker. Sinon votre « déploiement zéro downtime » devient « plus de disque, plus de service ».
Un modèle Docker Compose opérationnel
Ceci est volontairement ennuyeux. L’ennuyeux, c’est bien. Vous pourrez l’ajuster plus tard une fois que ce sera fiable.
cr0x@server:~$ cat docker-compose.yml
version: "3.9"
services:
proxy:
image: nginx:1.25-alpine
container_name: bg-proxy
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/snippets:/etc/nginx/snippets:ro
depends_on:
app-blue:
condition: service_healthy
app-green:
condition: service_healthy
networks:
- bg-net
app-blue:
image: myapp:blue
container_name: app-blue
environment:
- APP_COLOR=blue
expose:
- "8080"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health | grep -q ok"]
interval: 5s
timeout: 2s
retries: 10
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
networks:
- bg-net
app-green:
image: myapp:green
container_name: app-green
environment:
- APP_COLOR=green
expose:
- "8080"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health | grep -q ok"]
interval: 5s
timeout: 2s
retries: 10
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
networks:
- bg-net
networks:
bg-net:
name: bg-net
La configuration du proxy décide quelle couleur reçoit le trafic. Gardez cette décision dans un petit fichier.
cr0x@server:~$ mkdir -p nginx/conf.d nginx/snippets
...output...
cr0x@server:~$ cat nginx/snippets/upstream-active.conf
set $upstream app-blue;
...output...
cr0x@server:~$ cat nginx/conf.d/default.conf
server {
listen 80;
location /healthz {
return 200 "proxy ok\n";
}
location / {
include /etc/nginx/snippets/upstream-active.conf;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Request-Id $request_id;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://$upstream:8080;
}
}
...output...
Ce modèle utilise une variable de cible upstream. C’est simple à changer, simple à diff et se recharge proprement.
Mécanique de la bascule : atomique, observable, réversible
Déployer green sans toucher au trafic
Construisez/tirez la nouvelle image en tant que myapp:green. Démarrez-la à côté de blue. Validez la santé et le comportement via le proxy (ou directement sur le réseau interne via un curl en exec).
Basculer le trafic en changeant un fichier et en rechargeant Nginx
La bascule doit être :
- Suffisamment atomique : un changement, un reload.
- Observable : vous pouvez voir quelle upstream sert.
- Réversible : même mécanisme dans l’autre sens.
Le reload de Nginx est gracieux : il charge la nouvelle config et laisse les anciens workers terminer les connexions actives. Cela n’empêche pas tous les cas limites (flux longue durée), mais c’est l’option la moins mauvaise sur un seul hôte.
Rollback en rebasculant le fichier et en rechargant
La procédure de rollback doit être mécaniquement identique à la bascule. Si le rollback est « différent », il échouera à 2 h du matin quand votre cerveau fonctionne surtout au café.
Deuxième et dernière plaisanterie : le système de monitoring le plus fiable reste « un client », mais il a un routage d’alertes médiocre et aucun support de fenêtre de maintenance.
Tâches pratiques (commandes, sorties, décisions)
Voici les opérations que vous ferez réellement. Chaque tâche inclut : commande, sortie d’exemple, ce que ça signifie et la décision à prendre.
Task 1: Confirm Docker and Compose versions (capability check)
cr0x@server:~$ docker version --format '{{.Server.Version}}'
26.1.3
Ce que cela signifie : vous êtes sur un moteur Docker moderne. Cela importe pour la stabilité du réseau et le comportement des health checks.
Décision : si c’est ancien (pensez à l’ère 19.x), prévoyez du temps pour une mise à jour avant d’accuser votre application de comportements étranges.
cr0x@server:~$ docker compose version
Docker Compose version v2.29.1
Ce que cela signifie : Compose v2 est disponible ; vous pouvez compter sur le comportement actuel de l’CLI.
Décision : standardisez sur docker compose (plugin) plutôt que sur l’ancien binaire docker-compose pour réduire les différences « ça marche sur mon laptop ».
Task 2: Create the network once (reduce surprise)
cr0x@server:~$ docker network create bg-net
8d4b7b7f0d3e5e2e0f6f5f5a8d1c0d3a9c2b1f2e3a4b5c6d7e8f9a0b1c2d3e4
Ce que cela signifie : le réseau existe ; les conteneurs peuvent se résoudre par nom de service.
Décision : si votre organisation a plusieurs stacks sur un hôte, nommer explicitement les réseaux évite les collisions et les moments « pourquoi ce conteneur voit cette base ? ».
Task 3: Bring up blue and green (but don’t cut over yet)
cr0x@server:~$ docker compose up -d
[+] Running 3/3
✔ Container app-blue Started
✔ Container app-green Started
✔ Container bg-proxy Started
Ce que cela signifie : tout a démarré. Ce n’est pas la même chose que « tout est healthy ».
Décision : vérifiez immédiatement l’état de santé ensuite ; ne vous réjouissez pas sur la base d’un « Started ».
Task 4: Check container health (gate cutover)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}'
NAMES STATUS
bg-proxy Up 25 seconds
app-blue Up 25 seconds (healthy)
app-green Up 25 seconds (healthy)
Ce que cela signifie : les deux versions passent leurs health checks. Le proxy tourne.
Décision : si green est (unhealthy), arrêtez. Inspectez les logs et corrigez avant de switcher un seul paquet.
Task 5: Verify which color is serving through the proxy (ground truth)
cr0x@server:~$ curl -sS localhost/ | head -n 1
hello from blue
Ce que cela signifie : votre snippet Nginx pointe actuellement vers app-blue.
Décision : si ce n’est pas ce à quoi vous vous attendiez, corrigez la config du proxy avant de continuer. Ne déboguez pas un « déploiement » quand ce n’est que du routage.
Task 6: Validate green directly from the proxy container (bypass external noise)
cr0x@server:~$ docker exec -it bg-proxy sh -lc "wget -qO- http://app-green:8080/ | head -n 1"
hello from green
Ce que cela signifie : Nginx peut résoudre et atteindre green sur le réseau interne.
Décision : si la résolution de nom échoue, vous avez un décalage réseau (mauvais réseau, mauvais nom de service, ou conteneurs non attachés).
Task 7: Run Nginx config test before reload (avoid self-inflicted outages)
cr0x@server:~$ docker exec -it bg-proxy nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Ce que cela signifie : le reload ne cassera pas le proxy avec une erreur de syntaxe.
Décision : si cela échoue, ne reload pas. Corrigez la config d’abord. Un proxy cassé, c’est une indisponibilité instantanée.
Task 8: Switch to green (one-file change + reload)
cr0x@server:~$ sed -i 's/set \$upstream app-blue;/set $upstream app-green;/' nginx/snippets/upstream-active.conf
...output...
cr0x@server:~$ docker exec -it bg-proxy nginx -s reload
2026/01/03 09:22:48 [notice] 1#1: signal process started
Ce que cela signifie : Nginx a accepté le signal de reload et a démarré de nouveaux workers avec la nouvelle cible de routage.
Décision : vérifiez immédiatement que le trafic va maintenant sur green et surveillez les taux d’erreur quelques minutes.
Task 9: Confirm cutover worked (and didn’t half-work)
cr0x@server:~$ curl -sS localhost/ | head -n 1
hello from green
Ce que cela signifie : les requêtes sont maintenant routées vers green.
Décision : si vous voyez des résultats mixtes, vous pourriez avoir plusieurs instances de proxy, un load balancer en amont, ou du cache qui masque la réalité. Validez de bout en bout.
Task 10: Watch logs for 60 seconds after cutover (cheap canary)
cr0x@server:~$ docker logs -f --since=60s app-green
INFO listening on :8080
INFO GET / 200 3ms
INFO GET /api/orders 200 21ms
Ce que cela signifie : green sert du vrai trafic et renvoie des 200.
Décision : si vous voyez des 500, des timeouts ou des erreurs BD : rollbackez maintenant, puis déboguez calmement.
Task 11: Roll back to blue (same mechanism)
cr0x@server:~$ sed -i 's/set \$upstream app-green;/set $upstream app-blue;/' nginx/snippets/upstream-active.conf
...output...
cr0x@server:~$ docker exec -it bg-proxy nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ docker exec -it bg-proxy nginx -s reload
2026/01/03 09:24:10 [notice] 1#1: signal process started
Ce que cela signifie : le rollback est complet en ce qui concerne le routage.
Décision : si vous ne pouvez pas rollbacker en moins d’une minute, votre processus n’est pas blue/green—il est « blue/green-ish ». Corrigez le processus, pas les héroïques.
Task 12: Inspect resource pressure (single-host reality check)
cr0x@server:~$ docker stats --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
app-blue 0.10% 120MiB / 8GiB 1.46% 15MB / 18MB 5MB / 1MB 23
app-green 2.40% 620MiB / 8GiB 7.56% 120MB / 95MB 60MB / 4MB 45
bg-proxy 0.05% 20MiB / 8GiB 0.24% 135MB / 130MB 2MB / 1MB 5
Ce que cela signifie : green est plus lourd. Ça peut aller, ou être le début de la fin.
Décision : si la mémoire augmente fortement, vérifiez les fuites, les changements de cache ou l’absence de limites. Sur un seul hôte, « ajouter des replicas » n’est pas un plan.
Task 13: Verify port bindings (avoid accidental exposure and collisions)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES PORTS
bg-proxy 0.0.0.0:80->80/tcp
app-blue 8080/tcp
app-green 8080/tcp
Ce que cela signifie : seul le proxy lie le port 80 de l’hôte. Blue/green sont internes uniquement.
Décision : si vous voyez 0.0.0.0:8080->8080/tcp sur un conteneur d’app, corrigez cela. Vous avez créé un second point d’entrée qui contourne votre bascule.
Task 14: Debug DNS and network attachment (when proxy “can’t reach upstream”)
cr0x@server:~$ docker network inspect bg-net --format '{{json .Containers}}'
{"a1b2c3d4":{"Name":"app-blue","IPv4Address":"172.20.0.2/16"},"b2c3d4e5":{"Name":"app-green","IPv4Address":"172.20.0.3/16"},"c3d4e5f6":{"Name":"bg-proxy","IPv4Address":"172.20.0.4/16"}}
Ce que cela signifie : les trois conteneurs sont sur le même réseau. Les noms doivent se résoudre.
Décision : si le proxy n’est pas listé, attachez‑le au réseau ou corrigez le stanza réseau du Compose.
Task 15: Confirm image digests (ensure you’re running what you think you’re running)
cr0x@server:~$ docker image inspect myapp:green --format '{{.Id}}'
sha256:7f0c1e9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8
Ce que cela signifie : le tag green pointe vers un ID d’image spécifique.
Décision : si votre CI retague des images, fiez‑vous aux digests dans vos notes de release. « Green » ne doit pas muter silencieusement pendant un incident.
Task 16: Spot disk pressure from Docker quickly (before it becomes downtime)
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 42 8 18.4GB 9.1GB (49%)
Containers 16 3 1.2GB 900MB (75%)
Local Volumes 12 7 6.8GB 1.1GB (16%)
Build Cache 31 0 3.5GB 3.5GB
Ce que cela signifie : vous avez de l’espace récupérable, surtout le build cache.
Décision : si le disque est tendu, nettoyez le build cache dans une fenêtre contrôlée. Si vous prunez à l’aveugle pendant un incident, vous pourriez supprimer une image de rollback encore nécessaire.
Trois mini-histoires d’entreprise issues du terrain
Incident causé par une mauvaise hypothèse : « Écouter = sain »
Une entreprise de taille moyenne exploitait une API interne de facturation sur un seul hôte. L’app était conteneurisée, l’hôte stable, et les releases étaient « rapides ». Ils ont implémenté le blue/green en démarrant green sur un port différent et en changeant l’upstream Nginx.
La vérification de santé était une connexion TCP. Si le port acceptait une connexion, green était déclaré bon. Ça semblait raisonnable : le processus est lancé, non ?
Puis une release a ajouté une migration de démarrage qui remplissait des caches en récupérant un gros morceau de données depuis la base. Le processus ouvrait le port tôt (comportement par défaut du framework), mais il n’était pas réellement prêt à servir de vraies requêtes. Sous charge, il renvoyait des 503 et des timeouts tout en étant déclaré « healthy ». Nginx a joyeusement envoyé le trafic de production vers un service à moitié réveillé.
L’incident n’a pas duré longtemps parce que le rollback était rapide. Le dommage fut que tout le monde a perdu confiance dans le processus de déploiement et a commencé à pousser des changements seulement pendant les heures de bureau. Ce n’est pas de la fiabilité ; c’est de la planification basée sur la peur.
Ils ont corrigé cela en changeant l’endpoint de santé pour valider les dépendances en aval : connectivité BD, migrations terminées, et une requête triviale. L’endpoint est resté rapide et peu coûteux. La bascule est redevenue ennuyeuse.
Optimisation qui s’est retournée contre eux : « Partageons tout pour économiser du disque »
Une autre équipe exploitait un service de contenu avec des uploads utilisateurs stockés sur disque local. Pour « optimiser », ils ont monté le même volume d’uploads dans blue et green afin de ne rien synchroniser et pouvoir rollbacker instantanément.
Cela a fonctionné pendant des mois, jusqu’à ce qu’une release change la génération des miniatures. Green a commencé à écrire de nouveaux fichiers avec un nommage différent et un masque de permissions légèrement différent. Blue, toujours en cours d’exécution, continuait à servir d’anciennes pages qui référençaient des miniatures maintenant écrasées ou remplacées en plein vol.
Le symptôme était étrange : pas une panne totale, mais des images cassées intermittentes et des 403 occasionnels. Les tickets support sont arrivés en premier, puis l’ingénierie. L’équipe a chassé les en‑têtes de cache, le comportement du CDN et même des bugs de navigateur. La cause racine était banale : deux versions écrivant dans le même répertoire sans contrat de compatibilité.
La correction fut tout aussi banale : répertoires versionnés pour les artefacts générés, et un job en arrière‑plan pour backfiller. Les uploads partagés sont demeurés, mais les sorties dérivées sont devenues versionnées puis migrées. L’« optimisation disque » a économisé quelques gigaoctets et leur a coûté une semaine de debugging.
Pratique ennuyeuse mais correcte qui a sauvé la mise : « Garder l’ancienne image et le chemin de rollback »
Une entreprise régulée exploitait un hôte unique dans un segment réseau contrôlé pour une passerelle d’intégration legacy. Pas d’autoscaling, pas de magie — juste de la gestion de changement et un pager.
Ils ont implémenté le blue/green avec un conteneur proxy et deux conteneurs d’app, mais ils ont aussi appliqué une règle ennuyeuse : ne jamais prune les images pendant les heures de bureau, et toujours garder la dernière image connue bonne épinglée par digest dans un mirror de registre local. Personne n’aimait cette règle. Ça semblait du désordre.
Un après‑midi, une bibliothèque amont a publié une version mineure cassée qui passait les tests unitaires mais provoquait un crash à l’exécution sous une poignée spécifique de handshake TLS. Le conteneur green a démarré, est apparu healthy pendant un court instant, puis a crashé sous le vrai trafic client.
Le rollback a fonctionné instantanément. Pas de drame. Mais le véritable sauvetage fut ceci : l’équipe a pu redéployer l’image connue bonne même après que le CI ait fait avancer les tags, car le digest était conservé localement. Ils n’ont pas eu à « reconstruire le commit de la semaine dernière » sous pression.
Rien d’héroïque ne s’est passé, ce qui est exactement le but. Le postmortem fut court, technique et sans états d’âme.
Mode opératoire de diagnostic rapide
Quand un déploiement blue/green sur un seul hôte tourne mal, le goulot est généralement l’un de : routage, santé, ressources ou état. Vérifiez dans cet ordre ; c’est optimisé pour « arrêter l’hémorragie » d’abord.
1) Routage : le trafic va‑t‑il là où vous croyez ?
- Vérifiez que le proxy est vivant et sert
/healthz. - Vérifiez quel upstream est actif dans le fichier snippet.
- Vérifiez que le test de config Nginx passe et que le reload a eu lieu.
cr0x@server:~$ curl -sS -i localhost/healthz | head -n 1
HTTP/1.1 200 OK
Décision : si la santé du proxy échoue, arrêtez de déboguer l’app. Réparez le proxy/conteneur/pare‑feu hôte d’abord.
2) Santé : green est‑il réellement healthy, ou juste « running » ?
cr0x@server:~$ docker inspect -f '{{.State.Health.Status}}' app-green
healthy
Décision : si unhealthy, ne basculez pas. Si vous avez déjà basculé, rollbackez et déboguez green hors production.
3) Ressources : affamez‑vous l’hôte ?
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 6.9Gi 210Mi 120Mi 610Mi 430Mi
Swap: 0B 0B 0B
Décision : si la mémoire disponible est faible et que green est plus lourd, vous êtes en voie d’être tué par l’OOM. Rollbackez ou ajoutez des limites et de la capacité.
4) État : avez‑vous changé quelque chose d’irréversible ?
Si l’app utilise une base de données, vérifiez les migrations de schéma et la compatibilité. Si vous avez changé des formats de données, le rollback peut ne pas restaurer le comportement.
cr0x@server:~$ docker logs --since=10m app-green | tail -n 20
INFO migration complete
INFO connected to db
Décision : si des migrations ont été exécutées et ne sont pas rétrocompatibles, revenir au code précédent ne corrigera peut‑être pas le problème. Votre « déploiement » est devenue un incident de données.
Erreurs courantes : symptômes → cause racine → fix
1) Symptom: Nginx shows “502 Bad Gateway” after cutover
- Cause racine : le proxy ne peut pas atteindre green (mauvais réseau, nom de conteneur incorrect, green a crashé, ou l’app écoute sur un port différent).
- Correctif : vérifiez l’attachement réseau et le nom de service ; confirmez que green est healthy ; confirmez que l’app écoute sur 8080.
cr0x@server:~$ docker exec -it bg-proxy sh -lc "wget -S -qO- http://app-green:8080/health"
HTTP/1.1 200 OK
...
ok
2) Symptom: Green is “healthy” but users see errors
- Cause racine : le health check est trop superficiel (port ouvert, pas prêt côté dépendances), ou il ne représente pas des chemins de requête réels.
- Correctif : faites en sorte que le health check valide les dépendances critiques et une requête représentative légère. Gardez‑le rapide.
3) Symptom: Cutover works, then performance collapses 5–15 minutes later
- Cause racine : fuite mémoire, croissance du cache, changement de pool de connexions, ou amplification des logs provoquant une pression I/O disque.
- Correctif : vérifiez
docker stats, la mémoire de l’hôte, et le volume des logs ; fixez des limites mémoire pour les conteneurs ; faites la rotation des logs ; investiguez les pools de connexions.
4) Symptom: Rollback doesn’t fix the problem
- Cause racine : green a effectué un changement d’état irréversible (migration de schéma, réécriture des données), ou le proxy route encore vers green à cause d’une config obsolète ou de proxies multiples.
- Correctif : confirmez le routage ; si l’état a changé, appliquez un correctif en avant ou restaurez depuis une sauvegarde/snapshot. On ne peut pas « revenir dans le temps » avec du YAML.
5) Symptom: Both blue and green are serving traffic unexpectedly
- Cause racine : les conteneurs d’app ont publié des ports hôtes, contournant le proxy, ou un load balancer externe cible les deux.
- Correctif : supprimez la publication de ports hôtes des conteneurs d’app ; assurez‑vous que seul le proxy est exposé ; validez les cibles du routage externe.
6) Symptom: Deploys intermittently fail with “bind: address already in use”
- Cause racine : vous avez essayé de binder les conteneurs d’app directement aux ports hôtes pour les deux couleurs.
- Correctif : arrêtez de binder les ports d’app sur l’hôte. Ne bind que le proxy sur les ports hôtes, et routez en interne.
7) Symptom: Nginx reload causes a brief drop in long-lived connections
- Cause racine : websockets/flux longs et paramètres de proxy pas ajustés ; le reload est gracieux mais pas magique.
- Correctif : vérifiez les settings keepalive ; pour des connexions vraiment longue durée, envisagez HAProxy avec un comportement de backend explicite, ou acceptez une courte fenêtre de maintenance.
8) Symptom: Disk fills during deploy
- Cause racine : croissance des logs, prolifération d’images, accumulation du build cache.
- Correctif : configurez la rotation des logs Docker ; prunez de manière intentionnelle ; gardez les images de rollback épinglées ; surveillez le disque.
Listes de contrôle / plan étape par étape
Check-list pré‑vol (avant de toucher au trafic de production)
- Le proxy est le seul service à publier
80/443sur l’hôte. - Blue et green tournent sur le même réseau Docker interne.
- Des health checks existent et valident les dépendances (pas seulement « port ouvert »).
- Les logs ont une rotation (
max-size,max-file). - Le chemin de rollback est testé : rebasculer vers blue et recharger le proxy.
- Les changements d’état sont planifiés : les migrations sont rétrocompatibles ou séparées.
- Vous savez comment vérifier « quelle couleur est active » en utilisant une requête réelle.
Plan de déploiement étape par étape (blue/green sur un seul hôte)
- Construire/tirer l’image green et la labelliser clairement.
- Démarrer green à côté de blue (pas de ports publics sur green).
- Attendre la santé : green doit être
healthy. - Faire un smoke test de green depuis l’intérieur du conteneur proxy (réseau et réponse).
- Exécuter le test de config Nginx avant le reload.
- Changer le fichier upstream vers green.
- Recharger le proxy et confirmer que le trafic en direct va vers green.
- Observer les logs, le taux d’erreur, la latence et les ressources hôte pendant quelques minutes.
- Garder blue en fonctionnement jusqu’à ce que vous soyez confiant (votre fenêtre de rollback).
- Après acceptation, arrêter blue et conserver son image pour au moins un cycle de release.
Check-list de rollback (quand green vous déçoit)
- Rebasculer le fichier upstream vers blue.
- Tester la config Nginx.
- Recharger Nginx.
- Vérifier que le trafic en direct atteint blue.
- Capturer les logs et métriques de green pour débogage.
- Décider de garder green pour enquête ou de l’arrêter pour libérer des ressources.
FAQ
1) Puis‑je faire du blue/green sans proxy inverse ?
Vous pouvez, mais vous allez réinventer un proxy inverse avec des ports hôtes, iptables ou des astuces DNS. Sur un seul hôte, un proxy explicite est l’option la moins mauvaise.
2) Pourquoi ne pas simplement utiliser la publication de ports Docker et échanger les ports ?
Parce que vous ne pouvez pas binder deux conteneurs sur le même port hôte, et l’échange n’est pas atomique à moins d’ajouter de la machinerie. De plus, vous finirez par exposer les deux versions à un moment donné.
3) Le reload Nginx est‑il vraiment sans interruption ?
Pour des requêtes HTTP typiques, c’est proche. Le reload Nginx est gracieux, mais les connexions longue durée et certains clients étranges peuvent voir des coupures. Conceptionnez en tenant compte de cela.
4) Blue et green doivent‑ils partager la même base de données ?
Généralement oui, mais seulement si votre stratégie de migration de schéma supporte la période de chevauchement. Si green nécessite une migration incompatible, vous avez besoin d’une étape de données planifiée ou d’une fenêtre d’indisponibilité.
5) Quelle est la manière la plus sûre de gérer les migrations de schéma ?
Privilégiez les migrations rétrocompatibles : ajoutez des colonnes, évitez les changements destructeurs, et déployez du code pouvant gérer les anciennes et nouvelles formes. Faites le nettoyage destructif plus tard.
6) Combien de temps garder blue en fonctionnement après la bascule ?
Assez longtemps pour attraper les échecs que vous observez réellement dans votre environnement : généralement des minutes à quelques heures. Si les échecs apparaissent souvent « le lendemain matin », gardez blue disponible plus longtemps—si les ressources le permettent.
7) Puis‑je canaryser un petit pourcentage du trafic sur un seul hôte ?
Oui, mais c’est plus complexe. HAProxy rend les backends pondérés simples. Avec Nginx, c’est faisable, mais votre configuration et votre observabilité doivent être solides.
8) Qu’en est‑il du TLS sur le proxy ?
Terminez le TLS au proxy. Gardez les certificats sur l’hôte en montages lecture seule. Ne terminez pas le TLS dans les deux conteneurs d’app à moins d’aimer le débogage.
9) Ai‑je besoin de limites de ressources conteneur pour blue/green ?
Sur un seul hôte, oui. Sans limites, deux versions exécutées ensemble peuvent affamer l’hôte et rendre le rollback inutile parce que tout est lent.
10) Comment prouver quelle version a servi une requête ?
Retournez une chaîne de version dans un header de réponse ou dans un endpoint de debug. Enregistrez‑la. Quand quelqu’un dit « green est cassé », vous avez besoin de preuves, pas d’impressions.
Conclusion : prochaines étapes pratiques
Le blue/green sur un seul hôte ne vise pas la perfection. Il vise à éliminer les pires échecs : déploiements cassés, rollbacks lents, et « on ne sait pas ce qui est en production ». Si vous ne faites que trois choses, faites celles‑ci :
- Placez un proxy inverse devant et faites‑en le seul point d’entrée public.
- Rendez les health checks réels, et gatez la bascule sur eux.
- Faites de la bascule et du rollback la même opération en sens inverse.
Puis disciplinez‑vous sur les parties peu sexy : rotation des logs, conservation des images de rollback, et considérer les changements d’état comme des événements séparés. Vos incidents futurs arriveront encore. Ils seront juste plus courts, plus propres et moins théâtraux.