Arquitectura en Docker Swarm (Orquestación de Clúster y Alta Disponibilidad)
Mientras que Docker Engine gestiona contenedores individuales en una sola máquina, Docker Swarm convierte un conjunto de servidores Linux en un único clúster de alta disponibilidad (High Availability) para Mañuti.
En esta arquitectura dejamos de definir web01, web02 y web03 manualmente. En su lugar, declaramos un único servicio web con replicas: 3 sobre una red overlay (swarm_net), utilizamos Docker Secrets para proteger credenciales, Docker Configs para distribuir la configuración de Nginx y un sistema de archivos compartido NFS para que cualquier contenedor pueda migrar entre servidores físicos sin perder su estado.
1. Ventajas Clave sobre Docker Puro
| Característica | Docker Compose Estándar | Docker Swarm (Stack) |
|---|---|---|
| Alcance | Un solo servidor (Single-Host). | Clúster multi-servidor (Multi-Node). |
| Alta Disponibilidad | Si el servidor cae, el sistema muere. | Auto-curación (Self-Healing): Si un Worker cae, Swarm reprograma las réplicas en los nodos vivos automáticamente. |
| Escalabilidad | Duplicar bloques YAML y editar Nginx. | Un solo comando: docker service scale mi-cluster_web=5. |
| Balanceo Interno | Configuración manual de IPs/Hostnames. | Ingress Routing Mesh y DNS interno (server web:9000;). |
| Despliegues | Recreación con micro-cortes. | Rolling Updates (parallelism: 1, delay: 10s) con Rollback automático. |
2. Topología del Clúster (Laboratorio de 3 Nodos)
| Nodo | Hostname | IP Privada | Rol en Swarm | Funciones Adicionales |
|---|---|---|---|---|
| Nodo 1 | swarm-manager |
192.168.1.162 |
Manager (Leader) | Orquestador, Servidor NFS (/srv/swarm_data), LB Nginx, MariaDB |
| Nodo 2 | swarm-worker-1 |
192.168.1.163 |
Worker | Ejecución distribuida de Réplicas Web, Horizon, Reverb, Redis, Mongo |
| Nodo 3 | swarm-worker-2 |
192.168.1.164 |
Worker | Ejecución distribuida de Réplicas Web, Horizon, Reverb, MinIO |
(Puertos requeridos entre nodos para el plano de control y red Overlay: 2377/tcp, 7946/tcp/udp y 4789/udp).
3. Fase 1: Preparación de los Nodos y Almacenamiento NFS
3.1. Resolución de Nombres (/etc/hosts en los 3 Nodos)
Agrega al final de /etc/hosts en los tres servidores:
192.168.1.162 swarm-manager
192.168.1.163 swarm-worker-1
192.168.1.164 swarm-worker-2
3.2. Instalar Docker Engine y Autenticar en GHCR (En los 3 Nodos)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# Autenticar contra GitHub Container Registry en TODOS los nodos
echo "TU_GITHUB_PAT" | sudo docker login ghcr.io -u TU_USUARIO_GITHUB --password-stdin
3.3. Configurar el Servidor de Almacenamiento Compartido NFS
En Swarm, si un contenedor cambia del Worker 1 al Worker 2, sus datos deben seguirlo por la red:
A. En swarm-manager (192.168.1.162 - Servidor NFS):
sudo apt-get update && sudo apt-get install -y nfs-kernel-server
# Crear estructura de directorios persistentes
sudo mkdir -p /srv/swarm_data/{mariadb,redis,mongo,minio,assets}
sudo chown -R nobody:nogroup /srv/swarm_data
sudo chmod -R 777 /srv/swarm_data
# Exportar volumen a la subred local
echo "/srv/swarm_data 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
sudo exportfs -rav
sudo systemctl restart nfs-kernel-server
B. En swarm-worker-1 y swarm-worker-2 (Clientes NFS):
sudo apt-get update && sudo apt-get install -y nfs-common
4. Fase 2: Inicialización del Clúster, Red Overlay, Secrets y Configs
4.1. Iniciar Swarm y Unir Workers
# 1. Ejecutar en swarm-manager:
docker swarm init --advertise-addr 192.168.1.162
# 2. Copiar el comando 'docker swarm join --token SWMTKN-1-... 192.168.1.162:2377'
# y ejecutarlo en swarm-worker-1 y swarm-worker-2.
# 3. Verificar en el Manager que los 3 nodos estén en estado Ready:
docker node ls
4.2. Crear la Red Overlay y la Bóveda de Secretos (En swarm-manager)
# Red multi-host cifrada y acoplable
docker network create --driver overlay --attachable swarm_net
# Bóveda de Docker Secrets (montados en RAM dentro de /run/secrets/)
printf "kls2qr824" | docker secret create db_password -
printf "root" | docker secret create db_root_password -
printf "tCT8H83o7U7Gm2" | docker secret create minio_secret_key -
4.3. Crear el Docker Config para el Balanceador Nginx (nginx_swarm.conf)
En Swarm, el bloque upstream apunta simplemente a server web:9000;. El balanceador interno (VIP) de Swarm distribuye las peticiones entre las N réplicas activas en cualquier nodo:
upstream web_cluster {
server web:9000;
}
upstream reverb_cluster {
server reverb:8080;
}
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
error_log /dev/stderr;
access_log /dev/stdout;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ ^/app/ {
proxy_pass http://reverb_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
location ~ \.php$ {
fastcgi_pass web_cluster;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 300s;
fastcgi_send_timeout 300s;
fastcgi_read_timeout 300s;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
}
Registrar la configuración en el clúster:
docker config create nginx_conf_v1 nginx_swarm.conf
5. Fase 3: El Archivo Maestro stack-deploy.yml
Crea el archivo stack-deploy.yml en el nodo Manager:
services:
# 1. LOAD BALANCER (NGINX)
lb:
image: nginx:alpine
volumes:
- assets_data:/var/www/html/public:ro
ports:
- "80:80"
- "443:443"
configs:
- source: nginx_conf_v1
target: /etc/nginx/conf.d/default.conf
networks:
- swarm_net
deploy:
mode: replicated
replicas: 1
placement:
constraints: [node.role == manager]
# 2. APLICACIÓN WEB (3 RÉPLICAS DISTRIBUIDAS CON ROLLING UPDATES)
web:
image: ghcr.io/activemediacdmx/manuti:v12.00.02
volumes:
- assets_data:/var/www/html/public
environment: &app_env
APP_KEY: base64:GKY3hXtvMAYs24+yWv3VYxM/x4riaOODbOVypz5wWJQ=
APP_URL: "http://192.168.1.162"
APP_NAME: "Mañutí v12"
APP_ENV: production
APP_DEBUG: "false"
APP_TIMEZONE: America/Mexico_City
DB_HOST: mariadb
DB_PORT: 3306
DB_DATABASE: framedev_db
DB_USERNAME: framedev
DB_PASSWORD: kls2qr824
REDIS_HOST: redis
REDIS_PORT: 6379
MONGODB_DSN: "mongodb://mongo:27017/framedev_logs"
AWS_ACCESS_KEY_ID: minioadmin
AWS_SECRET_ACCESS_KEY: tCT8H83o7U7Gm2
AWS_BUCKET: framedev-bucket
AWS_URL: "http://192.168.1.162:9000/framedev-bucket"
AWS_ENDPOINT: "http://192.168.1.162:9000"
REVERB_APP_ID: 148200
REVERB_APP_KEY: kr9xqenc91s2vmyyaqv7
REVERB_APP_SECRET: smdsjrgccehbv4yuyle9
REVERB_HOST: reverb
REVERB_PORT: 8080
REVERB_SCHEME: http
VITE_REVERB_APP_KEY: kr9xqenc91s2vmyyaqv7
VITE_REVERB_HOST: 192.168.1.162
VITE_REVERB_PORT: 80
VITE_REVERB_SCHEME: http
networks:
- swarm_net
deploy:
mode: replicated
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
# 3. REVERB (WEBSOCKETS)
reverb:
image: ghcr.io/activemediacdmx/manuti:v12.00.02
environment: *app_env
command: php artisan reverb:start --host=0.0.0.0 --port=8080
networks:
- swarm_net
deploy:
replicas: 1
# 4. WORKER (COLAS HORIZON)
worker:
image: ghcr.io/activemediacdmx/manuti:v12.00.02
environment: *app_env
command: php artisan horizon
networks:
- swarm_net
deploy:
replicas: 1
# 5. BASES DE DATOS Y STORAGE (CON SECRETOS Y PERSISTENCIA NFS)
mariadb:
image: mariadb:11.2
secrets:
- db_root_password
- db_password
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_DATABASE: framedev_db
MYSQL_USER: framedev
volumes:
- mariadb_data:/var/lib/mysql
networks:
- swarm_net
ports:
- "3306:3306"
deploy:
placement:
constraints: [node.role == manager]
redis:
image: redis:alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- swarm_net
mongo:
image: mongo:latest
volumes:
- mongo_data:/data/db
networks:
- swarm_net
ports:
- "27017:27017"
minio:
image: minio/minio
command: server /data --console-address ":9001"
secrets:
- minio_secret_key
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD_FILE: /run/secrets/minio_secret_key
volumes:
- minio_data:/data
ports:
- "9000:9000"
- "9001:9001"
networks:
- swarm_net
networks:
swarm_net:
external: true
secrets:
db_password:
external: true
db_root_password:
external: true
minio_secret_key:
external: true
configs:
nginx_conf_v1:
external: true
# VOLÚMENES MONTADOS POR RED VÍA NFS (192.168.1.162)
volumes:
mariadb_data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.162,rw
device: ":/srv/swarm_data/mariadb"
redis_data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.162,rw
device: ":/srv/swarm_data/redis"
mongo_data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.162,rw
device: ":/srv/swarm_data/mongo"
minio_data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.162,rw
device: ":/srv/swarm_data/minio"
assets_data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.162,rw
device: ":/srv/swarm_data/assets"
6. Fase 4: Despliegue del Stack, Migraciones y Operación
6.1. Desplegar el Clúster propagando credenciales de GHCR
docker stack deploy -c stack-deploy.yml mi-cluster --with-registry-auth --resolve-image always
docker service ls
6.2. Inicializar Migraciones y Buckets MinIO en el Clúster
# 1. Identificar el contenedor de una réplica web y correr migraciones
WEB_CONTAINER=$(docker ps -q -f name=mi-cluster_web)
sudo docker exec $WEB_CONTAINER php artisan migrate --seed --force
# 2. Identificar el contenedor de MinIO y configurar buckets
MINIO_CONTAINER=$(docker ps -q -f name=mi-cluster_minio)
sudo docker exec $MINIO_CONTAINER mc alias set local http://localhost:9000 minioadmin tCT8H83o7U7Gm2
sudo docker exec $MINIO_CONTAINER mc mb local/framedev-bucket
sudo docker exec $MINIO_CONTAINER mc anonymous set download local/framedev-bucket/avatars
sudo docker exec $MINIO_CONTAINER mc anonymous set download local/framedev-bucket/architecture
7. Comandos de Monitoreo, Escalado y Mantenimiento de Nodos
| Operación | Comando en swarm-manager |
|---|---|
| Monitoreo en tiempo real | watch -n 2 "docker stack ps mi-cluster" |
| Escalar a 6 réplicas Web al vuelo | docker service scale mi-cluster_web=6 |
| Ver logs combinados de las réplicas | docker service logs -f mi-cluster_web |
| Drenar un nodo para mantenimiento | docker node update --availability drain swarm-worker-1 |
| Reactivar un nodo drenado | docker node update --availability active swarm-worker-1 |
| Forzar actualización del LB | docker service update --force mi-cluster_lb |