angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Docker Swarm rolling updates

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

7 juillet 2024
post main image
https://unsplash.com/@coinstash_au

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 :

  1. Mise à jour d'une variable d'environnement du service
  2. 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

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.

avatar

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!