Docker Swarm - Parte 2
https://docs.docker.com/engine/swarm/key-concepts/
Vamos a comprender primero algunos conceptos y para trabajar con swarm solamente utilizaremos el nodo master, ya que es él quien gestiona el clúster.
Service
El servicio es el estado deseado o estado declarativo. Es nuestro despliegue. Un servicio instancia un planificador (scheduler) para cuidar de sus tareas. Imaginemos que queremos 40 nginx.

Es posible tener dos tipos de servicios
- Replicas: Garantiza el número X de tareas pero no define en qué nodos.
- global: Garantiza una y solamente una tarea para cada uno de los nodos, incluyendo los masters. Generalmente usada en agentes o servicios de monitoreo para cada host.

El comando para el servicio es docker service.
Ejecuta
docker service --helpy comprueba.
Salida...
vagrant@master:~$ docker service --help
Usage: docker service COMMAND
Manage services
Commands:
create Create a new service
inspect Display detailed information on one or more services
logs Fetch the logs of a service or task
ls List services
ps List the tasks of one or more services
rm Remove one or more services
rollback Revert changes to a service's configuration
scale Scale one or multiple replicated services
update Update a service
Run 'docker service COMMAND --help' for more information on a command.
Scheduler
Él cuida y monitorea las tareas para alcanzar el estado deseado, si es posible. Imaginemos que quieres 40 contenedores de nginx, para eso él va a crear 40 tareas para replicar el servicio. Si una tarea se detiene, él es el responsable de levantarla nuevamente.
Quien define para quién va cada tarea es el swarm y no el scheduler
Task
Las tareas son las unidades programadas por el planificador para alcanzar el servicio. Si definiste un servicio para 40 nginx por ejemplo, él creará 40 tareas.
Una tarea puede contener más de un contenedor, pero no es recomendado. Generalmente cuando esto ocurre es cuando tienes un sidecar junto a tu contenedor principal.
Una tarea puede estar en varios estados, pero existen 3 principales.
- assigned (entregada a un nodo)
- prepared (docker está comenzando a ejecutar la tarea)
- running (en ejecución y lista)
Si una tarea falla, el orquestador elimina la tarea y luego crea una nueva tarea para reemplazarla de acuerdo con el estado deseado especificado por el servicio
| Estado | Descripción |
|---|---|
NEW | Tarea fue inicializada |
PENDING | Recursos están siendo asignados |
ASIGNED | Tarea fue asignada a un nodo |
ACCEPTED | Tarea aceptada por un nodo worker |
PREPARING | Docker está preparando la tarea |
STARTING | Docker está iniciando la tarea |
RUNNING | Tarea en ejecución |
COMPLETE | Tarea finalizó sin código de error |
FAILED | Tarea finalizó con código de error |
SHUTDOWN | Docker solicitó el apagado de la tarea |
REJECTED | El nodo worker rechazó la tarea |
ORPHANED | El nodo estuvo caído por mucho tiempo |
REMOVE | La tarea no terminó pero el recurso asociado fue eliminado o reducido |
El diagrama abajo muestra un servicio con tres réplicas en amarillo y un servicio global en gris
Creando el primer servicio
https://docs.docker.com/engine/swarm/services/
Recordando que este comando solo funciona en un nodo master. Si ejecutas un comando en el worker mira el error.
vagrant@worker1:~$ docker service create --name webserver registry.docker-dca.example:5000/nginx
Error response from daemon: This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.
vagrant@worker1:~$
# Ya descargando la imagen de nuestro registry.
vagrant@master:~$ docker service create --name webserver 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.
# Vea la importancia de definir una versión.
hzzekzrenlgheyycesni914n3
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
vagrant@master:~$
Podemos listar los servicios a través del subcomando ls y listar las tareas a través del comando ps
# Comprobando
# ls va a listar los servicios
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
hzzekzrenlgh webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest
# ps va a listar las tareas en un servicio
vagrant@master:~$ docker service ps webserver
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
x03ce6x9pqqn webserver.1 registry.docker-dca.example:5000/nginx:latest worker2.docker-dca.example Running Running 9 minutes ago
docker service psverifica el estado de la tarea, el id de la tarea y en qué nodo está siendo ejecutada la tarea, errores si existen y puerto publicado.
El Swarm crea una red overlay, como estudiamos anteriormente, entonces cuando se publica un puerto externo apuntando al servicio, él mapea ese puerto en todos los hosts. Cualquier dirección IP o DNS de nuestros hosts apunta al servicio expuesto en ella.

Vamos a comprobar esta red
vagrant@master:~$ docker network inspect ingress
[
{
"Name": "ingress",
"Id": "z9uj2ahzsg2827bmo7jm0ownm",
"Created": "2022-07-04T05:08:09.208854476Z",
# vea aquí scope y driver
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": true,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"ingress-sbox": {
"Name": "ingress-endpoint",
"EndpointID": "183a3618a258b927bf1a5be4b640ba197a57daef432b1d90c6c14708858f2c0d",
"MacAddress": "02:42:0a:00:00:02",
"IPv4Address": "10.0.0.2/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4096"
},
"Labels": {},
# Vea nuestros nodos
"Peers": [
{
"Name": "e159ab980457",
"IP": "10.10.10.100"
},
{
"Name": "9dd602f5ced3",
"IP": "10.10.10.200"
},
{
"Name": "2f3ccb6467d2",
"IP": "10.10.10.110"
},
{
"Name": "ac8c20da3b2b",
"IP": "10.10.10.120"
}
]
}
]
El swarm, así como kubernetes, utilizan puertos altos entre 30000-32767 siendo que si no especificas el puerto él asigna uno automáticamente. Generalmente es secuencial, pero si cae en el examen es aleatorio.
Eliminando el servicio
vagrant@master:~$ docker service rm webserver
webserver
vagrant@master:~$ docker service rm webserver
webserver
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
Publicando el puerto
Para publicar el puerto podemos pasar el puerto directamente en el comando o hacer un update en caso de que no se haya pasado.
vagrant@master:~$ docker service create --name webserver --publish 80 registry.docker-dca.example:5000/nginx
t4jwpwmcb6ds74f06xhf7mblf
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
# observe que él apuntó al 30000 es decir el primero disponible y nunca utilizado.
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
t4jwpwmcb6ds webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30000->80/tcp
# creando sin el puerto
vagrant@master:~$ docker service create --name webserver2 registry.
3ly198hikj8xmq67wnbduq8ty
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
t4jwpwmcb6ds webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30000->80/tcp
3ly198hikj8x webserver2 replicated 1/1 registry.docker-dca.example:5000/nginx:latest
vagrant@master:~$ docker service
create inspect logs ls ps rm rollback scale update
# haciendo un update del servicio para agregar el puerto
vagrant@master:~$ docker service update webserver2 --publish-add 80
webserver2
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
# comprobando si tomó el puerto
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
t4jwpwmcb6ds webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30000->80/tcp
3ly198hikj8x webserver2 replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30002->80/tcp
# agregando uno más
vagrant@master:~$ docker service create --name webserver3 registry.docker-dca.example:5000/nginx
4o3zs0turlfdmrks79a37s9o4
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
vagrant@master:~$ docker service update webserver3 --publish-add 80
webserver3
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
# comprobando
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
t4jwpwmcb6ds webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30000->80/tcp
3ly198hikj8x webserver2 replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30002->80/tcp
4o3zs0turlfd webserver3 replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30003->80/tcp
Curiosidad: en el medio del camino agregué y eliminé un servicio, por eso saltó el 30001, porque aunque ya había sido usado antes y el servicio utilizado ya estaba caído, el incremento ya había pasado y es por eso que debemos marcar aleatorio en el examen.
Eliminé los servidores webserver2 y webserver3 y vamos a ver...
vagrant@master:~$ docker service create --name webserver2 --publish 80
bjp25u6lhxqdy9odrc5vjfejb
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
# aunque el 30001 estaba disponible tomó el 30004
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
t4jwpwmcb6ds webserver replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30000->80/tcp
bjp25u6lhxqd webserver2 replicated 1/1 registry.docker-dca.example:5000/nginx:latest *:30004->80/tcp
vagrant@master:~$
Vamos a hacer un inspect del servicio. La etiqueta --pretty trae un formato de mejor lectura, pero podría pasarse sin ella.
vagrant@master:~$ docker service inspect webserver --pretty
ID: t4jwpwmcb6ds74f06xhf7mblf
Name: webserver
# número de réplicas deseado
Service Mode: Replicated
Replicas: 1
Placement:
UpdateConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
# configuraciones predeterminadas de rollback
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
Image: registry.docker-dca.example:5000/nginx:latest
Init: false
Resources:
# vip = virtual ip
Endpoint Mode: vip
Ports:
# puerto externo
PublishedPort = 30000
Protocol = tcp
# puerto interno
TargetPort = 80
# red de entrada es la ingress creada por swarm
PublishMode = ingress
# consultando nginx a través del registry
vvagrant@master:~$ curl http://registry.docker-dca.example:30000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
# consultando nginx a través del master
vagrant@master:~$ curl http://master.docker-dca.example:30000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
vagrant@master:~$
Escalando el servicio y viendo los logs
https://docs.docker.com/engine/swarm/swarm-tutorial/scale-service/
Vamos a crear y escalar contenedores para hacer ping a google
vagrant@master:~$ docker service rm webserver
webserver
vagrant@master:~$ docker service create --name pinger registry.docker-dca.example:5000/alpine ping google.com
image registry.docker-dca.example:5000/alpine:latest could not be accessed on a registry to record
its digest. Each node will access registry.docker-dca.example:5000/alpine:latest independently,
possibly leading to different nodes running different
versions of the image.
c4lfq7i30zl5axkws9h4s5znr
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
vagrant@master:~$ docker service ls
## 1/1 quiere decir que quieres 1 y tienes 1 ejecutándose
ID NAME MODE REPLICAS IMAGE PORTS
c4lfq7i30zl5 pinger replicated 1/1 registry.docker-dca.example:5000/alpine:latest
vagrant@master:~$ docker service ps pinger
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
o3l22j6jm6tv pinger.1 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 22 seconds ago
# la etiqueta -f al final puede mostrar en tiempo real
vagrant@master:~$ docker service logs pinger
[email protected] | PING google.com (142.251.129.238): 56 data bytes
[email protected] | 64 bytes from 142.251.129.238: seq=0 ttl=61 time=20.254 ms
[email protected] | 64 bytes from 142.251.129.238: seq=1 ttl=61 time=20.299 ms
[email protected] | 64 bytes from 142.251.129.238: seq=2 ttl=61 time=18.974 ms
[email protected] | 64 bytes from 142.251.129.238: seq=3 ttl=61 time=19.248 ms
# eliminado
Ahora vamos a escalar horizontalmente!
Vamos a pasar directo a 50
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
obrf27hmw0jl pinger replicated 1/1 registry.docker-dca.example:5000/alpine:latest
vagrant@master:~$ docker service scale pinger=50
pinger scaled to 50
overall progress: 50 out of 50 tasks
verify: Service converged
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
obrf27hmw0jl pinger replicated 50/50 registry.docker-dca.example:5000/alpine:latest
vagrant@master:~$
# vamos a disminuir usando otro comando que también funciona
vagrant@master:~$ docker service update --replicas 10 pinger
pinger
overall progress: 10 out of 10 tasks
1/10: No such image: registry.docker-dca.example:5000/alpine:latest
2/10: No such image: registry.docker-dca.example:5000/alpine:latest
3/10: No such image: registry.docker-dca.example:5000/alpine:latest
4/10: No such image: registry.docker-dca.example:5000/alpine:latest
5/10: No such image: registry.docker-dca.example:5000/alpine:latest
6/10: running [==================================================>]
7/10: No such image: registry.docker-dca.example:5000/alpine:latest
8/10: running [==================================================>]
9/10: running [==================================================>]
10/10: running [==================================================>]
verify: Service converged
vagrant@master:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
obrf27hmw0jl pinger replicated 10/10 registry.docker-dca.example:5000/alpine:latest
# se usó un filtro porque si haces un ps él va a traer todos los 50 contenedores que ya fueron utilizados antes con status shutdown
vagrant@master:~$ docker service ps pinger --filter desired-state=running
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
z1tqamnptnh4 pinger.1 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 5 minutes ago
at4yz7zx5pce pinger.2 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 4 minutes ago
wvzc9p7mcmn4 pinger.3 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 4 minutes ago
vdk7zibnlg3o pinger.4 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 4 minutes ago
qgzzwntz07h5 pinger.5 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 4 minutes ago
yhbm84y11b87 pinger.6 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 4 minutes ago
ie3ddfuo1hby pinger.7 registry.docker-dca.example:5000/alpine:latest registry.docker-dca.example Running Running 4 minutes ago
4wguhskm9kh8 pinger.8 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 4 minutes ago
ivb6e4hax9ig pinger.9 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 4 minutes ago
ol60tyle5vp0 pinger.10 registry.docker-dca.example:5000/alpine:latest worker2.docker-dca.example Running Running 4 minutes ago
vagrant@master:~$
Mostrando un log
vagrant@master:~$ docker service logs pinger -f
# eliminado hacia arriba habría mucho contenido, pero quité un pedazo
# log de la tarea 8
[email protected] | 64 bytes from 142.251.128.78: seq=449 ttl=61 time=18.690 ms
# log de la tarea 5
[email protected] | 64 bytes from 142.251.128.78: seq=448 ttl=61 time=18.027 ms
# log de la tarea 10
[email protected] | 64 bytes from 142.251.128.78: seq=449 ttl=61 time=18.068 ms
Elimina el servicio.
vagrant@master:~$ docker service rm pinger
pinger