Docker Swarm rolling updates
Docker Swarm rolling updates son una manera muy fácil de realizar actualizaciones sin ningún tiempo de inactividad
Hace algún tiempo escribí que sería mejor pasar a una variante Kubernetes y ahora este post es sobre Docker Swarm. Sí, sigo utilizando Docker Swarm porque tengo un proyecto que lo utiliza. Recientemente moví el desarrollo de Docker a Docker Swarm, principalmente porque con Docker Swarm aprendes los fundamentos de la orquestación de contenedores, así que por qué no aprender esto durante el desarrollo.
En este post, veremos rolling updates: una variable de entorno y una imagen. Asumo que ya tienes experiencia práctica con Docker Swarm. Como siempre, estoy haciendo esto en Ubuntu 22.04.
Nuestro proyecto Docker-Compose
Primero creamos un proyecto Docker-Compose. El árbol del proyecto:
.
├── docker-compose.yml
├── .env
├── app
│ └── run.sh
Los ficheros:
# file: .env
COMPOSE_PROJECT_NAME=my-project
LOGGER_LEVEL=DEBUG
Utilizamos la imagen busybox y empezamos con dos réplicas. También ponemos a disposición algunas variables específicas de Docker Swarm :
- X_SERVICE_LABEL_STACK_IMAGE: información sobre la imagen.
- X_TASK_SLOT: el número de la instancia (tarea).
# 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
Utilizamos un script 'run.sh', llamado cuando se inicia el contenedor, que hace dos cosas:
- Inicia un httpd server en segundo plano.
- Genera líneas de registro en un bucle, impresas en 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
Con Docker Swarm, normalmente no creamos redes en el 'docker-compose.yml' sino que utilizamos redes externas, más concretamente redes 'overlay' . Al crear una red de este tipo, también podemos especificar un indicador que permita a los contenedores no gestionados por Docker Swarm conectarse a esta red.
Para crear la red:
docker network create -d overlay --attachable my-project-network
Para ver esta red:
docker network ls
Resultado:
NETWORK ID NAME DRIVER SCOPE
...
qn7qwhpsooty my-project-network overlay swarm
...
Algunos comandos Docker Swarm
Una nota sobre los comandos. Muchas veces utilizamos '--detach=false'. Esto significa que el comando no retorna inmediatamente, sino que lo hace al finalizar. Mientras tanto, se muestra información útil en el terminal.
Vamos a traer nuestro proyecto, la construcción fea se utiliza para pasar las variables de entorno:
env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project
Resultado:
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
Note la advertencia aquí. Esto es inesperado y diferente de Docker y significa que con Docker Swarm, estamos creando un puerto abierto, ¡ten cuidado!
Para eliminar nuestro proyecto, podemos utilizar:
docker stack rm --detach=false my-project
Para mostrar los servicios de la pila:
docker stack services my-project
Resultado:
ID NAME MODE REPLICAS IMAGE PORTS
2oz3yg39zuvx my-project_busybox replicated 2/2 busybox:1.35.0 *:8280->8280/tcp
Para mostrar las tareas del servicio 'mi-proyecto_busybox':
docker service ps my-project_busybox
Resultado:
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
Comprobar los logs, para cada tarea, cada segundo una nueva línea de log:
docker service logs -t -f my-project_busybox
Resultado:
...
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
En el log vemos que ambas tareas se están ejecutando.
Para comprobar el httpd server, ejecutamos en nuestro host:
curl 127.0.0.1:8280
Resultado:
Hello from httpd server instance 1
Si repetimos esto varias veces
cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done
Resultado :
...
Hello from httpd server instance 2
Hello from httpd server instance 1
Hello from httpd server instance 2
Hello from httpd server instance 1
Aquí vemos que el balanceador de carga Docker Swarm alterna las peticiones a ambas instancias.
Por último, inspeccionemos el servicio:
docker service inspect --pretty my-project_busybox
Resultado:
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:
...
Observe el parámetro 'UpdateConfig' 'Parallelism' . El valor 1 significa que el proceso de actualización actualizará primero una única tarea y, una vez completada esta actualización, actualizará una siguiente tarea. El mismo parámetro también está presente en 'RollbackConfig'.
Ampliación mediante la adición de réplicas
Hasta ahora nuestro servicio tiene dos réplicas. Si realizamos un rolling update con sólo una tarea presente, nuestro servicio no estará disponible temporalmente. Esto no es lo que queremos. Con dos tareas, Docker Swarm puede actualizar una tarea y, una vez actualizada ésta, puede actualizar la segunda. Esto significa que nuestro servicio permanece disponible todo el tiempo.
Para añadir más réplicas, por ejemplo 3:
docker service scale my-project_busybox=3
Comprobación de rolling updates
Para comprobar si nuestro servicio está actualizado, podemos consultar el registro del servicio. Esto mostrará qué tareas se están ejecutando y cuándo se reinicia una tarea.
docker service logs -t -f my-project_busybox
Para comprobar que nuestro servicio no se interrumpe durante el proceso de actualización, podemos comprobar el httpd server en un terminal separado, en un bucle "sin fin" como se mencionó anteriormente. No deberíamos ver ninguna interrupción:
cmd="curl 127.0.0.1:8280"; for i in $(seq 1000); do $cmd; sleep 0.5; done
Rolling updates y rollbacks
¿Por qué lo llamamos 'Rolling update'? Porque pasamos una instrucción de actualización con nuevos datos a Docker Swarm, y le pedimos que realice la actualización.
A continuación, hay dos escenarios de actualización del servicio:
- Actualizar una variable de entorno del servicio
- Actualizar la imagen del servicio
El comando de actualización es:
docker service update <parameters> my-project_busybox
El tipo de actualización se especifica mediante los parámetros.
Como una actualización puede fallar, queremos poder volver a la versión anterior. El comando rollback en ambos casos es:
docker service rollback my-project_busybox
1. Rolling update: Variable de entorno
Aquí cambiamos el 'LOGGER_LEVEL' de nuestra aplicación. La 'LOGGER_LEVEL' se carga inicialmente desde el fichero '.env ' y tiene el valor 'DEBUG'. Lo cambiamos a 'WARNING' utilizando el siguiente comando de actualización:
docker service update --env-add LOGGER_LEVEL=WARNING my-project_busybox
Resultado:
my-project_busybox
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service my-project_busybox converged
El registro de servicio muestra lo siguiente durante la actualización:
...
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
Ahora retrocedamos:
docker service rollback my-project_busybox
Resultado:
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
Tras la operación de rollback, el 'LOG_LEVEL' vuelve a ser 'DEBUG', comprueba el log de servicio.
2. Rolling update: Imagen
En otro escenario, tenemos una nueva imagen para nuestra aplicación. Aquí pasamos de busybox:1.35.0 a busybox:1.36.0. El comando de actualización es:
docker service update --image busybox:1.36.0 my-project_busybox
Y, de nuevo, el comando rollback es:
docker service rollback my-project_busybox
¿Qué pasa si algo va mal durante la actualización?
Cometamos un error y actualicemos con una imagen inexistente:
docker service update --image busybox:9.99.0 my-project_busybox
Resultado:
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
Cuando la actualización de una tarea falla, el proceso de actualización finaliza. La otra tarea sigue ejecutándose, lo que significa que nuestro servicio sigue estando disponible. También vemos esto inspeccionando el servicio:
docker service inspect --pretty my-project_busybox
Resultado:
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:
...
Como se mencionó anteriormente, podemos volver al estado anterior a la actualización mediante la emisión de un rollback:
docker service rollback my-project_busybox
El servicio se actualiza con 'docker stack deploy' y 'docker-compose.yml'.
Ahora las cosas se ponen un poco feas. Hasta ahora hemos actualizado nuestros servicios usando 'docker service update', y podíamos volver a una versión anterior ejecutando 'docker service rollback'.
Pero, estamos utilizando un archivo 'docker-compose.yml' aquí. Parece que es posible cambiar el archivo 'docker-compose.yml' y volver a desplegar de nuevo.
Veamos qué ocurre. En primer lugar, editamos la variable de entorno en el archivo ".env " y la etiqueta de imagen en el archivo "docker-compose.yml". A continuación, volvemos a ejecutar el comando deploy:
env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --detach=false -c docker-compose.yml my-project
Resultado:
Updating service my-project_busybox (id: mi27j7jjsz146y4wqqre439io)
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service mi27j7jjsz146y4wqqre439io converged
Observa que el resultado ahora menciona 'Updating service', mientras que la primera vez mencionaba 'Creating service'.
En cualquier caso, esto significa que Docker Swarm está realizando una actualización (sin tiempo de inactividad) para cada servicio en el archivo 'docker-compose.yml', de la misma manera que actualizamos servicios individuales utilizando 'docker service update'. Esto está muy bien.
Pero las cosas pueden ir mal y no existe el comando 'docker stack rollback' . Esto significa que, si las cosas van mal, podemos volver a la versión anterior, restaurando nuestro archivo anterior 'docker-compose.yml' y volver a desplegar de nuevo.
Resumen
Ejecutar actualizaciones sin causar tiempos de inactividad se ha convertido en algo muy importante. Docker Swarm hace que la gestión y ejecución de actualizaciones sea muy sencilla. Docker Swarm era y sigue siendo una herramienta de orquestación de contenedores extremadamente potente, a pesar de que el desarrollo parece haberse estancado.
Enlaces / créditos
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
Leer más
Docker Docker Swarm Docker-compose
Deje un comentario
Comente de forma anónima o inicie sesión para comentar.
Comentarios (1)
Deje una respuesta.
Responda de forma anónima o inicie sesión para responder.
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!
Recientes
- Gráfico de series temporales con Flask, Bootstrap y Chart.js
- Utilización de IPv6 con Microk8s
- Uso de Ingress para acceder a RabbitMQ en un clúster Microk8s
- Galería de vídeo simple con Flask, Jinja, Bootstrap y JQuery
- Programación básica de trabajos con APScheduler
- Un conmutador de base de datos con HAProxy y el HAProxy Runtime API
Más vistos
- Programación básica de trabajos con APScheduler
- Evitar el envío de mensajes duplicados a un sistema remoto
- LSTM optimización multipaso hyperparameter con Keras Tuner
- Documentación de un Flask RESTful API con OpenAPI (Swagger) utilizando APISpec
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Utilización de IPv6 con Microk8s