Docker Swarm rolling updates
Docker Swarm rolling updates sont un moyen très facile d'effectuer des mises à jour sans temps d'arrêt.

Il y a quelque temps, j'ai écrit qu'il serait préférable de passer à une variante Kubernetes et maintenant ce billet concerne Docker Swarm. Oui, j'utilise encore Docker Swarm parce que j'ai un projet qui l'utilise. J'ai récemment déplacé le développement de Docker vers Docker Swarm, principalement parce qu'avec Docker Swarm vous apprenez les bases de l'orchestration de conteneurs, alors pourquoi ne pas apprendre cela pendant le développement.
Dans ce billet, nous allons nous intéresser à rolling updates : une variable d'environnement et une image. Je suppose que vous avez déjà une certaine expérience pratique avec Docker Swarm. Comme toujours, je fais ceci sur Ubuntu 22.04.
Notre projet Docker-Compose
Nous commençons par créer un projet Docker-Compose. L'arborescence du projet :
.
├── docker-compose.yml
├── .env
├── app
│ └── run.sh
Les fichiers :
# file: .env
COMPOSE_PROJECT_NAME=my-project
LOGGER_LEVEL=DEBUG
Nous utilisons l'image busybox et commençons avec deux répliques. Nous mettons également à disposition certaines variables spécifiques à Docker Swarm :
- X_SERVICE_LABEL_STACK_IMAGE : informations sur l'image.
- X_TASK_SLOT : le numéro de l'instance (de la tâche).
# file: docker-compose.yml
version: "3.7"
x-service_defaults: &service_defaults
env_file:
- ./.env
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
busybox:
<< : *service_defaults
deploy:
mode: replicated
replicas: 2
restart_policy:
condition: on-failure
image: busybox:1.35.0
environment:
# swarm info
X_COMPOSE_PROJECT_NAME: "${COMPOSE_PROJECT_NAME}"
X_SERVICE_LABEL_STACK_IMAGE: '{{index .Service.Labels "com.docker.stack.image"}}'
X_TASK_SLOT: "{{.Task.Slot}}"
ports:
- "127.0.0.1:8280:8280"
volumes:
- "./app:/app"
command: /bin/sh /app/run.sh
networks:
- my-project-network
networks:
my-project-network:
external: true
name: my-project-network
Nous utilisons un script 'run.sh', appelé au démarrage du conteneur, qui fait deux choses :
- Il démarre un script httpd server en arrière-plan.
- Il génère des lignes de journal dans une boucle, imprimées sur stdout.
# file: app/run.sh
echo "Starting httpd server instance ${X_TASK_SLOT} ..."
echo "Hello from httpd server instance ${X_TASK_SLOT}" > /var/www/index.html
/bin/httpd -f -p 8280 -h /var/www/ &
echo "Starting output ..."
while true; do echo "IMAGE: ${X_SERVICE_LABEL_STACK_IMAGE}, LOGGER_LEVEL = ${LOGGER_LEVEL}"; sleep 1; done
Avec Docker Swarm, nous ne créons généralement pas de réseaux dans le fichier 'docker-compose.yml' mais nous utilisons des réseaux externes, plus précisément des réseaux 'overlay' . Lors de la création d'un tel réseau, nous pouvons également spécifier un drapeau qui permet aux conteneurs non gérés par Docker Swarm de se connecter à ce réseau.
Pour créer le réseau :
docker network create -d overlay --attachable my-project-network
Pour voir ce réseau :
docker network ls
Résultat :
NETWORK ID NAME DRIVER SCOPE
...
qn7qwhpsooty my-project-network overlay swarm
...
Quelques commandes Docker Swarm
Une remarque sur les commandes. Nous utilisons souvent '--detach=false'. Cela signifie que la commande ne revient pas immédiatement, mais qu'elle revient à la fin. Entre-temps, des informations utiles sont affichées dans le terminal.
Faisons apparaître notre projet, la construction laide est utilisée pour passer les variables d'environnement :
env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project
Resultat :
WARN[0000] ignoring IP-address (127.0.0.1:8280:8280/tcp) service will listen on '0.0.0.0'
Creating service my-project_busybox
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service vflo2g4fiybtx0p9b596uk445 converged
Notez l'avertissement ici. Ceci est inattendu et différent de Docker et signifie qu'avec Docker Swarm, nous créons un port ouvert, attention !
Pour supprimer notre projet, nous pouvons utiliser :
docker stack rm --detach=false my-project
Pour afficher les services de la pile :
docker stack services my-project
Résultat :
ID NAME MODE REPLICAS IMAGE PORTS
2oz3yg39zuvx my-project_busybox replicated 2/2 busybox:1.35.0 *:8280->8280/tcp
Pour afficher les tâches du service 'mon-projet_busybox' :
docker service ps my-project_busybox
Résultat :
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
v1pc5p8yb2fx my-project_busybox.1 busybox:1.35.0 myra Running Running about a minute ago
6ozvx31c6isq my-project_busybox.2 busybox:1.35.0 myra Running Running about a minute ago
Vérifier les logs, pour chaque tâche, chaque seconde une nouvelle ligne de log :
docker service logs -t -f my-project_busybox
Résultat :
...
2024-07-07T15:42:20.805354434Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:21.808005147Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:22.807919531Z my-project_busybox.1.v1pc5p8yb2fx@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:22.809102067Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:23.808999822Z my-project_busybox.1.v1pc5p8yb2fx@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T15:42:23.809973729Z my-project_busybox.2.6ozvx31c6isq@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
Dans le journal, nous voyons que les deux tâches sont en cours d'exécution.
Pour vérifier la tâche httpd server, nous l'exécutons sur notre hôte :
curl 127.0.0.1:8280
Résultat :
Hello from httpd server instance 1
Si nous répétons l'opération plusieurs fois :
cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done
Résultat :
...
Hello from httpd server instance 2
Hello from httpd server instance 1
Hello from httpd server instance 2
Hello from httpd server instance 1
Nous voyons ici que l'équilibreur de charge Docker Swarm alterne les requêtes vers les deux instances.
Enfin, inspectons le service :
docker service inspect --pretty my-project_busybox
Résultat :
ID: 2oz3yg39zuvxyl2k4hc77qsic
Name: my-project_busybox
Labels:
com.docker.stack.image=busybox:1.35.0
com.docker.stack.namespace=my-project
Service Mode: Replicated
Replicas: 2
Placement:
UpdateConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
...
Notez le paramètre 'UpdateConfig' 'Parallelism' . La valeur 1 signifie que le processus de mise à jour mettra d'abord à jour une seule tâche et qu'une fois cette mise à jour terminée, il mettra à jour une tâche suivante. Le même paramètre est également présent dans le paramètre 'RollbackConfig'.
Mise à l'échelle par l'ajout de répliques
Jusqu'à présent, notre service dispose de deux répliques. Si nous effectuons une rolling update avec une seule tâche présente, notre service sera temporairement indisponible. Ce n'est pas ce que nous voulons. Avec deux tâches, Docker Swarm peut mettre à jour une tâche et, une fois cette tâche mise à jour, il peut mettre à jour la deuxième tâche. Cela signifie que notre service reste disponible en permanence.
Pour ajouter d'autres répliques, par exemple 3 :
docker service scale my-project_busybox=3
Vérification de rolling updates
Pour vérifier si notre service est mis à jour, nous pouvons consulter le journal du service. Celui-ci indique les tâches en cours d'exécution et la date de redémarrage d'une tâche.
docker service logs -t -f my-project_busybox
Pour vérifier que notre service n'est pas interrompu pendant le processus de mise à jour, nous pouvons vérifier le httpd server dans un terminal séparé, dans une boucle "sans fin" comme mentionné précédemment. Nous ne devrions pas voir d'interruptions :
cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done
Rolling updates et les retours en arrière
Pourquoi l'appelle-t-on 'Rolling update' ? Parce que nous passons une instruction de mise à jour avec de nouvelles données à Docker Swarm, et lui demandons d'effectuer la mise à jour.
Ci-dessous, il y a deux scénarios de mise à jour de service :
- Mise à jour d'une variable d'environnement du service
- Mise à jour de l'image du service
La commande de mise à jour est :
docker service update <parameters> my-project_busybox
Le type de mise à jour est spécifié par les paramètres.
Comme une mise à jour peut échouer, nous voulons pouvoir revenir à la version précédente. Dans les deux cas, la commande de retour en arrière est la suivante
docker service rollback my-project_busybox
1. Rolling update : Variable d'environnement
Nous modifions ici la variable 'LOGGER_LEVEL' de notre application. La variable 'LOGGER_LEVEL' est initialement chargée à partir du fichier .env et a la valeur 'DEBUG'. Nous la remplaçons par 'WARNING' à l'aide de la commande de mise à jour suivante :
docker service update --env-add LOGGER_LEVEL=WARNING my-project_busybox
Résultat :
my-project_busybox
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service my-project_busybox converged
Le journal de service indique ce qui suit pendant la mise à jour :
...
2024-07-07T16:35:36.177525585Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:37.178678148Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:37.528564504Z my-project_busybox.2.yna9ftex6bau@myra | Starting httpd server instance 2 ...
2024-07-07T16:35:37.528847322Z my-project_busybox.2.yna9ftex6bau@myra | Starting output ...
2024-07-07T16:35:37.529281987Z my-project_busybox.2.yna9ftex6bau@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
2024-07-07T16:35:38.180094076Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:49.542707071Z my-project_busybox.2.yna9ftex6bau@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
2024-07-07T16:35:50.194103057Z my-project_busybox.1.5jwup67xwmbc@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = DEBUG
2024-07-07T16:35:52.132182215Z my-project_busybox.1.qrqsbiowltle@myra | Starting httpd server instance 1 ...
2024-07-07T16:35:52.132401060Z my-project_busybox.1.qrqsbiowltle@myra | Starting output ...
2024-07-07T16:35:52.132788443Z my-project_busybox.1.qrqsbiowltle@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
2024-07-07T16:35:52.546112046Z my-project_busybox.2.yna9ftex6bau@myra | IMAGE: busybox:1.35.0, LOGGER_LEVEL = WARNING
Revenons maintenant en arrière :
docker service rollback my-project_busybox
Résultat :
my-project_busybox
rollback: manually requested rollback
overall progress: rolling back update: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service my-project_busybox converged
Après l'opération de rollback, le 'LOG_LEVEL' est revenu à 'DEBUG', vérifiez le journal de service.
2. Rolling update : Image
Dans un autre scénario, nous avons une nouvelle image pour notre application. Ici, nous passons de busybox:1.35.0 à busybox:1.36.0. La commande de mise à jour est :
docker service update --image busybox:1.36.0 my-project_busybox
Et, encore une fois, la commande de retour en arrière est: :
docker service rollback my-project_busybox
Que se passe-t-il si un problème survient pendant la mise à jour ?
Faisons une erreur et mettons à jour avec une image inexistante :
docker service update --image busybox:9.99.0 my-project_busybox
Résultat :
image busybox:9.99.0 could not be accessed on a registry to record
its digest. Each node will access busybox:9.99.0 independently,
possibly leading to different nodes running different
versions of the image.
my-project_busybox
overall progress: 0 out of 2 tasks
1/2: preparing [=================================> ]
2/2:
service update paused: update paused due to failure or early termination of task n371geu35a4u5xe9oclefv5j9
Lorsque la mise à jour d'une tâche échoue, le processus de mise à jour est interrompu. L'autre tâche reste en cours d'exécution, ce qui signifie que notre service est toujours disponible. Nous le constatons également en inspectant le service :
docker service inspect --pretty my-project_busybox
Résultat :
ID: k4a0vy77wirk1fglso42qxx38
Name: my-project_busybox
Labels:
com.docker.stack.image=busybox:1.35.0
com.docker.stack.namespace=my-project
Service Mode: Replicated
Replicas: 2
UpdateStatus:
State: paused
Started: 2 minutes ago
Message: update paused due to failure or early termination of task n371geu35a4u5xe9oclefv5j9
Placement:
...
Comme mentionné précédemment, nous pouvons revenir à l'état précédant la mise à jour en émettant un rollback :
docker service rollback my-project_busybox
Le service est mis à jour avec 'docker stack deploy' et 'docker-compose.yml'.
Maintenant, les choses deviennent un peu moches. Jusqu'à présent, nous avons mis à jour nos services en utilisant 'docker service update', et nous pouvions revenir à une version précédente en lançant 'docker service rollback'.
Mais nous utilisons ici un fichier 'docker-compose.yml'. Il semble possible de modifier le fichier 'docker-compose.yml' et de redéployer à nouveau.
Voyons ce qui se passe. Tout d'abord, nous modifions la variable d'environnement dans le fichier '.env ' et la balise image dans le fichier 'docker-compose.yml'. Ensuite, nous lançons à nouveau la commande de déploiement :
env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project
Résultat :
Updating service my-project_busybox (id: mi27j7jjsz146y4wqqre439io)
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service mi27j7jjsz146y4wqqre439io converged
Notez que le résultat mentionne maintenant 'Updating service', alors que la première fois il mentionnait 'Creating service'.
Quoi qu'il en soit, cela signifie que Docker Swarm effectue une mise à jour (sans temps d'arrêt) pour chaque service dans le fichier 'docker-compose.yml', de la même manière que nous mettons à jour des services individuels en utilisant 'docker service update'. C'est très bien !
Mais les choses peuvent mal tourner et il n'y a pas de commande 'docker stack rollback' . Cela signifie que, si les choses tournent mal, nous pouvons revenir à la version précédente, en restaurant notre ancien fichier 'docker-compose.yml' et redéployer à nouveau !
Résumé
Il est devenu très important d'exécuter les mises à jour sans causer de temps d'arrêt. Docker Swarm facilite la gestion et l'exécution des mises à jour. Docker Swarm était et reste un outil d'orchestration de conteneurs extrêmement puissant, même si le développement semble s'être arrêté.
Liens / crédits
Docker - Apply rolling updates to a service
https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update
docker stack deploy in 1.13 doesn't load .env file as docker-compose up does #29133
https://github.com/moby/moby/issues/29133
En savoir plus...
Docker Docker Swarm Docker-compose
Laissez un commentaire
Commentez anonymement ou connectez-vous pour commenter.
Commentaires (1)
Laissez une réponse
Répondez de manière anonyme ou connectez-vous pour répondre.

One cool thing for you to look into is the use of Docker secrets. I think it may simplify some of your problems with env lists. Glad to read your articles. Interested to know if you are enjoying Swarm vs Kubernetes better!
Récent
- Graphique de séries temporelles avec Flask, Bootstrap et Chart.js
- Utiliser IPv6 avec Microk8s
- Utilisation de Ingress pour accéder à RabbitMQ sur un cluster Microk8s
- Galerie vidéo simple avec Flask, Jinja, Bootstrap et JQuery
- Planification de base des tâches avec APScheduler
- Un commutateur de base de données avec HAProxy et HAProxy Runtime API
Les plus consultés
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- Planification de base des tâches avec APScheduler
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes
- Graphique de séries temporelles avec Flask, Bootstrap et Chart.js