resources/views/pages/frontend/docs/arquitectura-docker-swarm.md
Última modificación: 23 Oct, 2026

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
Edita este documento en >> arquitectura-docker-swarm.md
← Volver a la Landing Page