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

Docker Swarm rolling updates

Docker Swarm rolling updates zijn een zeer eenvoudige manier om updates uit te voeren zonder enige downtime

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

Enige tijd geleden schreef ik dat het het beste zou zijn om over te stappen op een Kubernetes variant en nu gaat deze post over Docker Swarm. Ja, ik gebruik nog steeds Docker Swarm omdat ik een project heb dat deze variant gebruikt. Onlangs heb ik de ontwikkeling verplaatst van Docker naar Docker Swarm, voornamelijk omdat je met Docker Swarm de basis van container orkestratie leert, dus waarom zou je dit niet leren tijdens de ontwikkeling.

In deze post kijken we naar rolling updates: een omgevingsvariabele en een image. Ik ga ervan uit dat je al enige praktische ervaring hebt met Docker Swarm. Zoals altijd doe ik dit op Ubuntu 22.04.

Ons Docker-Compose project

We maken eerst een Docker-Compose project. De projectboom:

.
├── docker-compose.yml
├── .env
├── app
│   └── run.sh

De bestanden:

# file: .env
COMPOSE_PROJECT_NAME=my-project
LOGGER_LEVEL=DEBUG

We gebruiken de busybox image en beginnen met twee replica's. We maken ook enkele Docker Swarm specifieke variabelen beschikbaar:

  • X_SERVICE_LABEL_STACK_IMAGE: informatie over de image.
  • X_TASK_SLOT: het nummer van de (taak)instantie.
# 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

We gebruiken een script 'run.sh', dat wordt aangeroepen wanneer de container start, en dat twee dingen doet:

  • Het start een httpd server op de achtergrond.
  • Het genereert logregels in een lus, afgedrukt naar 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

Met Docker Swarm maken we meestal geen netwerken in de 'docker-compose.yml' maar gebruiken we externe netwerken, meer bepaald 'overlay' netwerken. Bij het maken van zo'n netwerk kunnen we ook een vlag opgeven waarmee niet-Docker Swarm beheerde containers verbinding kunnen maken met dit netwerk.

Om het netwerk aan te maken:

docker network create -d overlay --attachable my-project-network

Om dit netwerk te zien:

docker network ls

Resultaat:

NETWORK ID     NAME                                                DRIVER    SCOPE
...
qn7qwhpsooty   my-project-network                                  overlay   swarm
...

Enkele Docker Swarm commando's

Een opmerking over de commando's. Vaak gebruiken we '--detach=false'. Dit betekent dat de opdracht niet onmiddellijk terugkomt, maar pas na voltooiing. In de tussentijd wordt nuttige informatie getoond in de terminal.

Laten we ons project ophalen, de lelijke constructie wordt gebruikt om de omgevingsvariabelen door te geven:

env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project

Result:

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 

Let op de waarschuwing hier. Dit is onverwacht en anders dan Docker en betekent dat we met Docker Swarm een open poort creëren, wees voorzichtig!

Om ons project te verwijderen, kunnen we

docker stack rm --detach=false my-project

Om de stack services te tonen:

docker stack services my-project

Resultaat:

ID             NAME                 MODE         REPLICAS   IMAGE            PORTS
2oz3yg39zuvx   my-project_busybox   replicated   2/2        busybox:1.35.0   *:8280->8280/tcp

Om de taken van de service 'my-project_busybox' te tonen:

docker service ps my-project_busybox

Resultaat:

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  

Bekijk de logs, voor elke taak, elke seconde een nieuwe logregel:

docker service logs -t -f my-project_busybox

Resultaat:

...
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

In de log zien we dat beide taken draaien.

Om httpd server te controleren, draaien we op onze host:

curl 127.0.0.1:8280

Resultaat:

Hello from httpd server instance 1

Als we dit een paar keer herhalen:

cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done

Resultaat :

...
Hello from httpd server instance 2
Hello from httpd server instance 1
Hello from httpd server instance 2
Hello from httpd server instance 1

Hier zien we dat de Docker Swarm load balancer verzoeken afwisselt naar beide instanties.

Laten we tot slot de service inspecteren:

docker service inspect --pretty my-project_busybox

Resultaat:

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:
...

Let op de parameter 'UpdateConfig' 'Parallelism' . De waarde 1 betekent dat het updateproces eerst een enkele taak zal bijwerken en zodra deze update is voltooid een volgende taak zal bijwerken. Dezelfde parameter is ook aanwezig in 'RollbackConfig'.

Schalen door replicas toe te voegen

Tot nu toe heeft onze service twee replica's. Als we een rolling update uitvoeren met slechts één taak aanwezig, dan zal onze dienst tijdelijk niet beschikbaar zijn. Dat is niet wat we willen. Met twee taken kan Docker Swarm één taak bijwerken en zodra die taak is bijgewerkt, kan het de tweede taak bijwerken. Dit betekent dat onze service de hele tijd beschikbaar blijft.

Om meer replicas toe te voegen, bijvoorbeeld 3:

docker service scale my-project_busybox=3

rolling updates controleren

Om te controleren of onze service is bijgewerkt, kunnen we het servicelogboek controleren. Dit laat zien welke taken worden uitgevoerd en wanneer een taak opnieuw is gestart.

docker service logs -t -f my-project_busybox

Om te controleren of onze service niet wordt onderbroken tijdens het updateproces, kunnen we de httpd server controleren in een aparte terminal, in een "eindeloze" lus zoals eerder genoemd. We zouden geen onderbrekingen moeten zien:

cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done

Rolling updates en rollbacks

Waarom noemen we het een 'Rolling update'? Omdat we een update-instructie met nieuwe gegevens doorgeven aan Docker Swarm en deze vragen de update uit te voeren.

Hieronder staan twee scenario's voor het bijwerken van services:

  1. Een omgevingsvariabele van de service bijwerken
  2. De afbeelding van de service bijwerken

De updateopdracht is:

docker service update <parameters> my-project_busybox

Het type van de update wordt gespecificeerd door de parameters.

Omdat een update kan mislukken, willen we terug kunnen gaan naar de vorige versie. Het rollback commando in beide gevallen is:

docker service rollback my-project_busybox

1. Rolling update: Omgevingsvariabele

Hier wijzigen we de 'LOGGER_LEVEL' van onze applicatie. De 'LOGGER_LEVEL' wordt in eerste instantie geladen vanuit het '.env bestand' en heeft de waarde 'DEBUG'. We veranderen het in 'WARNING' met het volgende update-commando:

docker service update --env-add LOGGER_LEVEL=WARNING my-project_busybox

Resultaat:

my-project_busybox
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service my-project_busybox converged 

Het servicelogboek toont het volgende tijdens de update:

...
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

Laten we nu terugdraaien:

docker service rollback my-project_busybox

Resultaat:

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 

Na de rollback-bewerking is de 'LOG_LEVEL' terug op 'DEBUG', controleer het servicelogboek.

2. Rolling update: Afbeelding

In een ander scenario hebben we een nieuwe image voor onze applicatie. Hier gaan we van busybox:1.35.0 naar busybox:1.36.0. Het update commando is:

docker service update --image busybox:1.36.0 my-project_busybox

En, nogmaals, het rollback commando is:

docker service rollback my-project_busybox

Wat als er iets misgaat tijdens de update?

Laten we een fout maken en updaten met een niet-bestaande image:

docker service update --image busybox:9.99.0 my-project_busybox

Resultaat:

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

Wanneer de update van een taak mislukt, wordt het updateproces beëindigd. De andere taak blijft draaien, wat betekent dat onze service nog steeds beschikbaar is. We zien dit ook door de service te inspecteren:

docker service inspect --pretty my-project_busybox

Resultaat:

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:
...

Zoals eerder vermeld, kunnen we teruggaan naar de toestand vóór de update door een rollback uit te voeren:

docker service rollback my-project_busybox

De service wordt bijgewerkt met 'docker stack deploy' en 'docker-compose.yml'.

Nu worden de dingen een beetje lelijk. Tot nu toe hebben we onze services bijgewerkt met 'docker service update', en we konden teruggaan naar een vorige versie door 'docker service rollback' uit te voeren.

Maar we gebruiken hier een 'docker-compose.yml' bestand. Het lijkt mogelijk om het bestand 'docker-compose.yml' te wijzigen en opnieuw te implementeren.

Laten we eens kijken wat er gebeurt. Eerst wijzigen we de omgevingsvariabele in het '.env bestand' en de image tag in het 'docker-compose.yml' bestand. Vervolgens voeren we de opdracht deploy opnieuw uit:

env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project

Resultaat:

Updating service my-project_busybox (id: mi27j7jjsz146y4wqqre439io)
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service mi27j7jjsz146y4wqqre439io converged 

Merk op dat het resultaat nu 'Updating service' vermeldt, terwijl het de eerste keer 'Creating service' vermeldde.

Hoe dan ook, dit betekent dat Docker Swarm een update uitvoert (zonder downtime) voor elke service in het bestand 'docker-compose.yml', op dezelfde manier waarop we individuele services bijwerken met 'docker service update'. Dit is geweldig!

Maar er kunnen dingen fout gaan en er is geen 'docker stack rollback' commando. Dit betekent dat als het fout gaat, we terug kunnen gaan naar de vorige versie door ons vorige 'docker-compose.yml' bestand terug te zetten en opnieuw te implementeren!

Samenvatting

Het uitvoeren van updates zonder downtime te veroorzaken is erg belangrijk geworden. Docker Swarm maakt het beheren en uitvoeren van updates een fluitje van een cent. Docker Swarm was en blijft een zeer krachtige container orkestratie tool, ook al lijkt de ontwikkeling te zijn stilgevallen.

Links / credits

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

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen (1)

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.

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!