Aurora Libros ja es construeix i es desplega sola, però continua vivint en una màquina. Si aquesta màquina s'apaga, la llibreria desapareix d'internet. Aquesta lliçó munta el teu primer clúster amb Docker Swarm: l'orquestració més propera al que ja saps, perquè parla compose.yaml i comandes docker.
Contingut
- Què resol un orquestrador
- Arquitectura de Swarm: managers, workers i Raft
- L'estat desitjat i el bucle de reconciliació
- Muntar el clúster:
init,joini ports - Gestionar nodes: promoure, degradar i drenar
- Serveis i tasques
- Rèpliques davant del mode global
- Escalat i restriccions d'ubicació
- Xarxes overlay: VXLAN entre nodes
- La malla d'encaminament
- Stacks: el
compose.yamlque ja tens - La secció
deploycompleta - Secrets i configs natius de Swarm
- Aurora Libros en un clúster de tres nodes
- Swarm el 2026: quan continua sent l'elecció
Advertència. Un clúster multiplica les decisions de xarxa, emmagatzematge i control d'accés: els ports entre nodes, el xifratge del trànsit intern i la ubicació de les dades persistents s'han d'acordar amb el responsable d'infraestructura i de seguretat de la teva organització abans de portar res a producció.
- Què resol un orquestrador
| Problema | Compose en una màquina | Orquestrador |
|---|---|---|
| La màquina s'apaga | Tot cau | Les tasques es reprogramen en altres nodes |
| Un contenidor mor | restart: el reinicia allà mateix |
Es recrea on hi hagi lloc |
| Cal més capacitat | Només si cap en aquella màquina | S'afegeix un node al clúster |
| Publicar una versió | Recrear: buit sense servei | Substitució progressiva, sense tall |
| On col·locar cada servei? | No hi ha elecció | Planificador segons recursos i regles |
| Balancejar entre rèpliques | Nginx a mà | Balanceig integrat per nom de servei |
| Un desplegament surt malament | Tornes a desplegar a mà | Rollback automàtic (06-07) |
Un orquestrador aporta tres coses que Compose en una sola màquina no pot donar per definició: planificació (decidir en quin node va cada cosa), reconciliació (mantenir l'estat desitjat passi el que passi) i xarxa entre màquines (que els contenidors es parlin encara que siguin en amfitrions diferents).
- Arquitectura de Swarm: managers, workers i Raft
flowchart TB
subgraph P["Pla de control — Raft"]
M1["manager-1 (líder)"] <--> M2[manager-2]
M2 <--> M3[manager-3]
M3 <--> M1
end
M1 -->|assigna tasques| W1[worker-1]
M1 -->|assigna tasques| W2[worker-2]
W1 --- W2
style M1 fill:#e8f0fe,stroke:#3367d6
| Rol | Què fa | Quants |
|---|---|---|
| Manager | Guarda l'estat, planifica, exposa l'API, participa en Raft | 1, 3, 5 o 7 (senar) |
| Líder | El manager escollit que pren les decisions de planificació | 1, escollit automàticament |
| Worker | Només executa tasques; no pren decisions | Els que calguin |
L'estat del clúster —quins serveis existeixen, quantes rèpliques, quins secrets— es replica entre els managers amb l'algorisme de consens Raft. Per acceptar un canvi cal l'acord de la majoria, el quòrum, que és (N/2) + 1.
D'aquí surt la regla del nombre senar, que no és superstició sinó aritmètica:
| Managers | Quòrum | Fallades tolerades | Comentari |
|---|---|---|---|
| 1 | 1 | 0 | Desenvolupament. Si cau, no hi ha clúster |
| 2 | 2 | 0 | Pitjor que un: qualsevol caiguda bloqueja |
| 3 | 2 | 1 | El mínim raonable en producció |
| 4 | 3 | 1 | Igual que 3, amb més cost |
| 5 | 3 | 2 | Clústers grans |
La fila dels dos managers és la que sorprèn: afegir un segon manager empitjora la disponibilitat, perquè necessites tots dos vius per assolir el quòrum. I hi ha una conseqüència important que convé tenir clara: perdre el quòrum no atura els contenidors en marxa, però deixa el clúster sense capacitat de decidir; no es pot escalar, ni desplegar, ni reprogramar res fins que tornin prou managers.
- L'estat desitjat i el bucle de reconciliació
Aquí hi ha el canvi mental respecte de tot l'anterior. Amb docker run dones ordres; amb un orquestrador declares com vols que estigui el món i ell se n'encarrega.
El líder executa un bucle permanent: compara l'estat desitjat (el que has declarat) amb l'estat real (el que hi ha), i si difereixen crea o elimina tasques fins que coincideixin. Quan coincideixen, espera el canvi següent i torna a comparar.
docker service create --replicas 3 aurora-api no vol dir "arrenca tres contenidors": vol dir "vull que sempre n'hi hagi tres". En mates un i n'apareix un altre. Apagues un node sencer i les seves tasques renaixen als que queden. Ningú no ha executat cap comanda de reparació: el bucle simplement ha notat una diferència entre el desitjat i el real i l'ha corregida.
- Muntar el clúster:
init, join i ports
init, join i portsdocker swarm init --advertise-addr 10.0.1.10 # a manager-1
# Swarm initialized: current node (k3f9x...) is now a manager.
# To add a worker to this swarm, run the following command:
# docker swarm join --token SWMTKN-1-49nj1...-8vxv8 10.0.1.10:2377L'--advertise-addr és obligatori tan bon punt la màquina tingui més d'una interfície, i ometre'l és el primer error clàssic: Swarm tria una IP qualsevol —sovint la pública, o una d'una VPN— i els altres nodes no aconsegueixen arribar-hi.
docker swarm join-token worker # els tokens són diferents per rol
docker swarm join-token --rotate manager # si un token s'ha filtrat
docker swarm join --token SWMTKN-1-49nj1...-8vxv8 10.0.1.10:2377 # a cada worker
docker node ls # des de qualsevol manager
# ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE
# k3f9x * manager-1 Ready Active Leader 27.3.1
# p8m2q worker-1 Ready Active 27.3.1
# r5t7w worker-2 Ready Active 27.3.1| Port | Protocol | Per a què | Entre |
|---|---|---|---|
| 2377 | TCP | API de gestió del clúster | Nodes → managers |
| 7946 | TCP i UDP | Descobriment i gossip entre nodes | Tots ↔ tots |
| 4789 | UDP | Trànsit de dades VXLAN de les xarxes overlay | Tots ↔ tots |
Aquests tres ports causen la meitat dels problemes d'un clúster nou, i sempre pel mateix: 7946 necessita TCP i UDP, i 4789 és UDP. Un tallafocs que només obri TCP produeix el símptoma més desconcertant possible: els nodes apareixen Ready, els serveis es despleguen, però els contenidors de nodes diferents no es veuen entre ells. No obris mai el 2377 a internet: qui hi arribi amb un token es pot unir al teu clúster.
- Gestionar nodes: promoure, degradar i drenar
docker node promote worker-1 worker-2 # de worker a manager
docker node demote manager-3 # de manager a worker
docker node inspect worker-1 --format '{{.Status.State}} {{.Spec.Availability}}'
docker node update --label-add zona=a --label-add disc=ssd worker-1Availability |
Tasques noves | Tasques existents | Quan |
|---|---|---|---|
active |
Sí | Continuen | Normal |
pause |
No | Continuen | Investigar sense que n'arribin més |
drain |
No | Es mouen a altres nodes | Manteniment o retirada |
docker node update --availability drain worker-1 # buidar abans de tocar la màquina
# ... manteniment, reinici, actualització del kernel ...
docker node update --availability active worker-1El cicle drain → manteniment → active és la rutina bàsica d'operació d'un clúster: les tasques es recol·loquen soles abans que toquis res, i ningú no es queda sense servei. Compte amb un detall: en tornar a active, les tasques no tornen soles al node; es queden on són fins al desplegament següent.
- Serveis i tasques
Swarm introdueix dos conceptes nous sobre el que ja coneixes:
- Servei: la declaració. "Vull tres rèpliques d'aquesta imatge amb aquesta configuració".
- Tasca: cada unitat de treball assignada a un node. Una tasca acaba sent un contenidor, i és immutable: no es modifica ni es reinicia, se substitueix per una de nova.
docker service create --name aurora-api --replicas 3 \
--network aurora-posterior --env DB_HOST=aurora-db --publish published=8080,target=3000 \
--limit-memory 512M --limit-cpu 1.0 --reserve-memory 128M \
--health-interval 10s --health-retries 3 ghcr.io/auroralibros/aurora-api:2.0.0| Comanda | Què mostra | Equivalent que ja coneixes |
|---|---|---|
docker service ls |
Serveis i rèpliques preparades | docker compose ps |
docker service ps <svc> |
Cada tasca, el seu node i el seu historial | docker ps per servei |
docker service logs -f <svc> |
Logs agregats de totes les rèpliques | docker compose logs -f |
docker service inspect <svc> |
Especificació completa | docker inspect |
docker service update <svc> |
Canvia l'estat desitjat | Editar i up -d |
docker service rm <svc> |
Elimina el servei i les seves tasques | docker compose rm |
docker service ps és l'eina de diagnòstic principal, perquè mostra també les tasques mortes i per què van morir:
NAME NODE DESIRED STATE CURRENT STATE ERROR
aurora-api.1 worker-1 Running Running 4 minutes ago
aurora-api.2 worker-2 Running Running 4 minutes ago
aurora-api.3 worker-2 Running Running 40 seconds ago
\_ aurora-api.3 worker-1 Shutdown Failed 45 seconds ago "task: non-zero exit (78)"Aquesta última línia explica una història completa: la tasca 3 va morir a worker-1 amb el codi 78 que vas programar a 06-01, és a dir, configuració invàlida, i l'orquestrador la va recrear a worker-2. Sense la validació d'arrencada, aquí hi hauria un genèric exit (1).
- Rèpliques davant del mode global
| Mode | Quantes tasques | En afegir un node | Per a què |
|---|---|---|---|
--mode replicated (per defecte) |
Les que demanis | No canvia | Aplicacions: aurora-api, aurora-web |
--mode global |
Una per node, sempre | N'apareix una de nova | Agents: Promtail, cAdvisor, node-exporter |
--mode replicated-job |
N execucions i acaba | — | Migracions, tasques puntuals |
El mode global és exactament el que necessiten els recol·lectors de mètriques i logs del mòdul 5: cada node necessita el seu cAdvisor i el seu Promtail, i vols que apareguin sols a cada màquina que afegeixis al clúster sense recordar-te'n.
- Escalat i restriccions d'ubicació
docker service scale aurora-api=5 aurora-web=3 # diversos alhora
docker service update --replicas 2 aurora-api # equivalent
# Restriccions DURES: si no es compleixen, la tasca no es programa (queda Pending)
docker service update --constraint-add 'node.labels.disc==ssd' aurora-db
# Preferències TOVES: reparteixen, però no impedeixen
docker service update --placement-pref 'spread=node.labels.zona' aurora-api| Expressió | Significat |
|---|---|
node.role==worker |
Només en workers (manté els managers descarregats) |
node.labels.zona==a |
Només en nodes amb aquesta etiqueta |
node.hostname!=worker-2 |
En qualsevol menys aquest |
spread=node.labels.zona |
Repartiment equitatiu entre zones |
La diferència entre restricció i preferència importa molt a la pràctica: una restricció impossible deixa el servei en Pending per sempre, sense cap error evident, mentre que una preferència només influeix en el repartiment. Si un servei no arrenca i docker service ps no mostra cap error, sospita d'una restricció que cap node no compleix.
- Xarxes overlay: VXLAN entre nodes
La xarxa bridge de 03-05 només connecta contenidors del mateix amfitrió. Una xarxa overlay connecta contenidors de nodes diferents com si compartissin un cable.
docker network create -d overlay --attachable --subnet 10.10.0.0/24 aurora-frontal
docker network create -d overlay --opt encrypted --internal aurora-posteriorPer dins és un túnel VXLAN: cada paquet Ethernet del contenidor s'encapsula dins d'un datagrama UDP cap al port 4789 del node destí, que el desencapsula i l'entrega. Per als contenidors, l'existència de dues màquines és invisible; continuen resolent aurora-db per DNS i parlant per la seva IP de la xarxa overlay.
| Opció | Efecte | Cost |
|---|---|---|
--attachable |
Permet connectar contenidors solts, no només serveis | Cap |
--opt encrypted |
Xifra amb IPsec el trànsit entre nodes | 10-30 % de rendiment |
--internal |
Sense sortida a internet | Cap |
--subnet |
Rang fix, evita col·lisions amb la teva xarxa corporativa | Cap |
El --opt encrypted mereix una decisió conscient. El trànsit VXLAN viatja en clar per defecte: si els teus nodes són en una xarxa privada d'un mateix centre de dades pot ser acceptable, però si travessen internet o una xarxa compartida, el catàleg, les consultes i les credencials que hi passin són llegibles per qualsevol amb accés al medi. És exactament el tipus de decisió que has de validar amb el teu responsable de seguretat. Compte també: el xifratge s'aplica al pla de dades, i no es pot activar ni desactivar sense recrear la xarxa.
- La malla d'encaminament
flowchart TB
C[Client] -->|:8080| N2["worker-2<br/>(sense rèplica local)"]
N2 -->|IPVS per la overlay| T1["tasca 1 @ worker-1"]
N2 --> T2["tasca 2 @ manager-1"]
style N2 fill:#fff4e5,stroke:#e8a33d
Quan publiques un port en mode ingress (el predeterminat), tots els nodes del clúster hi escolten, fins i tot els que no executen cap rèplica d'aquell servei. El node que rep la connexió la reparteix per IPVS entre les tasques vives, siguin on siguin.
L'avantatge és enorme per al balancejador d'entrada: apunta a qualsevol node, o a tots, i no li cal saber on és res. Els inconvenients són dos i convé conèixer-los: hi ha un salt de xarxa extra quan el node que rep no té la tasca, i la IP d'origen que veu la teva aplicació és la del node, no la del client real, perquè hi ha SNAT pel mig.
| Mode de publicació | Sintaxi | Comportament |
|---|---|---|
ingress (per defecte) |
--publish 8080:3000 |
Tots els nodes escolten; balanceig per IPVS |
host |
--publish mode=host,published=8080,target=3000 |
Només els nodes amb rèplica; IP real del client, sense salt extra |
El mode host és la sortida quan necessites la IP del client (registre d'accés, límits per IP, geolocalització) o l'últim gram de latència, i té un preu: obliga el balancejador extern a saber en quins nodes hi ha rèpliques, i no pots tenir dues rèpliques del mateix servei en un node, perquè xocarien al port.
- Stacks: el
compose.yaml que ja tens
compose.yaml que ja tensAquí arriba la recompensa de tot el mòdul 4: docker stack deploy accepta el teu compose.yaml.
docker stack deploy -c compose.swarm.yaml --with-registry-auth aurora
docker stack ls
docker stack services aurora
docker stack ps aurora --no-trunc
docker stack rm auroraEl --with-registry-auth és imprescindible amb un registre d'imatges privat com ghcr.io: sense ell, el manager entén les credencials però no les reenvia als workers, i les tasques fallen amb no basic auth credentials a tots els nodes menys on vas fer docker login.
| Clau de Compose | A Swarm |
|---|---|
image, environment, networks, ports, secrets, configs, healthcheck |
Funcionen igual |
deploy: |
Només aquí té efecte (Compose la ignorava llevat de resources) |
build: |
Ignorada: cal publicar la imatge en un registre d'imatges |
depends_on: |
Ignorada: no hi ha ordre d'arrencada, hi ha reintents |
restart:, container_name, develop, profiles |
Ignorades |
volumes: amb ruta relativa |
Perillosa: es resol a cada node |
Les dues ignorades del mig canvien com dissenyes l'aplicació. Sense build, el flux obliga a passar pel registre d'imatges, cosa que és correcta en producció. I sense depends_on, la teva API ha de suportar arrencar abans que la base de dades: per això vas implementar el reintent amb backoff a 04-04, i per això les sondes de 06-01 retornen 503 mentre les dependències no hi siguin.
- La secció
deploy completa
deploy completaservices:
aurora-api:
image: ghcr.io/auroralibros/aurora-api:2.0.0
deploy:
mode: replicated
replicas: 3
placement:
constraints: ["node.role==worker"]
preferences: [{ spread: node.labels.zona }]
max_replicas_per_node: 2
resources:
limits: { cpus: "1.0", memory: 512M }
reservations: { cpus: "0.1", memory: 128M } # el planificador SÍ que les respecta
restart_policy:
condition: on-failure # any | on-failure | none
delay: 5s
max_attempts: 3
window: 120s
update_config: # es detalla a 06-07
parallelism: 1
delay: 10s
order: start-first
failure_action: rollback
rollback_config: { parallelism: 2, order: stop-first }
labels: { com.aurora.component: api }Un matís que separa reservations de limits i que a Compose no existia: a Swarm, les reserves afecten la planificació. El planificador suma les reserves de les tasques ja ubicades a cada node i només en col·loca una de nova on càpiga. Reservar de més deixa nodes buits amb serveis en Pending; reservar de menys amuntega tasques fins que el node es queda sense memòria. Els números mesurats de 06-01 són just el que necessites aquí.
- Secrets i configs natius de Swarm
printf 'clau-ficticia-aurora' | docker secret create aurora_db_password -
docker config create aurora_init_sql ./db/init.sql
docker secret ls && docker config lsservices:
aurora-api:
secrets:
- source: aurora_db_password
target: db_password # queda a /run/secrets/db_password
environment:
DB_PASSWORD_FILE: /run/secrets/db_password # el patró _FILE de 06-01, intacte
secrets:
aurora_db_password:
external: true
configs:
aurora_init_sql:
external: trueEls secrets viatgen als nodes xifrats per TLS mutu, es desen xifrats a l'estat Raft i es munten en un tmpfs que no toca mai el disc; l'exercici 3 ho comprova dimensió per dimensió. A més, un secret és immutable: no es pot modificar. Rotar-lo consisteix a crear-ne un de nou amb un altre nom, actualitzar el servei amb --secret-rm i --secret-add i eliminar el vell. És incòmode expressament: força que la rotació sigui un desplegament traçable i no un canvi silenciós.
Els configs funcionen igual però sense xifratge en repòs, i són la via natural per a init.sql, nginx.conf o prometheus.yml: fitxers de configuració que han d'arribar a tots els nodes sense haver-los de copiar a mà.
- Aurora Libros en un clúster de tres nodes
# compose.swarm.yaml — stack aurora (extracte dels quatre serveis)
services:
aurora-web:
image: nginx:alpine
ports: ["80:80"]
configs: [{ source: aurora_nginx, target: /etc/nginx/conf.d/default.conf }]
networks: [frontal]
deploy:
mode: global # una per node: qualsevol atén
update_config: { parallelism: 1, order: start-first }
aurora-api:
image: ghcr.io/auroralibros/aurora-api:2.0.0
networks: [frontal, posterior]
environment:
DB_HOST: aurora-db
DB_USER: aurora
DB_NAME: aurora_llibres
DB_PASSWORD_FILE: /run/secrets/db_password
REDIS_HOST: aurora-cache
secrets: [{ source: aurora_db_password, target: db_password }]
healthcheck:
test: ["CMD","node","-e","require('http').get('http://127.0.0.1:3000/salut/preparat',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"]
interval: 10s
start_period: 20s
deploy:
replicas: 3
placement: { constraints: ["node.role==worker"] }
resources: { limits: { memory: 512M, cpus: "1.0" }, reservations: { memory: 128M } }
update_config: { parallelism: 1, delay: 15s, order: start-first, failure_action: rollback }
aurora-cache:
image: redis:7-alpine
command: ["redis-server","--maxmemory","200mb","--maxmemory-policy","allkeys-lru"]
networks: [posterior]
deploy: { replicas: 1, resources: { limits: { memory: 256M } } }
aurora-db:
image: postgres:16-alpine
environment: { POSTGRES_USER: aurora, POSTGRES_DB: aurora_llibres, POSTGRES_PASSWORD_FILE: /run/secrets/db_password }
secrets: [{ source: aurora_db_password, target: db_password }]
volumes: ["aurora-dades:/var/lib/postgresql/data"]
configs: [{ source: aurora_init_sql, target: /docker-entrypoint-initdb.d/init.sql }]
networks: [posterior]
deploy:
replicas: 1
placement: { constraints: ["node.labels.dades==si"] } # ancorada al node del volum
update_config: { order: stop-first } # mai dos PostgreSQL sobre les mateixes dades
networks:
frontal: { driver: overlay }
posterior: { driver: overlay, internal: true, driver_opts: { encrypted: "" } }
volumes: { aurora-dades: {} }
secrets: { aurora_db_password: { external: true } }
configs:
aurora_nginx: { external: true }
aurora_init_sql: { external: true }docker node update --label-add dades=si worker-1
docker stack deploy -c compose.swarm.yaml --with-registry-auth aurora
docker stack services auroraNAME MODE REPLICAS IMAGE PORTS
aurora_aurora-api replicated 3/3 ghcr.io/auroralibros/aurora-api:2.0.0
aurora_aurora-cache replicated 1/1 redis:7-alpine
aurora_aurora-db replicated 1/1 postgres:16-alpine
aurora_aurora-web global 3/3 nginx:alpine *:80->80/tcpAdvertència sobre les dades. La
constraintd'aurora-dbés un pedaç, no una solució. Els volums locals no viatgen: siworker-1mor, Swarm recrearà la tasca de PostgreSQL en un altre node i hi trobarà un volum buit, amb la qual cosa el catàleg desapareix. Un clúster de debò necessita emmagatzematge de xarxa (NFS, iSCSI, Ceph, un volum de bloc del proveïdor de núvol) o una base de dades gestionada fora del clúster. Discuteix-ho amb el teu responsable d'infraestructura abans de posar-hi dades reals: no és un detall de configuració, és una decisió d'arquitectura.
- Swarm el 2026: quan continua sent l'elecció
Swarm continua inclòs a Docker Engine i mantingut, però és minoritari: la indústria va estandarditzar Kubernetes, i allà hi ha els proveïdors gestionats, les eines i la major part de la documentació.
| Situació | Elecció raonable |
|---|---|
| 2-10 nodes, un equip petit, sense plataforma dedicada | Swarm |
| L'equip ja domina Compose i no hi ha temps de formació | Swarm |
| Un clúster a les instal·lacions del client, mantingut per altres | Swarm |
| Núvol gestionat (EKS, GKE, AKS) disponible | Kubernetes |
| Autoescalat, operadors, service mesh, polítiques, ecosistema | Kubernetes |
La comparació detallada la faràs a 07-02. El que sí que convé retenir: gairebé tot el d'aquesta lliçó —estat desitjat, reconciliació, serveis i rèpliques, xarxa entre nodes, secrets muntats com a fitxers, actualitzacions progressives— és exactament el mateix model mental que faràs servir a Kubernetes, amb altres noms. Swarm no ha estat una desviació: ha estat la introducció senzilla al mateix problema.
Errors Habituals i Consells
- Dos managers. Empitjora la disponibilitat respecte d'un. Sempre 1, 3, 5 o 7.
- Oblidar
--advertise-addr. Amb diverses interfícies, Swarm tria malament i el clúster no es forma. Posa-ho sempre explícit. - Obrir només TCP al tallafocs. El 7946 necessita UDP també i el 4789 és UDP pur. El símptoma són nodes
Readyamb la overlay morta. - Esperar que
build:funcioni. Swarm no construeix: publica la imatge abans i referencia-la per etiqueta o digest. I no oblidis--with-registry-auth, o les tasques fallaran a tots els nodes menys on vas ferdocker login. - Volums locals per a dades. La tasca es reprograma i el volum no la segueix. Emmagatzematge de xarxa o base de dades externa.
- Restriccions que cap node no compleix. El servei es queda en
Pendingsense missatge clar. Verifica-ho ambdocker node inspect. - Consell: fes servir
docker service ps --no-trunc. La columnaERRORcompleta sol contenir la causa exacta de la fallada. - Consell: etiqueta els nodes des del primer dia (
zona,disc,dades). Reubicar serveis després és qüestió d'una restricció.
Exercicis
Exercici 1. Munta un clúster de tres nodes, desplega aurora-api amb tres rèpliques i demostra la reconciliació: mata un contenidor a mà i després buida un node sencer, comprovant en cada cas on reapareixen les tasques.
Exercici 2. Demostra la malla d'encaminament: publica aurora-web amb una sola rèplica, comprova que respon des dels tres nodes i esbrina des de quin s'està servint realment.
Exercici 3. Compara un secret de Swarm amb una variable d'entorn: intenta extreure'n els dos valors des de fora del contenidor amb docker inspect i des de dins amb /proc, i explica on viu físicament cadascun.
Solucions
Solució 1.
docker service ps aurora_aurora-api --format '{{.Name}}\t{{.Node}}\t{{.CurrentState}}'
docker kill "$(docker ps -q --filter name=aurora_aurora-api)" # executat a worker-1
sleep 8
docker service ps aurora_aurora-api --format '{{.Name}}\t{{.Node}}\t{{.CurrentState}}'aurora_aurora-api.1 worker-1 Running 6 minutes ago
aurora_aurora-api.2 worker-2 Running 6 minutes ago
aurora_aurora-api.3 worker-2 Running 6 minutes ago
--- després del kill ---
aurora_aurora-api.1 worker-1 Running 5 seconds ago
\_ aurora_aurora-api.1 worker-1 Failed 7 seconds ago "task: non-zero exit (137)"docker node update --availability drain worker-2 && sleep 15
docker service ps aurora_aurora-api --filter desired-state=running --format '{{.Name}}\t{{.Node}}'
docker service ls --filter name=aurora_aurora-api
# aurora_aurora-api.1 worker-1
# aurora_aurora-api.2 manager-1
# aurora_aurora-api.3 worker-1
# aurora_aurora-api replicated 3/3Les dues proves mostren la mateixa maquinària a dues escales. En matar el contenidor, la tasca .1 es va recrear al mateix node, perquè el node continuava sa i era el lloc preferit; el 137 és el SIGKILL del teu docker kill, i l'historial amb \_ conserva l'intent fallit. En drenar worker-2, les seves dues tasques es van reubicar als altres dos nodes, i el recompte va tornar a 3/3 en uns quinze segons.
L'important és el que no vas fer: no vas executar cap comanda de reparació. Només vas declarar "en vull tres" una vegada, i el bucle de reconciliació manté aquella afirmació certa davant d'un contenidor mort, un node drenat o una màquina apagada de cop. És el mateix mecanisme que a Kubernetes governa un Deployment, i per això restart: always deixa de tenir sentit aquí: la política de reinici és del servei, no del contenidor.
Fixa't també que la tasca .2 va anar a parar a manager-1. Amb la constraint node.role==worker del compose.swarm.yaml no hi hauria pogut anar: s'hauria quedat en Pending fins que worker-2 tornés. És el compromís entre protegir els managers i tenir lloc on reprogramar.
Solució 2.
docker service update --replicas 1 aurora_aurora-web
docker service ps aurora_aurora-web --filter desired-state=running --format '{{.Node}}'
for n in manager-1 worker-1 worker-2; do
printf '%s -> %s\n' "$n" "$(curl -s -o /dev/null -w '%{http_code}' http://$n/salut/viu)"
done
# worker-2
# manager-1 -> 200
# worker-1 -> 200
# worker-2 -> 200Els tres nodes responen 200 encara que només worker-2 executa la rèplica. Això és la malla d'encaminament: en publicar en mode ingress, cada node obre el port 80 i reenvia per IPVS a través de la xarxa overlay fins on siguin les tasques vives.
docker service logs aurora_aurora-web --tail 3
sudo iptables -t nat -L DOCKER-INGRESS -n | head -4
# 10.0.0.2 - - [05/Aug/2026:11:42:07] "GET /salut/viu HTTP/1.1" 200
# DNAT tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.18.0.2:80I aquí apareix l'efecte secundari que cal conèixer: la IP registrada és 10.0.0.2, una adreça de la xarxa ingress, no la del client real. El DNAT de la taula nat reescriu el destí i, pel camí, l'SNAT substitueix l'origen. Si Aurora Libros necessités la IP real —per limitar peticions per client o per a analítica—, hauries de publicar en mode=host, acceptant que llavors només respondrien els nodes amb rèplica i que el balancejador extern ho ha de saber.
L'avantatge compensa en la majoria dels casos: el balancejador d'entrada pot apuntar als tres nodes sense saber res d'on viu cada servei, i afegir o treure rèpliques no l'obliga a canviar la seva configuració.
Solució 3.
docker service create --name prova-env --env CLAU=ficticia-a-entorn alpine sleep 600
docker service create --name prova-sec --secret aurora_db_password alpine sleep 600
docker service inspect prova-env --format '{{.Spec.TaskTemplate.ContainerSpec.Env}}'
docker service inspect prova-sec --format '{{json .Spec.TaskTemplate.ContainerSpec.Secrets}}' | jq -r '.[0].SecretName'
# [CLAU=ficticia-a-entorn]
# aurora_db_passwordL'asimetria ja és total a la primera comprovació: la variable es llegeix sencera a l'especificació del servei, que qualsevol amb accés a l'API de Docker pot consultar; del secret només se'n veu el nom i el punt de muntatge.
cid=$(docker ps -q --filter name=prova-sec)
docker exec "$cid" cat /run/secrets/aurora_db_password; echo
docker exec "$cid" df -h /run/secrets | tail -1
sudo tr '\0' '\n' < /proc/$(docker inspect "$cid" --format '{{.State.Pid}}')/environ | grep -c CLAU
# clau-ficticia-aurora
# tmpfs 64M 4.0K 64M 1% /run/secrets
# 0| Dimensió | Variable d'entorn | Secret de Swarm |
|---|---|---|
A docker service inspect |
Valor complet | Només el nom |
A /proc/<pid>/environ |
Llegible | Absent |
| Suport físic | Memòria del procés | tmpfs de 64 MB (RAM) |
| A l'estat del clúster | En clar a Raft | Xifrat a Raft |
| Herència a subprocessos | Automàtica | Cap |
| En aturar la tasca | — | Es desmunta i desapareix |
El tmpfs de la segona línia és la clau del disseny: el secret no toca mai el disc en cap node. En aturar la tasca, el sistema de fitxers es desmunta i el valor deixa d'existir; no queda en cap capa d'imatge, ni en un volum, ni en un fitxer de log, ni en una còpia de seguretat del sistema de fitxers del node.
La fila de l'herència és la que més incidents provoca a la pràctica. Una variable d'entorn la veuen tots els subprocessos: si la teva API invoca pg_dump, un script de desplegament o qualsevol eina de tercers, aquesta contrasenya hi va amb ells, i n'hi ha prou que una llibreria registri el seu entorn en fallar —cosa que fan moltes— perquè la credencial acabi en un servei extern de gestió d'errors. El fitxer de /run/secrets/ només el llegeix qui decideix llegir-lo, que és justament el que fa el teu config.js de 06-01, una vegada, en arrencar.
Conclusió
Aurora Libros ja no depèn d'una màquina. Saps què aporta un orquestrador —planificació, reconciliació i xarxa entre nodes— i has muntat un clúster de Swarm amb docker swarm init --advertise-addr, tokens diferents per rol i els tres ports que cal obrir, amb el detall que costa tardes senceres: 7946 en TCP i UDP, 4789 en UDP. Entens per què els managers han de ser senars i per què dos són pitjor que un, i domines la rutina de drain → manteniment → active que permet tocar una màquina sense que ningú deixi de comprar llibres.
Has canviat de model mental: ja no dones ordres, declares un estat desitjat i un bucle el manté cert. Ho has comprovat matant un contenidor i drenant un node sencer, veient com les tasques reapareixen soles sense que executessis cap reparació. Distingeixes servei de tasca, rèpliques de mode global —el que necessiten Promtail i cAdvisor—, i saps que una restricció impossible deixa un servei en Pending mut. Has creat xarxes overlay sobre túnels VXLAN, amb la decisió conscient sobre --opt encrypted per al trànsit que travessa xarxes que no controles, i has vist la malla d'encaminament respondre des dels tres nodes amb una sola rèplica viva, juntament amb el seu preu: la IP que registra Nginx és la de la xarxa ingress, no la del client.
El compose.yaml del mòdul 4 s'ha convertit en un stack amb docker stack deploy, amb la taula del que Swarm ignora —build, depends_on, restart— i la secció deploy completa, on les reservations ja no són decoratives perquè governen la planificació. Els secrets natius han demostrat el seu avantatge de manera mesurable: invisibles a inspect, absents de /proc/<pid>/environ, muntats en un tmpfs que no toca mai el disc i sense heretar-se als subprocessos, mentre el patró _FILE de 06-01 continua funcionant sense tocar ni una línia de codi. I queda anotada en vermell la limitació real: els volums locals no viatgen, així que aurora-db està ancorada per una etiqueta de node i això és un pedaç que cal substituir per emmagatzematge de xarxa o una base de dades gestionada abans de posar-hi dades reals.
A la lliçó següent, Introducció a Kubernetes, canvies d'eina però no d'idees. Veuràs el pla de control amb el seu kube-apiserver, el seu etcd i el seu planificador, els objectes fonamentals —Pod, Deployment, Service, Ingress, ConfigMap, Secret, PVC— amb el seu YAML mínim comentat, el paper central de les etiquetes i els selectors, la taula de kubectl traduïda a les comandes de Docker que ja domines, i muntaràs un clúster local amb kind per fer els teus primers passos.
Docker: De Principiant a Avançat
Mòdul 1: Introducció a Docker
- Què és Docker?
- Instal·lant Docker
- Arquitectura de Docker
- Comandes Bàsiques de Docker
- Entenent les Imatges de Docker
- Creant el teu Primer Contenidor Docker
- El Projecte del Curs: la Plataforma Aurora Libros
Mòdul 2: Treballant amb Imatges Docker
- Docker Hub i Repositoris
- Construint Imatges Docker
- Conceptes Bàsics de Dockerfile
- Instruccions Avançades del Dockerfile
- Gestionant Imatges Docker
- Etiquetatge i Publicació d'Imatges
Mòdul 3: Contenidors Docker
- Executant Contenidors
- Cicle de Vida del Contenidor
- Gestionant Contenidors
- Inspecció i Depuració de Contenidors
- Xarxes a Docker
- Persistència de Dades amb Volums
- Límits de Recursos i Polítiques de Reinici
Mòdul 4: Docker Compose
- Introducció a Docker Compose
- Definint Serveis a Docker Compose
- Comandes de Docker Compose
- Aplicacions Multi-Contenidor
- Variables d'Entorn a Docker Compose
- Perfils, Overrides i Múltiples Entorns
- Desenvolupament Local amb Docker Compose
Mòdul 5: Conceptes Avançats de Docker
- Aprofundiment en Xarxes Docker
- Opcions d'Emmagatzematge Docker
- Millors Pràctiques de Seguretat a Docker
- Optimitzant Imatges Docker
- Builds Avançades amb BuildKit i Buildx
- Registre i Monitoratge a Docker
- El Runtime per Dins: Namespaces, Cgroups i Capes
Mòdul 6: Docker en Producció
- Preparar una Imatge per a Producció
- CI/CD amb Docker
- Orquestrant Contenidors amb Docker Swarm
- Introducció a Kubernetes
- Desplegant Contenidors Docker a Kubernetes
- Escalat i Balanceig de Càrrega
- Estratègies de Desplegament i Rollback
