Docker Swarm - Parte 3
Disponibilidad de los nodos
¿Qué sucede cuando alguno de nuestros nodos queda inactivo? ¿A dónde van los contenedores que están allí?
Para esto vamos a utilizar nuevamente el servicio de ping que utilizamos para ser escalados y ya vamos a escalar a 5.
vagrant@master:~$ docker service create --name pinger --replicas 5 registry.docker-dca.example:5000/alpine ping google.com
# ...
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
dd0grakiu2ow pinger replicated 5/5 registry.docker-dca.example:5000/alpine:latest
vagrant@master:~$
vagrant@master:~$ docker service ps pinger --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
slkn7snmuo4l pinger.1 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 2 minutes ago
rwk8i4r1wsvb pinger.2 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 2 minutes ago
6qhufz7xf5kf pinger.3 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 2 minutes ago
697qkomicb6w pinger.4 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 2 minutes ago
puca1zg6l61n pinger.5 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 2 minutes ago
Ahora vamos a imaginar que el worker2 tiene un problema y se detuvo. El propio swarm va a reubicar el contenedor a otros nodos. Vamos a probarlo.
❯ vagrant halt worker2
==> worker2: Attempting graceful shutdown of VM...
Ahora vamos a verificar
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
dd0grakiu2ow pinger replicated 7/5 registry.docker-dca.example:5000/alpine:latest
vagrant@master:~$ docker service ps pinger
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
slkn7snmuo4l pinger.1 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 5 minutes ago
uqbq7n4x61fd \_ pinger.1 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 5 minutes ago "No such image: registry.docke…"
xw7wspjvl6c2 \_ pinger.1 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 6 minutes ago "No such image: registry.docke…"
ivgkwsejvm1n \_ pinger.1 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 6 minutes ago "No such image: registry.docke…"
rwk8i4r1wsvb pinger.2 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 6 minutes ago
neu2uugr2cag pinger.3 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 17 seconds ago
rfy2lisfrz06 \_ pinger.3 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 28 seconds ago "No such image: registry.docke…"
gag78grl9krw \_ pinger.3 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 33 seconds ago "No such image: registry.docke…"
476xl49hc75k \_ pinger.3 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 38 seconds ago "No such image: registry.docke…"
6qhufz7xf5kf \_ pinger.3 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Shutdown Running 5 minutes ago
697qkomicb6w pinger.4 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 6 minutes ago
h5ogmu03mth5 pinger.5 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 17 seconds ago
pnmp0cf22mb4 \_ pinger.5 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 28 seconds ago "No such image: registry.docke…"
p4cmg1dntadm \_ pinger.5 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 33 seconds ago "No such image: registry.docke…"
qmb5ch04n1si \_ pinger.5 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 38 seconds ago "No such image: registry.docke…"
puca1zg6l61n \_ pinger.5 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Shutdown Running 6 minutes ago
Observando lo que sucedió, simplemente ahora está con 7/5 porque levantó dos nuevos, pero el anterior permaneció allí en el historial. Vamos a levantar nuevamente el worker2.
❯ vagrant up worker2
Y verificando
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
dd0grakiu2ow pinger replicated 5/5 registry.docker-dca.example:5000/alpine:latest
vagrant@master:~$
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
1ppd0q97wdcx pinger.1 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 4 minutes ago
slkn7snmuo4l \_ pinger.1 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Shutdown Shutdown 4 minutes ago
uqbq7n4x61fd \_ pinger.1 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 12 minutes ago "No such image: registry.docke…"
xw7wspjvl6c2 \_ pinger.1 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 12 minutes ago "No such image: registry.docke…"
ivgkwsejvm1n \_ pinger.1 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 12 minutes ago "No such image: registry.docke…"
rwk8i4r1wsvb pinger.2 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 12 minutes ago
neu2uugr2cag pinger.3 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 7 minutes ago
rfy2lisfrz06 \_ pinger.3 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 7 minutes ago "No such image: registry.docke…"
gag78grl9krw \_ pinger.3 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 7 minutes ago "No such image: registry.docke…"
476xl49hc75k \_ pinger.3 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 7 minutes ago "No such image: registry.docke…"
6qhufz7xf5kf \_ pinger.3 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Shutdown Shutdown about a minute ago
92yxi5wjb3xn pinger.4 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 4 minutes ago
697qkomicb6w \_ pinger.4 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Shutdown Shutdown 4 minutes ago
h5ogmu03mth5 pinger.5 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 7 minutes ago
pnmp0cf22mb4 \_ pinger.5 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 7 minutes ago "No such image: registry.docke…"
p4cmg1dntadm \_ pinger.5 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 7 minutes ago "No such image: registry.docke…"
qmb5ch04n1si \_ pinger.5 registry.docker-dca.example:5000/alpine:latest master.docker-dca.example Shutdown Rejected 7 minutes ago "No such image: registry.docke…"
puca1zg6l61n \_ pinger.5 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Shutdown Shutdown about a minute ago
vagrant@master:~$
Esta no es la manera correcta si necesitas hacer un mantenimiento. Para eso necesitamos drenar, es decir, retirar todo lo que está ejecutándose en él y automáticamente docker swarm ya reasignará a un nodo disponible.
Vamos a drenar todo lo que está en worker2 ahora y ver qué sucede
vagrant@master:~$ docker node ls
# todos activos y vamos a drenar el registry y el worker2
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
jdmwyhbti8s3fnmd17lw79rhw * master.docker-dca.example Ready Active Leader 20.10.17
qvp6um8mstrgrlhhfpjj6khdc registry.docker-dca.example Ready Active 20.10.17
rxgmhpjtky4s6mktwis2jyr99 worker1.docker-dca.example Ready Active 20.10.17
7980uc978wk928ncb6esv3jy3 worker2.docker-dca.example Ready Active 20.10.17
vagrant@master:~$ docker node update worker2.docker-dca.example --availability drain
worker2.docker-dca.example
vagrant@master:~$ docker node update registry.docker-dca.example --availability drain
registry.docker-dca.example
# observe que los nodos están en drain
vagrant@master:~$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
jdmwyhbti8s3fnmd17lw79rhw * master.docker-dca.example Ready Active Leader 20.10.17
qvp6um8mstrgrlhhfpjj6khdc registry.docker-dca.example Ready Drain 20.10.17
rxgmhpjtky4s6mktwis2jyr99 worker1.docker-dca.example Ready Active 20.10.17
7980uc978wk928ncb6esv3jy3 worker2.docker-dca.example Ready Drain 20.10.17
¿Será que nuestro contenedor de registry de imágenes que estaba ejecutándose en ese nodo cayó?
Details
Respuesta
No, porque no era un servicio de docker swarm, fue un contenedor creado independientemente.vagrant@registry:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
06159f4f1e53 registry:2 "/entrypoint.sh /etc…" 36 hours ago Up 36 hours 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp registry
¿Y qué sucedió con los contenedores de ping? Todos pasaron a ejecutarse en worker1 como podemos ver.
vagrant@master:~$ docker service ps pinger --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
1ppd0q97wdcx pinger.1 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 21 minutes ago
rwk8i4r1wsvb pinger.2 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 29 minutes ago
neu2uugr2cag pinger.3 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 23 minutes ago
92yxi5wjb3xn pinger.4 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 21 minutes ago
h5ogmu03mth5 pinger.5 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 23 minutes ago
Haz una inspección del nodo que está en drain
vagrant@master:~$ docker node inspect worker2.docker-dca.example --pretty | grep Availability
Availability: Drain
Vamos a reactivar los nodos
vagrant@master:~$ docker node update worker2.docker-dca.example --availability active
worker2.docker-dca.example
vagrant@master:~$ docker node update registry.docker-dca.example --availability active
registry.docker-dca.example
vagrant@master:~$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
jdmwyhbti8s3fnmd17lw79rhw * master.docker-dca.example Ready Active Leader 20.10.17
qvp6um8mstrgrlhhfpjj6khdc registry.docker-dca.example Ready Active 20.10.17
rxgmhpjtky4s6mktwis2jyr99 worker1.docker-dca.example Ready Active 20.10.17
7980uc978wk928ncb6esv3jy3 worker2.docker-dca.example Ready Active 20.10.17
¿El swarm rebalancea el servicio en los nodos?
Details
Respuesta
No. El estado deseado del servicio fue alcanzado. Solamente en los próximos despliegues de contenedores estos nodos activos serán considerados.vagrant@master:~$ docker service ps pinger --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
1ppd0q97wdcx pinger.1 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 30 minutes ago
rwk8i4r1wsvb pinger.2 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 38 minutes ago
neu2uugr2cag pinger.3 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 32 minutes ago
92yxi5wjb3xn pinger.4 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 30 minutes ago
h5ogmu03mth5 pinger.5 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 32 minutes ago
Haz un escalado a 10 y verás que ahora tomará en consideración los nodos activos
vagrant@master:~$ docker service scale pinger=10
pinger scaled to 10
overall progress: 10 out of 10 tasks
1/10: running [==================================================>]
2/10: running [==================================================>]
3/10: running [==================================================>]
4/10: running [==================================================>]
5/10: running [==================================================>]
6/10: running [==================================================>]
7/10: running [==================================================>]
8/10: running [==================================================>]
9/10: running [==================================================>]
10/10: running [==================================================>]
verify: Service converged
vagrant@master:~$ docker service ps pinger --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
1ppd0q97wdcx pinger.1 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 32 minutes ago
rwk8i4r1wsvb pinger.2 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 40 minutes ago
neu2uugr2cag pinger.3 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 34 minutes ago
92yxi5wjb3xn pinger.4 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 32 minutes ago
h5ogmu03mth5 pinger.5 registry.docker-dca.example:5000/alpine:latest worker1.docker-dca.example Running Running 34 minutes ago
y73mw5vgyg16 pinger.6 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 19 seconds ago
4jvjg82n23p7 pinger.7 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 9 seconds ago
y41hd554jn8d pinger.8 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 19 seconds ago
8juifuow9gdl pinger.9 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 14 seconds ago
v1671qo296zd pinger.10 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 9 seconds ago
vagrant@master:~$
Observa también que el swarm no entregó nada al worker1, porque intenta balancear los recursos en el escalado.
Elimina el servicio para los próximos laboratorios
vagrant@master:~$ docker service rm pinger
pinger
Atención - Apagar una máquina a la fuerza sin hacer el drain puede ocasionar pérdida de datos durante una transacción
Docker swarm suele balancear los recursos y no las tareas por nodos.
Secrets
Los secrets sirven para almacenar recursos sensibles (confidenciales), como contraseñas, claves privadas, certificados SSL o cualquier otro recurso que no deben ser transmitidos por la red sin cifrado.
Se almacenan en blobs (Binary Large OBject) que es una colección de datos binarios almacenados como una única entidad.
Para gestionar los secrets utilizamos el comando docker secret.
El comando
docker secret createno acepta una entrada de texto en la consola, solamente vía STDIN o a través de archivo.
vagrant@master:~$ echo "senha123" | docker secret create senha_db -
asfnakvaj9uyfzj4dld3pob6g
# observa que no puedes ver el contenido de la contraseña
vagrant@master:~$ docker secret inspect senha_db --pretty
ID: asfnakvaj9uyfzj4dld3pob6g
Name: senha_db
Driver:
Created at: 2022-07-05 16:18:21.940056803 +0000 utc
Updated at: 2022-07-05 16:18:21.940056803 +0000 utc
vagrant@master:~$
El secret debe ser pasado por parámetro al contenedor que se almacena en el archivo
/run/secrets/<secret_name>del contenedor.
Vamos a ejecutar un contenedor mysql pasando la contraseña como un secret
vagrant@master:~$ docker service create --name mysql_database \
> --publish 3306:3306/tcp \
> --secret senha_db \
> -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/senha_db \
> registry.docker-dca.example:5000/mysql:5.7
image registry.docker-dca.example:5000/mysql:5.7 could not be accessed on a registry to record
its digest. Each node will access registry.docker-dca.example:5000/mysql:5.7 independently,
possibly leading to different nodes running different
versions of the image.
4i4v1sqgy78w2ekzdscychojc
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
Podemos acceder a la base de datos instalando un cliente. Para eso vamos a instalar un cliente de mariadb e intentar entrar al servidor con la contraseña para verificar.
vagrant@master:~$ sudo apt-get install mariadb-client -y
# como publicamos el puerto, cualquiera de los hostnames respondería a la solicitud aunque estuviera en otro nodo
vagrant@master:~$ mysql -h master.docker-dca.example -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.38 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> CREATE DATABASE testedb
-> ;
Query OK, 1 row affected (0.002 sec)
MySQL [(none)]> SHOW DATABASES
-> ;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| testedb |
+--------------------+
5 rows in set (0.002 sec)
MySQL [(none)]> EXIT
Bye
Elimina el servicio
vagrant@master:~$ docker service rm mysql_database
Network
Por defecto, como se mencionó anteriormente, el swarm cuando se inicia crea una red overlay llamada ingress, pero también podemos crear otra red para el alcance del swarm.
vagrant@master:~$ docker network create -d overlay dca
vcql6ccw3n11fixhyh687ng6r
vagrant@master:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
7d44eda15cc1 bridge bridge local
# Observa que cuando pasas overlay ya toma el alcance del swarm
vcql6ccw3n11 dca overlay swarm
44b44c41564a docker_gwbridge bridge local
f7e2501a4afc host host local
z9uj2ahzsg28 ingress overlay swarm
c2447a55b6c2 none null local
¿Esta red se levantó en todos los nodos del swarm?
Vamos a levantar un servicio en esta red.
# el publish puede especificarse cuál es el target y el published port en caso de que se quiera definir. Si no se expone, publicará el mismo puerto expose que la imagen define.
vagrant@master:~$ docker service create --name webserver --publish target=80,published=80 --network dca registry.docker-dca.example:5000/nginx
image registry.docker-dca.example:5000/nginx:latest could not be accessed on a registry to record
its digest. Each node will access registry.docker-dca.example:5000/nginx:latest independently,
possibly leading to different nodes running different
versions of the image.
z071ovcmnnhwpja74insryi0c
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
# observa que se levantó en worker2 pero creamos la red overlay en el master
vagrant@master:~$ docker service ps webserver
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
azpyvzjvfb2r webserver.1 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running 30 seconds ago
vagrant@master:~$
¿La red overlay dca que creamos está presente en los otros nodos? Sí, cuando creamos una red overlay el swarm automáticamente ya crea esa red en todos los nodos. ¡Vamos a verificarlo!
❯
[vagrant@worker2 ~]$ docker network ls
NETWORK ID NAME DRIVER SCOPE
9dc63665ea48 bridge bridge local
# está aquí
vcql6ccw3n11 dca overlay swarm
258d442717bf docker_gwbridge bridge local
f988e2b4b3d5 host host local
z9uj2ahzsg28 ingress overlay swarm
8c368d1f1a41 none null local
# ¿y el contenedor está ejecutándose?
[vagrant@worker2 ~]$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4cf386c682fc registry.docker-dca.example:5000/nginx:latest "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 80/tcp webserver.1.azpyvzjvfb2r2tzexu24pjhn3
[vagrant@worker2 ~]$
Si escalamos el contenedor podemos observar que lo logra.
vagrant@master:~$ docker service ps webserver --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
azpyvzjvfb2r webserver.1 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running 10 minutes ago
n3f6egq78zeq webserver.2 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running 25 seconds ago
ijvv9lbzb6xg webserver.3 registry.docker-dca.example:5000/nginx:latest registry.docker-dca.example Running Running 45 seconds ago
vagrant@master:~$
¿Podríamos crear una red overlay en worker2 que no es manager del clúster?
Details
Respuesta
No, la red overlay necesita ser creada solamente en alguno de los masters.[vagrant@worker2 ~]$ docker network ls
NETWORK ID NAME DRIVER SCOPE
[vagrant@worker2 ~]$ docker network create -d overlay dcateste
Error response from daemon: Cannot create a multi-host network from a worker node. Please create the network from a manager node.
¿Y cómo queda la comunicación entre las redes? Por más que exista más de una red overlay, logra resolver solamente apuntando a los hostnames.
Volúmenes
Si levantamos un volumen, ¿dónde quedará en caso de que un contenedor sea escalado? Volviendo al estudio de volúmenes vamos a instalar el plugin de nfs, porque destruimos las máquinas anteriormente.
¿Es necesario instalar el plugin en todas las máquinas?
Stack
El stack es el compose del swarm. Con él podemos automatizar la creación de varios servicios al mismo tiempo con un único manifiesto. Una curiosidad es que no hay forma de crear un build dentro del stack como hacemos en compose, porque no existe el parámetro build, solo image. Es necesario pasar una imagen.
En el ambiente de producción desplegar servicios uno a uno no es la manera ideal. La mejor práctica es tener un archivo con todo el ambiente definido.
Cuando ejecutamos Docker en modo swarm, podemos utilizar el comando docker stack deploy
Utilizar el comando docker stack deploy para hacer el despliegue de una aplicación completa en el swarm. Es necesario pasar el archivo de compose en el comando deploy. Este archivo no necesita tener un nombre específico, pero necesita ser un archivo yaml.
Para trabajar con stacks, necesitamos utilizar el archivo compose con su versión 3 o superior.
Vamos a hacer el despliegue del webserver que utilizamos anteriormente, pero con el stack.
Primeramente crea una carpeta para almacenar archivos del stack y el archivo webserver.yml donde vamos a definir nuestro compose.
vagrant@master:~$ mkdir -p stack
vagrant@master:~$ cd stack/
vagrant@master:~/stack$ cat << EOF > webserver.yaml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
EOF
# Observa que al igual que en compose creó una network
# --compose-file podría ser -c
vagrant@master:~/stack$ docker stack deploy --compose-file webserver.yaml myproject
Creating network myproject_default
Creating service myproject_webserver
# Observa también que pone el nombre del proyecto seguido del nombre del servicio
vagrant@master:~/stack$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
7c47dz1amos0 myproject_webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:80->80/tcp
vagrant@master:~/stack$
Vamos a verificar los subcomandos del stack
# Para listar los despliegues
vagrant@master:~/stack$ docker stack ls
NAME SERVICES ORCHESTRATOR
myproject 1 Swarm
vagrant@master:~/stack$
# Para verificar los servicios de un despliegue
vagrant@master:~/stack$ docker stack services myproject
ID NAME MODE REPLICAS IMAGE PORTS
7c47dz1amos0 myproject_webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:80->80/tcp
vagrant@master:~/stack$
# O un ps para mostrar los contenedores que están siendo ejecutados
vagrant@master:~/stack$ docker stack ps myproject
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
zmroxabl08o0 myproject_webserver.1 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running 4 minutes ago
p17l8q2q1kiu \_ myproject_webserver.1 registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Shutdown Rejected 4 minutes ago "No such image: registry.docke…"
vagrant@master:~/stack$
Ahora vamos a modificar algunos parámetros del archivo del stack.
No es necesario remover un despliegue para levantar otro, simplemente actualiza el ya existente.
El campo deployment define las estrategias de nuestro despliegue, como réplicas, políticas, etc.
vagrant@master:~/stack$
cat << EOF > webserver.yaml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
deploy:
replicas: 5
restart_policy:
condition: on-failure
EOF
vagrant@master:~/stack$ docker stack deploy --compose-file webserver.yaml myproject
# verificando si las réplicas funcionaron
vagrant@master:~/stack$ docker stack services myproject
ID NAME MODE REPLICAS IMAGE PORTS
7c47dz1amos0 myproject_webserver replicated 5/5 registry.docker-dca.example:5000/nginx:latest *:80->80/tcp
vagrant@master:~/stack$
# Observa que no hace despliegue en el master por defecto.
vagrant@master:~/stack$ docker stack ps myproject --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
v2934l9iqick myproject_webserver.1 registry.docker-dca.example:5000/nginx:latest registry.docker-dca.example Running Running 2 minutes ago
ycj0gyhmder6 myproject_webserver.2 registry.docker-dca.example:5000/nginx:latest registry.docker-dca.example Running Running 2 minutes ago
md31kkccla2d myproject_webserver.3 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running 2 minutes ago
22wwst0ohbv5 myproject_webserver.4 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running 2 minutes ago
vzdo661usdf6 myproject_webserver.5 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running 2 minutes ago
vagrant@master:~/stack$
Haz la prueba nuevamente quitando las réplicas para el global y ver cómo funciona este método de despliegue
vagrant@master:~/stack$ cat << EOF > webserver.yaml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
deploy:
mode: global
restart_policy:
condition: on-failure
EOF
# Dejé esto para mostrar que no es posible cambiar el tipo de servicio en caliente. Es necesario remover el servicio y hacer el despliegue nuevamente.
vagrant@master:~/stack$ docker stack deploy --compose-file webserver.yaml myproject
Updating service myproject_webserver (id: 7c47dz1amos0u01bxik141yrx)
failed to update service myproject_webserver: Error response from daemon: rpc error: code = Unimplemented desc = service mode change is not allowed
vagrant@master:~/stack$
vagrant@master:~/stack$ docker stack rm myproject
vagrant@master:~/stack$ docker stack services myproject
ID NAME MODE REPLICAS IMAGE PORTS
286ebe59na9g myproject_webserver global 4/4 registry.docker-dca.example:5000/nginx:latest *:80->80/tcp
# Observa que creó uno en cada nodo incluyendo el master
vagrant@master:~/stack$ docker service ps myproject_webserver
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
mby8i5ycafmo myproject_webserver.7980uc978wk928ncb6esv3jy3 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running about a minute ago
qgz3s92kyd46 myproject_webserver.jdmwyhbti8s3fnmd17lw79rhw registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Running Running about a minute ago
zay6j3qxum2y myproject_webserver.qvp6um8mstrgrlhhfpjj6khdc registry.docker-dca.example:5000/nginx:latest registry.docker-dca.example Running Running about a minute ago
q04akpyw6jzh myproject_webserver.rxgmhpjtky4s6mktwis2jyr99 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running about a minute ago
Eliminando...
vagrant@master:~/stack$ docker stack rm myproject
Removing service myproject_webserver
Removing network myproject_default
Constraints e Labels
https://docs.docker.com/engine/swarm/services/#placement-constraints
Constraints son restricciones para en cuáles nodos deben ser desplegadas nuestras tareas. Deben estar generalmente dentro del bloque placement.
https://docs.docker.com/config/labels-custom-metadata/
Label es un metadato de etiqueta que podemos usar para hacer match con el fin de agrupar al hacer un filtro, pero también es usada por los constraints.
No es el tema, pero LABEL puede ser agregado en imágenes, directamente en un contenedor, en un volumen, en network, en nodos y en servicios. Para hacer match solamente puede ser usado
== o !=. O es o no es.
En este ejemplo vamos a solicitar solamente en los nodos manager.
vagrant@master:~/stack$ cat << EOF > webserver.yaml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
deploy:
mode: replicated
replicas: 5
placement:
constraints:
- node.role==manager # es manager o worker
## otros ejemplos: node.id node.hostname
restart_policy:
condition: on-failure
EOF
vagrant@master:~/stack$ docker stack deploy --compose-file webserver.yaml myproject
Updating service myproject_webserver (id: acjc23mh4gsz8r54gn3a7bvzg)
vagrant@master:~/stack$
# observa que solo entró en el master
vagrant@master:~/stack$ docker stack ps myproject --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
6gm3u7nwm24m myproject_webserver.1 registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Running Running 46 seconds ago
u45vc7u41dxn myproject_webserver.2 registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Running Running 45 seconds ago
0qywj4djqfxs myproject_webserver.3 registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Running Running 44 seconds ago
p9848shciu3m myproject_webserver.4 registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Running Running 43 seconds ago
i1s5wkozo2uy myproject_webserver.5 registry.docker-dca.example:5000/nginx:latest master.docker-dca.example Running Running 42 seconds ago
vagrant@master:~/stack$
Vamos a agregar algunas etiquetas de tipo de disco, región y sistema operativo en los nodos
vagrant@master:~/stack$ docker node update --label-add disk=ssd worker1.docker-dca.example
worker1.docker-dca.example
vagrant@master:~/stack$ docker node update --label-add region=us-east-1 worker1.docker-dca.example
worker1.docker-dca.example
vagrant@master:~/stack$ docker node update --label-add disk=hdd worker2.docker-dca.example
worker2.docker-dca.example
vagrant@master:~/stack$ docker node update --label-add region=us-east-1 worker2.docker-dca.example
worker2.docker-dca.example
vagrant@master:~/stack$ docker node update --label-add region=us-east-2 registry.docker-dca.example
registry.docker-dca.example
vagrant@master:~/stack$ docker node update --label-add os=ubuntu worker1.docker-dca.example
worker1.docker-dca.example
vagrant@master:~/stack$ docker node update --label-add os=centos worker2.docker-dca.example
worker2.docker-dca.example
# o ganar tiempo haciendo esto
vagrant@master:~/stack$ docker node update --label-add os=ubuntu --label-add disk=ssd --label-add region=us-east-1 master.docker-dca.example
master.docker-dca.example
Haciendo un inspect en uno de estos contenedores para ver la etiqueta podemos ver etiquetas por allí.
vagrant@master:~/stack$ docker node inspect master.docker-dca.example --pretty
ID: jdmwyhbti8s3fnmd17lw79rhw
Labels:
- disk=ssd
- os=ubuntu
- region=us-east-1
Hostname: master.docker-dca.example
Joined at: 2022-07-01 01:56:16.645464567 +0000 utc
Status:
State: Ready
Availability: Active
Address: 10.10.10.100
Manager Status:
Address: 10.10.10.100:2377
Raft Status: Reachable
Leader: Yes
Platform:
Operating System: linux
Architecture: x86_64
Resources:
CPUs: 2
Memory: 1.937GiB
Plugins:
Log: awslogs, fluentd, gcplogs, gelf, journald, json-file, local, logentries, splunk, syslog
Network: bridge, host, ipvlan, macvlan, null, overlay
Volume: local, trajano/nfs-volume-plugin:latest
Engine Version: 20.10.17
TLS Info:
TrustRoot:
-----BEGIN CERTIFICATE-----
MIIBaTCCARCgAwIBAgIUG+odVPoSYT2+BmLkq3wInnasx9owCgYIKoZIzj0EAwIw
EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMjIwNzAxMDE1MTAwWhcNNDIwNjI2MDE1
MTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABMYd+K9Z9i7NLOBRzUOYL4vKJ/jaJascVXJYKSafMbBwhr/WOgcZ6NlPBIMG
zsdcxTP9zIYggeiSGYmA7WIMZ3ujQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQbh3AGVtxzbeT6zxp0QghuuotPcjAKBggqhkjO
PQQDAgNHADBEAiAVHH83vBU5qb/sFbF8DBvFyWDHjFsV649/BAVWcAyncQIgFKcU
/M/pAK7YI5bdgKz1RA57XzUdVMVvD+ErJGSgnT0=
-----END CERTIFICATE-----
Issuer Subject: MBMxETAPBgNVBAMTCHN3YXJtLWNh
Issuer Public Key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExh34r1n2Ls0s4FHNQ5gvi8on+NolqxxVclgpJp8xsHCGv9Y6Bxno2U8EgwbOx1zFM/3MhiCB6JIZiYDtYgxnew==
Ahora utilizando estas etiquetas vamos a rehacer nuestro stack.
vagrant@master:~/stack$ cat << EOF > webserver.yaml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
deploy:
mode: replicated
replicas: 5
placement:
constraints:
- node.labels.disk==ssd
- node.labels.os==ubuntu
- node.labels.region==us-east-1
- node.role==worker
restart_policy:
condition: on-failure
EOF
Si aplicamos nuevamente este stack, ¿sustituirá lo que ya tiene allí? No. Recuerda que el estado del servicio ya fue alcanzado, entonces si tiene 5 contenedores ejecutándose solo aplicará a las nuevas tareas. Para que funcione vamos a remover lo que tiene allí.
Mira que creó todo en worker1 porque es el único que cumplió todas las restricciones.
agrant@master:~/stack$ docker stack rm myproject
Removing service myproject_webserver
Removing network myproject_default
vagrant@master:~/stack$ docker stack deploy --compose-file webserver.yaml myproject
Creating network myproject_default
Creating service myproject_webserver
vagrant@master:~/stack$ docker stack ps myproject --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
usyfq0koorpe myproject_webserver.1 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running less than a second ago
s5myway7hhwl myproject_webserver.2 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running less than a second ago
ivabd1hijfxs myproject_webserver.3 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running less than a second ago
ifn1imn3d3ip myproject_webserver.4 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running less than a second ago
knyi5l2xmery myproject_webserver.5 registry.docker-dca.example:5000/nginx:latest worker1.docker-dca.example Running Running less than a second ago
vagrant@master:~/stack$
Elimina el stack
vagrant@master:~/stack$ docker stack rm myproject
Removing service myproject_webserver
Removing network myproject_default
Ahora vamos a profundizar más creando un stack mayor
vagrant@master:~/stack$ cat << EOF > webserver.yaml
version: '3.9'
volumes:
mysql_db:
networks:
wordpress_net:
# Este secret ya había sido declarado anteriormente y fue reutilizado aquí para mostrar la funcionalidad
secrets:
senha_db:
external: true # external quiere decir que no fue creado aquí
services:
wordpress:
image: registry.docker-dca.example:5000/wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/senha_db
WORDPRESS_DB_NAME: wordpress
networks:
- wordpress_net
secrets:
- senha_db
deploy:
mode: replicated
replicas: 5
placement:
constraints:
- node.role==worker
restart_policy:
condition: on-failure
depends_on:
- db
db:
image: registry.docker-dca.example:5000/mysql:5.7
volumes:
- mysql_db:/var/lib/mysql
secrets:
- senha_db
networks:
- wordpress_net
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD_FILE: /run/secrets/senha_db
MYSQL_RANDOM_ROOT_PASSWORD: '1'
deploy:
replicas: 1
placement:
constraints:
- node.role==manager
restart_policy:
condition: on-failure
EOF
docker stack deploy wordpress --compose-file webserver.yaml
Vamos a hacer el despliegue del stack y verás que hizo la actualización.
vagrant@master:~/stack$ docker stack deploy wordpress --compose-file webserver.yaml
Updating service wordpress_db (id: z91i4piqiua4mufapobni2x3d)
Updating service wordpress_wordpress (id: lsp4152rvzpwxbvzyoq7k3g2g)
docker stack services wordpress
docker stack ps wordpress
Accede al navegador y configura la página web
Nota que podemos acceder al sitio desde cualquier dirección del clúster, ya que tenemos un vip configurado.
http://master.docker-dca.example:8080/

Gestión de Recursos
También podemos gestionar un límite/reserva de recursos para el contenedor a través del parámetro resources en compose. En el bloque de deploy del wordpress (esto es solo una porción del bloque) podríamos incluir algunos parámetros más.
vagrant@master:~/stack$ cat << EOF > webserver.yaml
version: '3.9'
volumes:
mysql_db:
networks:
wordpress_net:
# Este secret ya había sido declarado anteriormente y fue reutilizado aquí para mostrar la funcionalidad
secrets:
senha_db:
external: true # external quiere decir que no fue creado aquí
services:
wordpress:
image: registry.docker-dca.example:5000/wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/senha_db
WORDPRESS_DB_NAME: wordpress
networks:
- wordpress_net
secrets:
- senha_db
deploy:
mode: replicated
replicas: 5
placement:
constraints:
- node.role==worker
restart_policy:
condition: on-failure
########## Parámetros nuevos ##########
resources:
limits: # (Valores máximos)
cpus: "1"
memory: 60M # en megas
reservations: # (Valores Garantizados)
cpus: "0.5" # esto es 50% de una cpu
memory: 30M
######################################
depends_on:
- db
db:
image: registry.docker-dca.example:5000/mysql:5.7
volumes:
- mysql_db:/var/lib/mysql
secrets:
- senha_db
networks:
- wordpress_net
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD_FILE: /run/secrets/senha_db
MYSQL_RANDOM_ROOT_PASSWORD: '1'
deploy:
replicas: 1
placement:
constraints:
- node.role==manager
restart_policy:
condition: on-failure
EOF
Esto quiere decir que separará garantizadamente 0.5 cpu y 30M de ram, es decir, el contenedor siempre tiene eso disponible para él. Si necesita aumentar llegará hasta el máximo de 1 cpu y 60M de ram.
Si todos los contenedores llegan al máximo pero solo tienen garantizado el mínimo, ¿qué sucede?
Vamos a hacer el despliegue nuevamente
vagrant@master:~/stack$ docker stack services wordpress
ID NAME MODE REPLICAS IMAGE PORTS
z91i4piqiua4 wordpress_db replicated 1/1 registry.docker-dca.example:5000/mysql:5.7
lsp4152rvzpw wordpress_wordpress replicated 5/5 registry.docker-dca.example:5000/wordpress:latest *:8080->80/tcp
vagrant@master:~/stack$ docker stack ps wordpress --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
kk1j0x76zo8d wordpress_db.1 registry.docker-dca.example:5000/mysql:5.7 master.docker-dca.example Running Running 31 hours ago
odd02cbzelvj wordpress_wordpress.1 registry.docker-dca.example:5000/wordpress:latest worker2.docker-dca.example Running Running about a minute ago
wzaop3wrnrk6 wordpress_wordpress.2 registry.docker-dca.example:5000/wordpress:latest worker1.docker-dca.example Running Running about a minute ago
k6mjvcote05s wordpress_wordpress.3 registry.docker-dca.example:5000/wordpress:latest worker2.docker-dca.example Running Running about a minute ago
muvxhf3dzyxl wordpress_wordpress.4 registry.docker-dca.example:5000/wordpress:latest worker1.docker-dca.example Running Running about a minute ago
u2je2a8euo7s wordpress_wordpress.5 registry.docker-dca.example:5000/wordpress:latest registry.docker-dca.example Running Running about a minute ago
vagrant@master:~/stack$
# Verificando el servicio...
vagrant@master:~/stack$ docker service inspect wordpress_wordpress --pretty
ID: lsp4152rvzpwxbvzyoq7k3g2g
Name: wordpress_wordpress
Labels:
com.docker.stack.image=registry.docker-dca.example:5000/wordpress
com.docker.stack.namespace=wordpress
Service Mode: Replicated
Replicas: 5
UpdateStatus:
State: completed
Started: 3 minutes ago
Completed: 3 minutes ago
Message: update completed
Placement:
Constraints: [node.role==worker]
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:
Image: registry.docker-dca.example:5000/wordpress:latest@sha256:b57bf41505b6eb494a59034820f5bd3517bbedcddb35c3ad1be950bfc96c2164
Env: WORDPRESS_DB_HOST=db WORDPRESS_DB_NAME=wordpress WORDPRESS_DB_PASSWORD_FILE=/run/secrets/senha_db WORDPRESS_DB_USER=wpuser
Secrets:
Target: senha_db
Source: senha_db
Resources:
## aquí los resources que definimos
Reservations:
CPU: 0.5
Memory: 30MiB
Limits:
CPU: 1
Memory: 60MiB
Networks: wordpress_wordpress_net
Endpoint Mode: vip
Ports:
PublishedPort = 8080
Protocol = tcp
TargetPort = 80
PublishMode = ingress
Un comando para verificar el uso de memoria de un contenedor es docker stats.
El docker stats debe ser usado en cada uno de los nodos para verificar sus contenedores. Con este comando no podemos directamente desde el master saber esto.
Haz un docker stats en cada uno de los nodos y verifica

¿Es posible saber el uso de memoria de cada contenedor de otros nodos? Directamente por el swarm no. Para eso se usan otros recursos como veremos más adelante. ¿Recuerdas el global?
Vamos a instalar un stresser en la máquina master que no tiene wordpress y estresar las otras.
Vamos a instalar en la máquina master el apache benchmark para hacer un stress test en el contenedor.
sudo apt-get install apache2-utils -y
Ejecuta el apache benchmark y acompaña el uso de cpu/memoria del contenedor en los contenedores que están ejecutando wordpress, porque pasamos el puerto 8080 entonces va al worker1 y 2.
ab -n 10000 -c 100 http://master.docker-dca.example:8080/

Vamos a escalar a 10 y aumentar el número de concurrencia a 1000 y ver los contenedores trabajando.

Observa que los contenedores cayeron, no tenían memoria suficiente para garantizar la entrada y se bloquearon. Sin embargo, el scheduler los levantó nuevamente para alcanzar nuestro objetivo del servicio.
No es obligatorio, pero es necesario configurar límites para tus contenedores. Si el contenedor se ejecuta sin límite, invade toda la cpu y memoria de la máquina y puede derribar todos los otros contenedores. Mira el gif abajo.

Elimina el stack
docker stack rm wordpress-stack
Q1 = Option 3 Q2 = Option 1 Q3 = Option 1 Q4 = A Q5 = B Q6 = A Q7 = Option 3 Q8 = C Q9 = C