En articles anteriors hem desplegat aplicacions amb Docker Swarm usant l’estratègia per defecte: el rolling update. En aquest article explorarem amb més profunditat aquesta estratègia i n’introduirem una altra de molt popular: el desplegament Blue-Green. També veurem com fer rollback quan un desplegament falla.
Rolling updates #
El rolling update és l’estratègia per defecte de Docker Swarm. Consisteix en actualitzar les tasques d’un servei de forma progressiva, substituint les antigues per les noves de manera gradual, sense interrompre el servei.
A l’article Actualització i manteniment de serveis vàrem veure una configuració bàsica:
services:
web:
image: myuser/myapp:${VERSION:-latest}
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-firstAquesta configuració indica que:
parallelism: 1: Actualitza una tasca a la vegada.delay: 10s: Espera 10 segons entre cada actualització.order: start-first: Crea la tasca nova abans d’aturar l’antiga.
Paràmetres avançats #
Docker Swarm ofereix més paràmetres per controlar el comportament dels rolling updates:
| Paràmetre | Descripció | Valor per defecte |
|---|---|---|
parallelism |
Nombre de tasques a actualitzar simultàniament | 1 |
delay |
Temps d’espera entre actualitzacions | 0s |
order |
Ordre d’operacions: stop-first o start-first |
stop-first |
failure_action |
Acció si falla: pause, continue o rollback |
pause |
monitor |
Temps per monitoritzar l’estat després de l’actualització | 5s |
max_failure_ratio |
Percentatge màxim de fallades tolerat | 0 |
Exemple complet:
services:
web:
image: myuser/myapp:${VERSION:-latest}
deploy:
replicas: 6
update_config:
parallelism: 2
delay: 10s
order: start-first
failure_action: rollback
monitor: 30s
max_failure_ratio: 0.2Aquesta configuració:
- Actualitza 2 tasques simultàniament (
parallelism: 2). - Espera 10 segons entre cada lot de 2 tasques.
- Crea les tasques noves abans d’aturar les antigues.
- Monitoritza cada tasca durant 30 segons després d’iniciar-la.
- Si més del 20% de les tasques fallen, fa rollback automàticament.
- Si una tasca falla però no se supera el 20%, continua amb les següents.
Ordre d’operacions #
La diferència entre stop-first i start-first és significativa. Amb stop-first, valor per defecte, el procés d’actualització es comportarà de la següent forma en el temps:
Temps →
Tasca 1: [████ antiga ████] [████ nova ████]
Tasca 2: [████ antiga ████] [████ nova ████]
Tasca 3: [████ antiga ████] [████ nova ████]
↑ ↑
Manco capacitat durant la transicióÉs a dir, Docker Swarm:
- Primer atura la tasca antiga (envia
SIGTERM, espera, enviaSIGKILLsi cal). - Programa la nova tasca a un node.
- Baixa la imatge si no existeix al node.
- Inicia la nova tasca.
- Espera a que el contenidor estigui en estat
running, o passi elhealthchecksi n’hi ha. - Aplica el valor del paràmetre
delay. - Passa a la següent tasca.
Durant la transició, hi ha manco capacitat disponible. Aquesta estratègia és útil quan els recursos són limitats.
En canvi, amb start-first, la cronologia d’esdeveniments seria la següent:
Temps →
Tasca 1: [████ antiga ████]
[████ nova ████████████████████████████]
Tasca 2: [████ antiga ████]
[████ nova ████████████████████████████]
Tasca 3: [████ antiga ████]
[████ nova ████████████████████████████]
↑
Més capacitat durant la transició- Primer inicia la tasca nova.
- Espera a que la tasca estigui llesta (estat
runningo passihealthcheck, si escau). - Atura la tasca antiga.
- Aplica el valor del paràmetre
delay. - Passa a la següent tasca.
Durant la transició, hi ha més capacitat disponible. Per tant, requereix recursos addicionals temporalment. Aquesta estratègia és útil per a serveis crítics on no es pot perdre capacitat.
Monitorització #
Per seguir el progrés d’un rolling update podem combinar les comandes watch i docker. Per veure l’estat de les tasques en temps quasi real:
watch docker stack ps myappO, si tenim interès en un servei concret:
watch docker service ps myapp_webLa sortida mostra l’estat de cada tasca:
ID NAME IMAGE NODE STATE
abc123 myapp_web.1 myuser/myapp:1.2.0 node1 Running
def456 myapp_web.2 myuser/myapp:1.2.0 node2 Running
ghi789 myapp_web.3 myuser/myapp:1.1.0 node3 Running
jkl012 \_myapp_web.3 myuser/myapp:1.1.0 node3 ShutdownRollback #
Quan un desplegament falla o introdueix errors, cal tornar a la versió anterior. Docker Swarm ofereix dues maneres de fer-ho.
- Rollback automàtic.
- Rollback manual.
Si hem configurat failure_action: rollback, Swarm farà rollback automàticament quan detecti fallades:
deploy:
update_config:
failure_action: rollback
monitor: 30s
max_failure_ratio: 0.2Amb aquesta configuració, si més del 20% de les tasques fallen dins els primers 30 segons, Swarm reverteix automàticament a la versió anterior.
En canvi, per fer rollback manualment usam la comanda docker service rollback:
docker service rollback myapp_webAquesta comanda reverteix el servei a la configuració anterior, incloent la imatge, variables d’entorn i qualsevol altre paràmetre que s’hagi canviat.
Per veure l’historial de versions d’un servei podem usar la següent ordre:
docker service inspect --pretty myapp_webCompatibilitat #
Perquè el rollback sigui viable, l’aplicació s’ha de dissenyar tenint en compte aquesta possibilitat. Això és especialment crític quan hi ha canvis de model a la base de dades.
Migracions compatibles amb rollback:
- Afegir columnes noves amb valors per defecte.
- Crear taules noves.
- Afegir índexs.
Migracions problemàtiques:
- Eliminar columnes o taules (la versió anterior les necessita).
- Renombrar columnes (trenca ambdues versions).
- Canviar tipus de dades de forma incompatible.
Una estratègia habitual és separar els desplegaments en dues fases:
- Desplegament 1: Afegir els nous camps/taules sense eliminar els antics. El codi suporta ambdós esquemes.
- Desplegament 2: Un cop confirmada l’estabilitat, eliminar els camps/taules antics.
Aquesta tècnica es coneix com a expand and contract o parallel change.
Configuració #
De la mateixa manera que hem fet amb update_config, podem configurar el comportament del rollback amb rollback_config:
services:
web:
image: myuser/myapp:${VERSION:-latest}
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
failure_action: rollback
rollback_config:
parallelism: 1
delay: 5s
order: start-firstEl rollback_config accepta els mateixos paràmetres que update_config.
| Paràmetre | Descripció | Valor per defecte |
|---|---|---|
parallelism |
Nombre de tasques a revertir simultàniament | 1 |
delay |
Temps d’espera entre reversions | 0s |
order |
Ordre d’operacions: stop-first o start-first |
stop-first |
monitor |
Temps per monitoritzar l’estat després de la reversió | 5s |
max_failure_ratio |
Percentatge màxim de fallades tolerat | 0 |
A l’exemple anterior, el rollback es fa més ràpidament (5 segons de delay en lloc de 10) per restaurar el servei el més aviat possible.
Redesplegament #
Una alternativa al rollback natiu passa per, simplement, tornar a desplegar una versió anterior de l’aplicació:
VERSION=1.1.0 docker stack deploy -c docker-stack.yml myappAquesta aproximació és més explícita i queda registrada com un desplegament normal. És útil quan:
- Volem botar més d’una versió enrere.
- El rollback natiu no funciona per algun motiu.
- Preferim tenir un registre clar de totes les versions desplegades.
Desplegament Blue-Green #
El desplegament Blue-Green és una estratègia que manté dues versions de l’aplicació en execució simultàniament: la versió activa (e.g., la blava) i la nova versió (e.g., la verda). El tràfic es redirigeix de la blava a la verda quan la nova versió està llesta i verificada.
Avantatges:
- El canvi de versió és instantani.
- Si hi ha problemes, es redirigeix el tràfic de nou a la versió blava (rollback immediat).
- Es pot provar la versió verda abans de redirigir el tràfic.
Inconvenients:
- Cal tenir capacitat per executar dues versions simultàniament (duplicació de recursos).
- Augment de la complexitat, car requereix de la gestió del tràfic (Traefik, en el nostre cas).
- Les migracions de base de dades poden ser problemàtiques.
Aproximacions #
Hi ha dues aproximacions principals per implementar l’estratègia de desplegament Blue-Green amb Docker Swarm:
Etiquetes sobre un únic stack
En aquest context, usarem un sol stack amb dos serveis (web-blue i web-green). Per redirigir el tràfic de la versió blava a la verda es canvien les etiquetes dels serveis. El nostre servei Traefik dirigeix el tràfic en funció dels valors que anem configurant.
| Aspecte | Descripció |
|---|---|
| Recursos | Més eficient, comparteixen xarxa i volums |
| Canvi de tràfic | Instantani, via etiquetes |
| Gestió | Un sol fitxer stack |
Dos stacks separats
En aquest cas, definirem dos stacks completament independents (myapp-blue i myapp-green). Per redirigir el tràfic de la versió blava a la verda es canvia la configuració del proxy extern o de la zona de DNS.
| Aspecte | Descripció |
|---|---|
| Recursos | Duplicats completament |
| Canvi de tràfic | Segons el mètode (DNS pot ser lent) |
| Gestió | Dos fitxers stack |
La segona aproximació és més comuna quan s’usa un balancejador extern al clúster de Docker Swarm (e.g., HAProxy, un servei oferit pel propi proveïdor d’hospedatge, etc.) o quan es requereix aïllament complet entre versions, per exemple, amb bases de dades separades per a cada entorn.
En aquest article implementarem la primera aproximació, usant Traefik.
Amb etiquetes #
Per a aquesta aproximació ens cal definir dos serveis per a l’aplicació web, un per a cada color:
# docker-stack.yml
services:
traefik:
image: traefik:v3.6
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
configs:
- source: traefik_config
target: /etc/traefik/traefik.yml
networks:
- public
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
web-blue:
image: myuser/myapp:${VERSION_BLUE:-latest}
environment:
- DEBUG=false
- ALLOWED_HOSTS=domini.com,www.domini.com
# ... més variables
secrets:
- source: secret_key
target: /run/secrets/SECRET_KEY
# ... més secrets
networks:
- public
- backend
deploy:
replicas: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`domini.com`)"
- "traefik.http.routers.web.entrypoints=https"
- "traefik.http.services.web.loadbalancer.server.port=8080"
web-green:
image: myuser/myapp:${VERSION_GREEN:-latest}
environment:
- DEBUG=false
- ALLOWED_HOSTS=domini.com,www.domini.com
# ... més variables
secrets:
- source: secret_key
target: /run/secrets/SECRET_KEY
# ... més secrets
networks:
- public
- backend
deploy:
replicas: 0 # Inicialment sense rèpliques
labels:
- "traefik.enable=false" # Inicialment desactivat
- "traefik.http.routers.web.rule=Host(`domini.com`)"
- "traefik.http.routers.web.entrypoints=https"
- "traefik.http.services.web.loadbalancer.server.port=8080"
# ... resta de serveis (postgres, redis, worker, beat)
networks:
public:
driver: overlay
backend:
driver: overlay
internal: true
configs:
traefik_config:
file: docker/traefik/traefik.prod.yml
secrets:
secret_key:
external: true
postgres_user:
external: true
postgres_password:
external: truePer tant, inicialment tenim:
- El servei
web-blueté 3 rèpliques i rep el tràfic. - El servei
web-greenté 0 rèpliques i està desactivat a Traefik.
L’etiqueta
traefik.enable=falseal serveiweb-greenés redundant, car amb 0 rèpliques no hi ha cap contenidor executant-se, per tant Traefik no té res a descobrir. Aquesta etiqueta només seria necessària si el servei tengués rèpliques actives però volguéssim que Traefik l’ignorés.
En aquest context, el procés de desplegament d’una nova versió amb aquesta estratègia Blue-Green segueix aquests passos:
-
Desplegar
web-greenamb la nova versió.VERSION_BLUE=1.1.0 VERSION_GREEN=1.2.0 \ docker stack deploy -c docker-stack.yml myapp -
Augmentar les rèpliques de
web-green.docker service scale myapp_web-green=3 -
Verificar l’estat dels servei de la versió
web-green.docker service ps myapp_web-green -
Verificar la nova funcionalitat. Abans de redirigir el tràfic, verificam que la nova versió funciona correctament accedint directament al servei intern o habilitant temporalment un subdomini o ruta de proves. Com a part d’aquestes proves revisarem els logs d’aplicació:
docker service logs myapp_web-green -
Un cop verificat, actualitzem les etiquetes per redirigir el tràfic:
# Activar green a Traefik docker service update \ --label-add "traefik.enable=true" \ myapp_web-green # Desactivar blue a Traefik docker service update \ --label-add "traefik.enable=false" \ myapp_web-blue -
Comprovam que el tràfic va a Green (e.g., usant
curl) i verificam l’estat dels serveis:curl --head https://domini.com/version docker stack services myapp -
Un cop confirmem que Green funciona correctament, podem reduir el número de rèpliques a Blue:
docker service scale myapp_web-blue=0
L’endpoint
/versionde l’aplicació web és molt útil en una aplicació web, com també ho són/healthi/ping.
Rollback #
Si detectem problemes amb la versió Green, el rollback és immediat. En primer lloc, reactivam Blue:
docker service update \
--label-add "traefik.enable=true" \
myapp_web-blue
docker service scale myapp_web-blue=3I, acte seguit, desactivam Green:
docker service update \
--label-add "traefik.enable=false" \
myapp_web-green
docker service scale myapp_web-green=0El canvi és quasi instantani perquè blue encara existeix i té la configuració preparada.
Scripting #
El desplegament, com hem pogut veure abans, té dues parts ben diferenciades. La primera desplega la nova versió al servei inactiu i escala les rèpliques, mentre que la segona canvia el tràfic al servei inactiu i redueix l’antic.
Per automatitzar el procés, podem crear sengles scripts, de forma que puguem verificar manualment que tot està bé abans de fer el canvi de tràfic, obtenint així un procés més determinista.
Primer script
#!/bin/bash
#
# deploy/deploy-blue-green.sh
#
# Desplega una nova versió al servei inactiu (blue o green)
#
set -euo pipefail
MANAGER_HOST="[email protected]"
NEW_VERSION="${1:-}"
if [[ -z "$NEW_VERSION" ]]; then
echo "Ús: $0 <versió>"
echo "Exemple: $0 1.2.0"
exit 1
fi
export DOCKER_HOST=ssh://$MANAGER_HOST
# Detectar quin color està actiu
BLUE_REPLICAS=$(
docker service inspect myapp_web-blue \
--format '{{.Spec.Mode.Replicated.Replicas}}' \
2>/dev/null || echo "0" \
)
if [[ "$BLUE_REPLICAS" -gt 0 ]]; then
INACTIVE="green"
else
INACTIVE="blue"
fi
echo "Desplegant versió $NEW_VERSION a $INACTIVE..."
# Actualitzar la imatge i escalar
docker service update --image myuser/myapp:$NEW_VERSION myapp_web-$INACTIVE
docker service scale myapp_web-$INACTIVE=3
echo ""
echo "Verifica que el servei està llest:"
echo "watch docker service ps myapp_web-$INACTIVE"Segon script
#!/bin/bash
#
# deploy/switch-blue-green.sh
#
# Canvia el tràfic entre blue i green
#
set -euo pipefail
MANAGER_HOST="[email protected]"
export DOCKER_HOST=ssh://$MANAGER_HOST
# Detectar quin color està actiu
BLUE_REPLICAS=$(
docker service inspect myapp_web-blue \
--format '{{.Spec.Mode.Replicated.Replicas}}' \
2>/dev/null || echo "0"
)
GREEN_REPLICAS=$(
docker service inspect myapp_web-green \
--format '{{.Spec.Mode.Replicated.Replicas}}' \
2>/dev/null || echo "0"
)
if [[ "$BLUE_REPLICAS" -gt 0 && "$GREEN_REPLICAS" -gt 0 ]]; then
# Ambdós actius: activar el que té traefik.enable=false
BLUE_ENABLED=$(
docker service inspect myapp_web-blue \
--format '{{index .Spec.Labels "traefik.enable"}}'
)
if [[ "$BLUE_ENABLED" == "true" ]]; then
ACTIVE="blue"
INACTIVE="green"
else
ACTIVE="green"
INACTIVE="blue"
fi
elif [[ "$BLUE_REPLICAS" -gt 0 ]]; then
ACTIVE="blue"
INACTIVE="green"
else
ACTIVE="green"
INACTIVE="blue"
fi
echo "Canviant tràfic de $ACTIVE a $INACTIVE..."
# Activar el servei inactiu
docker service update --label-add "traefik.enable=true" myapp_web-$INACTIVE
# Desactivar el servei actiu
docker service update --label-add "traefik.enable=false" myapp_web-$ACTIVE
# Reduir el servei antic
docker service scale myapp_web-$ACTIVE=0
echo "Tràfic redirigit a $INACTIVE"Abans d’usar aquests scripts, cal fer-los executables:
chmod +x deploy/deploy-blue-green.sh
chmod +x deploy/switch-blue-green.shA l’hora de fer-ne ús, només necessitam la nova versió com a paràmetre del primer script:
-
Desplegar la nova versió al servei inactiu i escalar les rèpliques:
deploy/deploy-blue-green.sh 1.2.0 -
Verificar que el servei està llest:
watch docker service ps myapp_web-<versió-inactiva> -
Canviar el tràfic al servei inactiu i reduir l’antic:
deploy/switch-blue-green.sh
Comparativa d’estratègies #
| Aspecte | Rolling update | Blue-Green |
|---|---|---|
| Temps de desplegament | Gradual (minuts) | Instantani (segons) |
| Recursos addicionals | Mínims | Duplicats durant transició |
| Complexitat | Baixa | Mitjana |
| Rollback | Automàtic o manual | Instantani |
| Verificació prèvia | No | Sí |
| Risc | Progressiu | Tot o res |
Quan usar cada estratègia:
- Rolling Update: Per a la majoria de desplegaments rutinaris. És l’opció per defecte i funciona bé per a actualitzacions incrementals.
- Blue-Green: Per a desplegaments crítics on el zero downtime és essencial, o quan es vol verificar la nova versió abans de redirigir el tràfic.
Exercicis pràctics #
Es proposen tres exercicis pràctics per facilitar l’aprenentatge progressiu.
Exercici 1 #
Rolling update avançat
Configura un rolling update amb paràmetres avançats i observa el comportament del rollback automàtic.
Tasques:
-
Modifica el fitxer
docker-stack.ymlper afegir una configuració avançada de rolling update al serveiweb:deploy: replicas: 4 update_config: parallelism: 2 delay: 15s order: start-first failure_action: rollback monitor: 30s max_failure_ratio: 0.3 rollback_config: parallelism: 2 delay: 5s order: start-first -
Desplega una nova versió i observa el progrés del rolling update:
bash VERSION=1.2.0 docker stack deploy -c docker-stack.yml myapp watch docker service ps myapp_web -
Simula una fallada desplegant una imatge que no existeix:
VERSION=inexistent docker stack deploy -c docker-stack.yml myapp -
Observa com Docker Swarm detecta la fallada i fa rollback automàticament.
-
Verifica que el servei ha tornat a la versió anterior:
docker service inspect --pretty myapp_web
Exercici 2 #
Rollback manual
Practica el rollback manual usant tant la comanda nativa com el redesplegament d’una versió anterior.
Tasques:
-
Desplega dues versions consecutives de l’aplicació:
VERSION=1.1.0 docker stack deploy -c docker-stack.yml myapp # Espera que estigui llest VERSION=1.2.0 docker stack deploy -c docker-stack.yml myapp -
Executa un rollback manual amb la comanda nativa:
docker service rollback myapp_web -
Verifica que el servei ha tornat a la versió 1.1.0:
docker service inspect myapp_web \ --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' -
Torna a desplegar la versió 1.2.0:
VERSION=1.2.0 docker stack deploy -c docker-stack.yml myapp -
Ara, fes rollback usant el mètode de redesplegament:
VERSION=1.1.0 docker stack deploy -c docker-stack.yml myapp -
Compara ambdós mètodes: quines diferències observes en el comportament i en la informació que mostra
docker service inspect?
Exercici 3 #
Desplegament Blue-Green
Implementa un desplegament Blue-Green complet, incloent el canvi de tràfic i el rollback.
Tasques:
-
Modifica el fitxer
docker-stack.ymlper tenir els serveisweb-blueiweb-greensegons l’estructura presentada a l’article. -
Desplega l’stack inicial amb
web-blueactiu:VERSION_BLUE=1.1.0 VERSION_GREEN=1.1.0 \ docker stack deploy -c docker-stack.yml myapp docker service scale myapp_web-blue=3 -
Verifica que només
web-bluerep tràfic:docker stack services myapp curl --head https://domini.com -
Desplega la nova versió a
web-green:docker service update --image myuser/myapp:1.2.0 myapp_web-green docker service scale myapp_web-green=3 -
Verifica que
web-greenestà llest:watch docker service ps myapp_web-green docker service logs myapp_web-green -
Redirigeix el tràfic a
web-green:docker service update \ --label-add "traefik.enable=true" myapp_web-green docker service update \ --label-add "traefik.enable=false" myapp_web-blue -
Verifica el canvi i redueix
web-blue:curl --head https://domini.com docker service scale myapp_web-blue=0 -
Practica el rollback tornant a
web-blue:docker service scale myapp_web-blue=3 docker service update \ --label-add "traefik.enable=true" myapp_web-blue docker service update \ --label-add "traefik.enable=false" myapp_web-green docker service scale myapp_web-green=0 -
Finalment, copia els scripts
deploy-blue-green.shiswitch-blue-green.shal directorideploy/del teu projecte, fes-los executables i repeteix el procés usant els scripts.