MAJ En savoir plus - Semaine 4 · L'essaim

Mises à jour continues
et retours arrière

Changer la version d'un service pendant que les clients continuent de naviguer, et revenir en arrière en une commande quand ça tourne mal. C'est la promesse de l'orchestration : le déploiement sans nuit blanche.

Ce qu'il vous faut

Un essaim qui fonctionne, même minuscule : une seule machine avec docker swarm init suffit pour tout l'article. Si vous avez encore vos cinq noeuds du laboratoire, c'est encore mieux : vous verrez les répliques se remplacer d'un noeud à l'autre.

Les deux idées

Mettre à jour sans débrancher, reculer sans paniquer

Au laboratoire, vous avez déclaré un état désiré et regardé l'essaim le maintenir. La suite logique : que se passe-t-il quand l'état désiré change ? Une nouvelle version de l'image, par exemple.

Rolling update

La mise à jour continue

Plutôt que d'arrêter tout le service pour le relancer dans sa nouvelle version, l'essaim remplace les répliques une par une (ou par petits lots). Pendant qu'une réplique change de version, les autres continuent de servir les clients : zéro interruption vue de l'extérieur.

Rollback

Le retour arrière

La nouvelle version est cassée ? docker service rollback ramène le service à sa configuration précédente : image, mais aussi ports, variables, répliques. Même mécanique progressive, dans l'autre sens. Le filet de sécurité du déploiement.

Vue d'ensemble

Le parcours en sept étapes

On monte un petit service web en version 1.19, on le met à jour vers la 1.21 sous les yeux des clients, on observe la mécanique, puis on recule. En fin de parcours : les réglages qui contrôlent le rythme.

Étape 01 · Essaim

Un essaim, même petit

Étape 02 · Service

Trois répliques en 1.19

Étape 03 · Photo

L'état avant le changement

Étape 04 · Mise à jour

1.19 vers 1.21, en marche

Étape 05 · Observation

Lire le remplacement

Étape 06 · Recul

Le retour arrière

Étape 07 · Réglages

Rythme, lots et échecs

01

Un essaim, même petit

Essaim

Avoir un essaim actif. Une machine seule fait très bien l'affaire : elle devient gestionnaire et travailleur à la fois.

Terminal - sur le gestionnaire
$ docker swarm init
Swarm initialized: current node (x7k2m...) is now a manager.
docker node ls montre au moins un noeud avec le statut Ready et la mention Leader. Si vous êtes déjà dans un essaim (le laboratoire !), sautez cette étape.
02

Créer le service : trois répliques en version 1.19

Service

Déployer la vitrine web de la petite entreprise : trois répliques de nginx, version 1.19, publiées sur le port 8080.

Terminal - création du service
$ docker service create --name vitrine --replicas 3 -p 8080:80 nginx:1.19
overall progress: 3 out of 3 tasks
verify: Service vitrine converged

Le détail qui compte : on écrit nginx:1.19, jamais nginx tout court. Une version épinglée rend la mise à jour visible et volontaire : on sait d'où on part et où on va.

docker service ls affiche vitrine avec REPLICAS 3/3 et IMAGE nginx:1.19. Et curl -sI localhost:8080 | grep -i server répond Server: nginx/1.19....
Avec nginx:latest, impossible de savoir quelle version tourne vraiment, et le retour arrière perd son sens : latest d'hier et latest de demain portent le même nom. Les versions épinglées sont la fondation de tout ce qui suit.
03

Photographier l'état avant le changement

Photo

Savoir lire docker service ps : la liste des tâches du service, avec leur version, leur noeud et leur état.

Terminal - l'état désiré, en chair et en os
$ docker service ps vitrine
NAME        IMAGE       NODE           DESIRED STATE  CURRENT STATE
vitrine.1   nginx:1.19  swarm-node01   Running        Running 2 minutes ago
vitrine.2   nginx:1.19  swarm-node02   Running        Running 2 minutes ago
vitrine.3   nginx:1.19  swarm-node03   Running        Running 2 minutes ago

Trois tâches, trois répliques de la même image, réparties sur les noeuds disponibles (sur une machine seule : trois fois le même noeud, c'est correct aussi). Gardez cette photo en tête : c'est elle qui va changer sous vos yeux.

04

La mise à jour continue : 1.19 vers 1.21

Mise à jour

Changer la version de l'image d'un service en marche, et regarder l'essaim orchestrer le remplacement réplique par réplique.

Terminal - la commande du jour
$ docker service update --image nginx:1.21 vitrine
vitrine
overall progress: 1 out of 3 tasks
1/3: running
2/3: preparing
3/3:

Par défaut, l'essaim remplace une tâche à la fois : il arrête la première réplique, lance sa remplaçante en 1.21, attend qu'elle soit en marche, puis passe à la deuxième. Pendant tout ce temps, les deux autres répliques continuent de répondre aux clients.

Pendant que ça roule

Ouvrez un deuxième terminal et martelez le service : il ne bronche pas.

Terminal 2 - le client qui ne se doute de rien
$ while true; do curl -sI localhost:8080 | grep -i server; sleep 1; done
Server: nginx/1.19.10
Server: nginx/1.19.10
Server: nginx/1.21.6   ← la bascule, sans une seule requête perdue
Server: nginx/1.21.6
La commande docker service update se termine par verify: Service vitrine converged : l'état désiré est atteint, les trois répliques sont en 1.21.
05

Lire le remplacement dans l'historique

Observation

Comprendre l'affichage de docker service ps après une mise à jour : les anciennes tâches restent visibles, marquées Shutdown.

Terminal - l'historique raconte la mise à jour
$ docker service ps vitrine
NAME            IMAGE       NODE           DESIRED STATE  CURRENT STATE
vitrine.1       nginx:1.21  swarm-node01   Running        Running 3 minutes ago
 \_ vitrine.1   nginx:1.19  swarm-node01   Shutdown       Shutdown 3 minutes ago
vitrine.2       nginx:1.21  swarm-node02   Running        Running 2 minutes ago
 \_ vitrine.2   nginx:1.19  swarm-node02   Shutdown       Shutdown 2 minutes ago
vitrine.3       nginx:1.21  swarm-node03   Running        Running 2 minutes ago
 \_ vitrine.3   nginx:1.19  swarm-node03   Shutdown       Shutdown 2 minutes ago

Chaque ligne \_ est une ancienne tâche : le conteneur 1.19 arrêté, conservé dans l'historique. On lit littéralement la mise à jour : la 1.21 est arrivée, la 1.19 s'est éteinte, réplique par réplique, avec l'heure de chaque bascule.

docker service ls n'affiche plus qu'une chose : vitrine ... nginx:1.21. La photo de l'étape 03 a changé.
06

Le retour arrière : une commande, pas une nuit blanche

Recul

Scénario catastrophe : la 1.21 casse quelque chose en production. Ramener le service à sa configuration précédente, sans interruption non plus.

Terminal - le filet de sécurité
$ docker service rollback vitrine
vitrine
rollback: manually requested rollback
overall progress: rolling back update: 3 out of 3 tasks
verify: Service vitrine converged

$ docker service ls
NAME      MODE        REPLICAS  IMAGE
vitrine   replicated  3/3       nginx:1.19

Même mécanique progressive que la mise à jour, dans l'autre sens : les répliques 1.21 sont remplacées une par une par des 1.19. Le retour arrière restaure toute la configuration précédente du service, pas seulement l'image : ports, variables d'environnement, nombre de répliques.

docker service update --rollback vitrine fait exactement la même chose : rollback est le raccourci moderne de cette forme.
Le retour arrière ne remonte que d'un cran : Swarm garde la configuration précédente, pas tout l'historique. Deux rollback de suite vous ramènent... à votre point de départ (le deuxième annule le premier). Pour viser une version précise, utilisez plutôt docker service update --image nginx:1.19 vitrine : explicite et sans surprise.
07

Régler le rythme : lots, pauses et échecs

Réglages

Contrôler la mise à jour comme un chef d'orchestre : combien de répliques à la fois, quelle pause entre les lots, et quoi faire si une nouvelle tâche refuse de démarrer.

Terminal - une mise à jour sur mesure
$ docker service update \
    --update-parallelism 2 \
    --update-delay 10s \
    --update-failure-action rollback \
    --image nginx:1.21 vitrine
OptionCe qu'elle contrôlePar défaut
--update-parallelismCombien de répliques sont remplacées en même temps. 2 : par lots de deux. 0 : toutes d'un coup (adieu la continuité !).1
--update-delayLa pause entre deux lots. 10s laisse le temps de voir un problème avant qu'il ne touche tout le monde.0s
--update-failure-actionLa réaction si une nouvelle tâche échoue : pause (on gèle et un humain regarde), continue (on fonce), rollback (retour arrière automatique).pause
--update-orderstop-first : on arrête l'ancienne avant de lancer la nouvelle. start-first : la nouvelle démarre d'abord, l'ancienne s'éteint ensuite (brièvement 4 répliques au lieu de 3).stop-first
--update-monitorCombien de temps surveiller chaque nouvelle tâche avant de la déclarer réussie.5s

Ces réglages peuvent aussi se donner dès la création du service (mêmes options sur docker service create), ou dans la section deploy: d'une pile Compose : c'est la même grammaire que votre wordpress-sticky.yml du laboratoire.

docker service inspect --pretty vitrine montre la section UpdateConfig avec vos valeurs : elles sont mémorisées pour toutes les mises à jour futures du service.
La mise à jour continue garantit qu'une nouvelle tâche démarre, pas que l'application fonctionne. Un conteneur peut être « Running » avec un site en erreur 500. Pour que --update-failure-action rollback attrape ces cas, l'image doit déclarer un HEALTHCHECK : c'est lui qui dit à l'essaim « je suis vraiment en santé ».

L'art du déploiement

Les bonnes pratiques, en cinq réflexes

Trois défis sur votre essaim

Le test du client fidèle

Relancez la boucle curl de l'étape 04, puis comparez une mise à jour en stop-first et une en start-first. Voyez-vous une différence dans les réponses ? Avec une seule réplique au lieu de trois ?

Le sabotage volontaire

Mettez à jour vers une image qui n'existe pas (nginx:1.99) avec --update-failure-action rollback. Observez l'essaim essayer, échouer et reculer tout seul dans docker service ps vitrine.

Le vrai morceau

Sur votre pile du laboratoire, mettez à jour l'image de WordPress vers une version précise, puis reculez. Question piège : pourquoi faut-il beaucoup plus de prudence avec la base de données qu'avec le web ? (Indice : la semaine 4 l'a déjà dit - le web sans état se réplique, la base avec état s'ancre... et son schéma ne « recule » pas tout seul.)