Docker multinodo sin Kubernetes: opciones reales y límites estrictos

¿Te fue útil?

Tienes más de un servidor. Tienes contenedores. No quieres Kubernetes—quizá porque tu equipo es pequeño,
tus cargas son aburridas (bien), o ya tienes suficientes piezas móviles que te despiertan a las 03:00.
Pero aún quieres “multi-host”: programación, descubrimiento de servicios, conmutación por error y actualizaciones que no impliquen
ruleta de SSH.

La trampa: “Docker entre hosts” no es una sola característica. Es una pila de decisiones sobre redes, identidad,
almacenamiento y semánticas de fallo. Puedes hacerlo sin Kubernetes. Solo necesitas aceptar
los límites desde el principio—especialmente alrededor del almacenamiento con estado y la red—y elegir un enfoque que coincida con tu
madurez operativa.

Qué significa realmente “Docker multinodo”

Cuando la gente dice “Docker multinodo”, normalmente se refiere a una o más de estas capacidades:

  • Programación: elegir un host para un contenedor y recolocarlo tras un fallo.
  • Descubrimiento de servicios: encontrar “la cosa” por nombre, no por IP.
  • Redes: los contenedores hablan entre hosts con nombres estables y políticas razonables.
  • Actualizaciones: avanzar, revertir, no hacer explotar producción.
  • Manejo del estado: datos persistentes que no se “recrean” hasta el olvido.
  • Modelo de salud: qué significa “saludable” y quién decide reiniciar qué.

Kubernetes agrupa esas decisiones en un sistema coherente (aunque complejo). Sin Kubernetes estás ensamblando el tuyo.
Eso puede ser una ventaja: menos abstracciones, menos controladores “mágicos”, modelo mental más sencillo. O puede ser una trampa:
reinventas el 20% más difícil (estado, identidad, redes) mientras te sientes productivo con el 80% más fácil.

La pregunta útil no es “¿Cómo hago multinodo sin Kubernetes?” Es: ¿Qué subconjunto necesito realmente?
Si ejecutas APIs sin estado detrás de un balanceador de carga, puedes estar contento con una orquestación mínima.
Si ejecutas bases de datos entre hosts con “contenedores por todas partes”, o bien estás haciendo ingeniería de almacenamiento seria,
o bien estás disfrazando la confiabilidad.

Hechos e historia que aún importan en 2026

  • La historia multihost original de Docker no fue Swarm: los primeros “clustering Docker” se apoyaban en sistemas externos (Mesos, experimentos basados en etcd) antes de que Swarm madurara.
  • Swarm mode (2016) se lanzó con Raft integrado: el quórum de managers es un sistema distribuido real; trátalo como tal.
  • “Docker Machine” tuvo su momento: automatizaba el aprovisionamiento de nodos en la era previa a IaC; la mayoría lo sustituyó por Terraform/Ansible y no miró atrás.
  • Kubernetes ganó en parte porque estandarizó expectativas: descubrimiento de servicios, despliegues continuos y estado deseado declarativo se volvieron básicos.
  • Las redes overlay existían mucho antes de los contenedores: VXLAN es más antiguo que la mayoría de plataformas de contenedores; sigue siendo la herramienta de trabajo para redes L2-¿casi?.
  • Los runtimes de contenedores se separaron de Docker: containerd y runc se convirtieron en componentes separados; “Docker” suele ser la capa de experiencia de usuario encima.
  • Los contenedores stateful siempre fueron polémicos: el debate “mascotas vs ganado” no envejeció; solo se trasladó a PVs, controladores CSI y clases de almacenamiento.
  • El descubrimiento de servicios pasó por fases: de configuraciones estáticas, a ZooKeeper/etcd/Consul, a “el orquestador es la fuente de verdad.” Sin K8s, eliges la fase.
  • iptables vs nftables sigue siendo un tema: la red de contenedores aún tropieza con semánticas de firewall del host y versiones del kernel.

Una idea para tener pegada en tu monitor, parafraseada y atribuida a Werner Vogels: Todo falla eventualmente; diseña y opera asumiendo que el fallo es normal.
Si tu plan multinodo requiere redes perfectas y discos inmortales, no es un plan. Es una esperanza.

Opciones realistas (y para quién son)

1) Docker Swarm mode

Swarm es la respuesta más directa si quieres “Docker, pero entre múltiples hosts” con maquinaria adicional mínima.
Te ofrece programación, descubrimiento de servicios, actualizaciones continuas, secretos y una red overlay. La integración es estrecha.
El ecosistema está más tranquilo que Kubernetes, pero tranquilo no es lo mismo que muerto. En muchas empresas, la tranquilidad es una ventaja.

Elige Swarm si: quieres una plataforma cohesiva, tus servicios son mayormente sin estado, y puedes convivir con menos integraciones del ecosistema.
Evita Swarm si: necesitas controles de políticas sofisticados, aislamiento multi-tenant, o esperas contratar gente que solo conoce Kubernetes.

2) HashiCorp Nomad (con Docker)

Nomad es un scheduler más fácil de razonar que Kubernetes, manteniendo una orquestación real.
Juega bien con Consul y Vault, y está contento ejecutando contenedores Docker. También puede programar cargas no contenedorizadas,
lo que a veces importa en entornos brownfield.

Elige Nomad si: quieres programación y chequeos de salud sin la amplitud de Kubernetes, y estás cómodo con el ecosistema de HashiCorp.
Evita Nomad si: necesitas el “mercado de Kubernetes” de controladores y operadores para cada sistema de nicho.

3) systemd + Docker Compose + un balanceador de carga

Este es el enfoque de “script bash para adultos”. Ejecutas Compose por host, gestionas despliegues con CI que empuja artefactos, e intervienes con un balanceador de carga apropiado.
No es glamoroso. Funciona. También te carga con resolver descubrimiento, despliegues y respuesta a fallos.

Elige esto si: tienes un puñado de nodos, cargas estables y valoras la transparencia sobre las características.
Evítalo si: quieres reprogramación automática tras la pérdida de un host, o tienes despliegues y escalados frecuentes.

4) Programación DIY (por favor, no) + descubrimiento de servicios

La gente aún lo intenta: un “scheduler” casero que elige host según CPU, luego ejecuta docker run por SSH, luego registra en Consul.
Esto puede funcionar… hasta que aparece el segundo modo de fallo. El tercer modo de fallo es donde se convierte en una decisión que limita tu carrera.

Broma #1: La orquestación casera es como escribir tu propia base de datos: es educativa, cara y la harás dos veces.

Docker Swarm: la opción de “orquestación suficiente”

Swarm mode está integrado en el Docker Engine. Inicializas un manager, unes workers y defines servicios.
Obtienes un plano de control con consenso Raft. Eso significa: los managers guardan estado y necesitas quórum.
Pierde el quórum y tu clúster se convierte en una exhibición de museo congelada.

Puntos fuertes de Swarm

  • Simplicidad operativa: un binario, un modelo mental, dependencias mínimas.
  • Modelo de servicio: réplicas, actualizaciones continuas, chequeos de salud y routing mesh.
  • Secretos: distribución integrada a las tareas, cifrados en tránsito y en reposo (en el log de Raft).
  • Buenas configuraciones por defecto: muchos equipos triunfan porque Swarm tiene menos perillas para desconfigurar.

Límites de Swarm (los que muerden)

  • Servicios stateful son tu problema: el scheduler puede mover una tarea; tus datos no se teletransportan.
  • La red es “suficiente” hasta que no lo es: los problemas de overlay se convierten en cacerías multinivel: kernel, MTU, conntrack, iptables, VXLAN.
  • Ecosistema: menos integraciones de terceros, menos “operadores”, menos patrones preconstruidos.
  • Escalado Day-2: tarde o temprano querrás controles de políticas y RBAC que Swarm no enfatiza.

Tareas prácticas (con comandos, salidas y decisiones)

Task 1: Validar estado de swarm y roles de nodos

cr0x@server:~$ docker info | sed -n '/Swarm:/,/Runtimes:/p'
Swarm: active
 NodeID: 8q2m0h9v6q6f0m8c7xqkqz0vv
 Is Manager: true
 ClusterID: k1l2m3n4o5p6q7r8s9t0u1v2w
 Managers: 3
 Nodes: 9
 Default Address Pool: 10.0.0.0/8
 SubnetSize: 24
 Data Path Port: 4789
 Orchestration:
  Task History Retention Limit: 5
 Raft:
  Snapshot Interval: 10000
  Number of Old Snapshots to Retain: 0
  Heartbeat Tick: 1
  Election Tick: 10
 Dispatcher:
  Heartbeat Period: 5 seconds
 CA Configuration:
  Expiry Duration: 3 months
 Autolock Managers: false

Qué significa: Swarm está activo, este nodo es manager y tienes 3 managers (el quórum requiere 2).
Decisión: Si tienes 1 manager, arréglalo antes de confiar en Swarm para algo más que un laboratorio. Para 3 managers, asegúrate de separarlos por dominios de fallo.

Task 2: Comprobar salud y disponibilidad de nodos

cr0x@server:~$ docker node ls
ID                            HOSTNAME   STATUS  AVAILABILITY  MANAGER STATUS  ENGINE VERSION
8q2m0h9v6q6f*                 mgr-a      Ready   Active        Leader          25.0.3
3k4l5m6n7o8p9q0r1s2t3u4v5w6x   mgr-b      Ready   Active        Reachable      25.0.3
7y8z9a0b1c2d3e4f5g6h7i8j9k0l   mgr-c      Ready   Active        Reachable      25.0.3
1a2b3c4d5e6f7g8h9i0j1k2l3m4n   wrk-a      Ready   Active                        25.0.3

Qué significa: Los managers son alcanzables; los workers están listos.
Decisión: Si algún manager aparece “Unreachable”, considéralo un riesgo de quórum e investiga antes de hacer despliegues.

Task 3: Inspeccionar salud del rollout del servicio

cr0x@server:~$ docker service ps api --no-trunc
ID                          NAME         IMAGE                   NODE   DESIRED STATE  CURRENT STATE           ERROR  PORTS
u1v2w3x4y5z6                api.1        registry/app:1.9.2      wrk-a  Running        Running 2 hours ago
a7b8c9d0e1f2                api.2        registry/app:1.9.2      wrk-b  Running        Running 2 hours ago
g3h4i5j6k7l8                api.3        registry/app:1.9.2      wrk-c  Running        Running 2 hours ago

Qué significa: Todas las réplicas están en ejecución; no hay bucle de reinicios.
Decisión: Si ves “Rejected” o “Failed”, detén el rollout e inspecciona logs/eventos antes de perseguir fantasmas en el balanceador.

Task 4: Confirmar puertos publicados y comportamiento del routing mesh

cr0x@server:~$ docker service inspect api --format '{{json .Endpoint.Ports}}'
[{"Protocol":"tcp","TargetPort":8080,"PublishedPort":80,"PublishMode":"ingress"}]

Qué significa: El puerto 80 está publicado vía ingress (routing mesh). Cualquier nodo puede aceptar conexiones y reenviar a tareas.
Decisión: Si estás depurando timeouts intermitentes, considera cambiar a modo host y usar un balanceador externo para rutas de tráfico más claras.

Task 5: Comprobar redes overlay y peers

cr0x@server:~$ docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
1c2d3e4f5g6h   ingress           overlay   swarm
7h8i9j0k1l2m   backend           overlay   swarm
a1b2c3d4e5f6   bridge            bridge    local
f6e5d4c3b2a1   host              host      local

Qué significa: Tienes la red ingress por defecto y una overlay personalizada.
Decisión: Si falta o está corrupta la red “ingress”, la red de servicios se comportará como una casa encantada. Arregla la red del clúster antes de culpar a la app.

Task 6: Verificar que los puertos de gossip/plano de control son accesibles

cr0x@server:~$ ss -lntup | egrep ':(2377|7946|4789)\b'
tcp   LISTEN 0      4096          0.0.0.0:2377      0.0.0.0:*    users:(("dockerd",pid=1123,fd=41))
tcp   LISTEN 0      4096          0.0.0.0:7946      0.0.0.0:*    users:(("dockerd",pid=1123,fd=54))
udp   UNCONN 0      0             0.0.0.0:7946      0.0.0.0:*    users:(("dockerd",pid=1123,fd=55))
udp   UNCONN 0      0             0.0.0.0:4789      0.0.0.0:*    users:(("dockerd",pid=1123,fd=56))

Qué significa: El puerto manager de Swarm (2377), gossip (7946 tcp/udp) y VXLAN (4789/udp) están escuchando.
Decisión: Si estos no están presentes o están bloqueados por firewalls del host, la red overlay y la membresía de nodos fallarán de maneras no obvias.

Nomad: programación sensata sin la carga completa de Kubernetes

El argumento de Nomad es directo: un scheduler único, clustering fácil, jobspecs claros y menos piezas móviles.
En la práctica, lo de “menos piezas móviles” es cierto hasta que añades Consul para descubrimiento y Vault para secretos,
en cuyo punto sigues siendo más simple que Kubernetes pero no exactamente acampando con una pedernal.

Dónde encaja mejor Nomad

  • Cargas mixtas (VMs, exec en crudo, contenedores Docker) en un solo scheduler.
  • Equipos que quieren jobs y asignaciones explícitas en lugar de un universo de controladores.
  • Entornos que ya usan Consul/Vault.

Límites de Nomad (prácticos, no ideológicos)

  • Integraciones de almacenamiento: aún necesitas una historia de volúmenes que sobreviva a la falla de un nodo.
  • Redes: puedes hacerlo bien, pero debes decidir: host networking, bridge, CNI, malla de servicios, etc.
  • Expectativas del ecosistema de apps: muchos proveedores asumen objetos de Kubernetes, no jobs de Nomad.

Tareas prácticas

Task 7: Comprobar salud del clúster Nomad rápidamente

cr0x@server:~$ nomad server members
Name     Address          Port  Status  Leader  Raft Version  Build  Datacenter  Region
nomad-1  10.20.0.11       4648  alive   true    3             1.7.5  dc1         global
nomad-2  10.20.0.12       4648  alive   false   3             1.7.5  dc1         global
nomad-3  10.20.0.13       4648  alive   false   3             1.7.5  dc1         global

Qué significa: 3 servidores, un líder, raft está saludable.
Decisión: Si no hay líder, detén los despliegues; arregla quórum/red primero.

Task 8: Inspeccionar allocations de un job y leer pistas de fallo

cr0x@server:~$ nomad job status api
ID            = api
Name          = api
Type          = service
Priority      = 50
Status        = running
Datacenters   = dc1
Task Groups   = web (3 running)

Latest Deployment
ID          = 3c1b2a9d
Status      = successful
Description = Deployment completed successfully

Allocations
ID        Node ID   Task Group  Version  Desired  Status   Created   Modified
a1b2c3d4  n-11      web         17       run      running  2h ago    2h ago
e5f6g7h8  n-12      web         17       run      running  2h ago    2h ago
i9j0k1l2  n-13      web         17       run      running  2h ago    2h ago

Qué significa: El despliegue tuvo éxito; las allocations están estables.
Decisión: Si las allocations están “failed”, ejecuta nomad alloc status y revisa errores del driver (pulls de imagen, conflictos de puertos, restricciones de CPU/mem).

Compose + systemd + SSH: herramientas afiladas, bordes afilados

Este enfoque es común en tiendas pequeñas-medianas y en entornos regulados donde “nueva plataforma” es una frase sucia.
También es habitual en empresas donde existen equipos de plataforma pero no te permiten usarlos.

El modelo: cada host ejecuta un conjunto conocido de contenedores. Desplas a través de CI (rsync, artefactos, imágenes),
reinicias vía systemd y usas un balanceador o DNS para distribuir tráfico. La conmutación por error la maneja el LB (sin estado)
o los humanos (con estado). Sí, es menos “cloud-native”. También es comprensible a las 02:00.

Tareas prácticas

Task 9: Confirmar el estado real de los contenedores en un host

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES        IMAGE                    STATUS                  PORTS
api          registry/app:1.9.2       Up 2 hours              0.0.0.0:8080->8080/tcp
worker       registry/worker:1.9.2    Up 2 hours
postgres     postgres:16              Up 14 days              0.0.0.0:5432->5432/tcp

Qué significa: Este host lleva una base de datos y contenedores de aplicación; la BD tiene mucho uptime.
Decisión: Si “postgres” vive en cada host “por conveniencia”, detén y diseña una colocación de estado adecuada. La conveniencia es donde nacen los outages.

Task 10: Usar systemd para que Compose se comporte como un servicio

cr0x@server:~$ systemctl status app-stack
● app-stack.service - Docker Compose App Stack
     Loaded: loaded (/etc/systemd/system/app-stack.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-01-03 09:12:44 UTC; 2h 3min ago
   Main PID: 1459 (docker)
      Tasks: 18 (limit: 18952)
     Memory: 612.4M
        CPU: 9min 12.180s
     CGroup: /system.slice/app-stack.service
             ├─1467 /usr/bin/docker compose up
             └─... containers ...

Qué significa: Tu stack está anclado al init; los reinicios no olvidarán iniciarlo silenciosamente.
Decisión: Si no tienes este tipo de andamiaje aburrido, eventualmente depurarás un “corte aleatorio” que en realidad es “host reiniciado”.

Redes multinodo: donde la confianza viene a morir

La red multinodo nunca es solo “abrir algunos puertos”. Es MTU, encapsulación, tablas conntrack, enrutamiento asimétrico,
reglas de firewall que olvidaste que existían y esa actualización de kernel que cambió el comportamiento de nftables.

Si usas overlays de Swarm (VXLAN), estás creando una red encapsulada encima de tu red.
Eso puede funcionar de maravilla—hasta que tu underlay tiene un MTU más pequeño del que asumiste, o tu equipo de seguridad
bloquea UDP 4789 porque “no lo usamos”. Spoiler: ahora lo usas.

Tareas prácticas

Task 11: Detectar síntomas de desajuste MTU rápidamente

cr0x@server:~$ ip link show dev eth0 | sed -n '1,2p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff

Qué significa: MTU es 1450 (común en redes cloud con VXLAN/GRE ya en juego).
Decisión: Si tu overlay asume 1500 y tu underlay es 1450, verás timeouts raros y respuestas parciales. Alinea MTUs o configura el MTU del overlay apropiadamente.

Task 12: Comprobar agotamiento de conntrack (clásico “funciona hasta que no”)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 248901
net.netfilter.nf_conntrack_max = 262144

Qué significa: Estás cerca del máximo de conntrack.
Decisión: Si el conteo se aproxima al máximo durante picos de tráfico, obtendrás conexiones descartadas y fallos “aleatorios”. Aumenta el máximo (considerando la memoria) y/o arregla los patrones de tráfico.

Task 13: Confirmar que el tráfico VXLAN del overlay está fluyendo

cr0x@server:~$ sudo tcpdump -ni eth0 udp port 4789 -c 5
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10:12:01.123456 IP 10.20.0.11.53422 > 10.20.0.12.4789: UDP, length 98
10:12:01.223455 IP 10.20.0.12.4789 > 10.20.0.11.53422: UDP, length 98
10:12:01.323441 IP 10.20.0.13.48722 > 10.20.0.11.4789: UDP, length 98
5 packets captured

Qué significa: Paquetes VXLAN están presentes en la red.
Decisión: Si no ves nada mientras los servicios intentan comunicarse, probablemente tienes bloqueos de firewall/security group o enrutamiento incorrecto entre nodos.

Broma #2: La red overlay es una excelente forma de aprender análisis de paquetes—principalmente porque no tendrás alternativa.

Almacenamiento para contenedores multinodo: realidad, no sensaciones

Las cargas sin estado son fáciles de distribuir entre hosts. Las cargas stateful son donde las plataformas justifican su existencia.
Sin Kubernetes no tienes abstracciones CSI ni un ciclo de vida estándar de PV. Aun así puedes manejar bien workloads stateful.
Pero debes elegir un modelo de almacenamiento deliberadamente.

Tres patrones de almacenamiento sensatos

Patrón A: Almacenamiento local, colocación fija (simple, honesto)

Ejecutas servicios stateful en hosts específicos con discos locales (LVM, ZFS, ext4). Fijas la colocación (constraints en Swarm, constraints en Nomad, o “este host ejecuta la BD”).
La conmutación por error es un procedimiento, no una ilusión.

Pros: Rápido, simple, menos dependencias. Contras: La falla del host implica recuperación manual a menos que añadas replicación a nivel de aplicación.

Patrón B: Almacenamiento en red (NFS)

NFS es el cucaracha de la infraestructura: sobrevive a todo, incluidas opiniones firmes.
Para muchas cargas—uploads compartidos, artefactos, contenido con muchas lecturas—está bien. Para bases de datos con muchas escrituras, suele ser miseria.

Pros: Sencillo, ampliamente soportado. Contras: Latencia, semánticas de bloqueo y problemas de vecino ruidoso; “es lento” se vuelve un estilo de vida.

Patrón C: Bloque distribuido (Ceph RBD) o sistema de archivos clusterizado

Ceph puede darte dispositivos de bloque replicados entre hosts. Es potente y viene con peso operativo.
Si no tienes la gente para operarlo, aprenderás sobre almacenamiento por las malas. Si la tienes, puede ser excelente.

Pros: Posibilidades reales de conmutación por error, semánticas de bloque consistentes. Contras: Complejidad operativa, tuning de rendimiento y una larga lista de “depende”.

Tareas prácticas

Task 14: Inspeccionar uso de volúmenes Docker y elección del driver

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     pgdata
local     uploads

Qué significa: Son volúmenes locales. Viven en el host.
Decisión: Si esperas que un contenedor se reubique a otro host y conserve sus datos, los volúmenes locales no lo harán. O fijas la colocación o usas almacenamiento compartido/distribuido.

Task 15: Encontrar dónde residen realmente los volúmenes locales de Docker

cr0x@server:~$ docker volume inspect pgdata --format '{{.Mountpoint}}'
/var/lib/docker/volumes/pgdata/_data

Qué significa: Los datos están bajo el directorio de datos de Docker en este host.
Decisión: Haz copias de seguridad como datos del host, no como “datos del contenedor”. Si reinstalas el host o mueves el directorio raíz de Docker, planifica migraciones.

Task 16: Validar salud y saturación del disco (verdad a nivel host)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server)  01/03/2026  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    4.22    8.77    0.00   74.70

Device            r/s     w/s   rkB/s   wkB/s  await  aqu-sz  %util
nvme0n1         45.2   210.1  1824.3  9312.7  18.40    2.31  96.8

Qué significa: El disco está cerca de la saturación (%util ~97%) con tiempos de espera significativos.
Decisión: Si tu “problema de clúster” es en realidad un disco caliente, arregla almacenamiento primero: medios más rápidos, mejor caché, menos amplificación de escrituras o mueve cargas ruidosas fuera del nodo.

Task 17: Si usas NFS, confirma opciones de montaje y riesgo de latencia

cr0x@server:~$ mount | grep nfs
10.30.0.50:/exports/uploads on /mnt/uploads type nfs4 (rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,sec=sys,clientaddr=10.20.0.21)

Qué significa: NFSv4.2 con rsize/wsize grandes y mounts hard (bueno para corrección, no siempre para latencia en cola).
Decisión: Si las apps se cuelgan durante fallos del servidor NFS, eso es esperado con hard. Decide si prefieres corrección (usualmente) o capacidad de respuesta (rara vez).

Task 18: Comprobar presión del filesystem a nivel de contenedor

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        18        27.4GB    10.2GB (37%)
Containers      51        9         1.8GB     1.1GB (61%)
Local Volumes   12        7         814.3GB   120.5GB (14%)
Build Cache     0         0         0B        0B

Qué significa: Los volúmenes dominan el uso de disco; limpiar imágenes no te salvará.
Decisión: Si los discos se están llenando, necesitas gestión del ciclo de vida de volúmenes y backups, no un ritual de docker image prune.

Seguridad, secretos e identidad entre hosts

Multinodo significa más límites de confianza. Docker en un solo host puede pasar con “bueno, está en la misma máquina”.
Una vez que tienes múltiples nodos, estás enviando secretos, abriendo puertos y dejando que los schedulers actúen en tu nombre.

Qué hacer (y qué evitar)

  • Hacer: usar secretos de Swarm o un gestor de secretos externo. No metas secretos en las imágenes.
  • Hacer: asegurar el socket de la API de Docker; es root con mejor marketing.
  • Hacer: rotar certificados del clúster (Swarm lo hace; aun así monitorea expiraciones).
  • Evitar: exponer Docker Engine TCP sin mTLS y políticas de red estrictas.

Tareas prácticas

Task 19: Comprobar riesgo de exposición del daemon Docker

cr0x@server:~$ ss -lntp | grep dockerd
LISTEN 0      4096       127.0.0.1:2375      0.0.0.0:*    users:(("dockerd",pid=1123,fd=7))

Qué significa: La API de Docker está en localhost:2375 (sin cifrado). Eso sigue siendo arriesgado pero no instantáneamente catastrófico.
Decisión: Si está ligada a 0.0.0.0, arréglalo inmediatamente. Si necesitas control remoto, usa túneles SSH o mTLS en 2376 con allowlists de firewall.

Task 20: Validar uso de secretos de Swarm en un servicio

cr0x@server:~$ docker service inspect api --format '{{json .Spec.TaskTemplate.ContainerSpec.Secrets}}'
[{"File":{"Name":"db_password","UID":"0","GID":"0","Mode":292},"SecretID":"p4s5w0rds3cr3t","SecretName":"db_password"}]

Qué significa: El servicio consume un secreto de Swarm como archivo con modo 0444 (292).
Decisión: Si los secretos se pasan como variables de entorno, asume que se filtrarán en logs y volcados. Prefiere secretos basados en archivos cuando sea posible.

Observabilidad y operaciones: logs, métricas, trazas

Multinodo sin Kubernetes no significa “sin observabilidad”. Significa que no puedes depender de herramientas nativas de Kubernetes para enmascarar huecos.
Necesitas estandarizar logs, métricas y telemetría básica del host—porque depurarás problemas entre hosts y querrás hechos.

Tareas prácticas

Task 21: Revisar tormentas de reinicios de contenedores y correlacionarlas con presión del host

cr0x@server:~$ docker inspect api --format 'RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'
RestartCount=7 OOMKilled=true ExitCode=137

Qué significa: El contenedor fue OOM-killed y reiniciado; el código de salida 137 lo confirma.
Decisión: Aumenta el límite de memoria, arregla la fuga de memoria o reduce la concurrencia. No “resuelvas” añadiendo más réplicas si cada réplica se OOMea bajo carga.

Task 22: Leer presión de memoria a nivel nodo (Linux no miente)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        27Gi       1.2Gi       512Mi       3.0Gi       1.8Gi
Swap:          4.0Gi       3.8Gi       256Mi

Qué significa: Poca memoria disponible y swap casi lleno: estás en la zona de peligro.
Decisión: Si estás intercambiando mucho, la latencia aumentará. Añade RAM, ajusta límites de contenedores o mueve cargas. “Está bien” no es una estrategia de gestión de memoria.

Task 23: Detectar errores del daemon Docker alrededor de redes e iptables

cr0x@server:~$ journalctl -u docker --since "1 hour ago" | tail -n 10
Jan 03 10:44:11 server dockerd[1123]: time="2026-01-03T10:44:11.112233Z" level=warning msg="could not delete iptables rule" error="iptables: No chain/target/match by that name."
Jan 03 10:44:15 server dockerd[1123]: time="2026-01-03T10:44:15.334455Z" level=error msg="failed to allocate gateway (10.0.2.1): Address already in use"
Jan 03 10:44:15 server dockerd[1123]: time="2026-01-03T10:44:15.334499Z" level=error msg="Error initializing network controller"

Qué significa: El controlador de red de Docker está descontento; desajustes en cadenas iptables y conflictos de gateway.
Decisión: Deja de batallar con los contenedores. Arregla el estado de la red del host (posiblemente reglas obsoletas, bridges conflictivos o un backend de firewall descoincidente), luego reinicia Docker limpiamente.

Guion de diagnóstico rápido

Las fallas multinodo suelen ser una de cuatro cosas: plano de control, underlay/overlay de red, latencia de almacenamiento o presión de recursos.
El truco es encontrar cuál en minutos, no horas.

Primero: confirma que el plano de control está sano

  • Swarm: docker node ls muestra managers alcanzables y workers listos.
  • Nomad: nomad server members muestra un líder y peers vivos.
  • Si el plano de control está degradado, detén despliegues y los “reinicios en cadena.” Solo churnarás estado.

Segundo: revisa presión de recursos en los nodos afectados

  • Memoria: OOM kills, uso de swap, free -h, contadores de reinicio de contenedores.
  • CPU: cola de ejecución y saturación (usa top o pidstat si está disponible).
  • Disco: iostat -xz y llenado de filesystem.
  • Si un nodo está caliente, drénalo (Swarm) o detén scheduling allí (Nomad) antes de depurar código de aplicación.

Tercero: valida fundamentos de red

  • Puertos escuchando: 2377/7946/4789 para overlays de Swarm.
  • MTU: confirma MTU del underlay y expectativas del overlay.
  • Conntrack: asegúrate de no llegar al techo.
  • Si el tráfico overlay está ausente (tcpdump no muestra nada), estás ante firewall, enrutamiento o políticas de security group.

Cuarto: aísla almacenamiento como cuello de botella (especialmente para timeouts “aleatorios”)

  • Util y await de disco: iostat -xz.
  • Comportamiento NFS: mounts, respuesta del servidor y si apps se cuelgan en mounts hard.
  • Crecimiento de volúmenes: docker system df y uso del filesystem.
  • Si el almacenamiento es lento, todo lo anterior parece roto. Arregla la capa base primero.

Errores comunes: síntoma → causa raíz → arreglo

1) “Los contenedores no se alcanzan entre hosts”

Síntoma: llamadas entre servicios se agotan; los nombres DNS resuelven pero las conexiones cuelgan.

Causa raíz: VXLAN (UDP 4789) bloqueado o desajuste MTU causando pérdida por fragmentación.

Arreglo: permite UDP 4789 de extremo a extremo; alinea MTU; valida con tcpdump y una prueba de payload pequeña; considera modo host + LB externo para claridad.

2) “El manager de Swarm quedó en modo solo lectura / atascado”

Síntoma: los despliegues se cuelgan, los servicios no convergen, el estado del manager hace flapping.

Causa raíz: pérdida de quórum o conectividad inestable entre managers; a veces latencia de disco en los logs de Raft del manager.

Arreglo: usa 3 o 5 managers; separa dominios de fallo; asegura que los discos de managers sean fiables; no coloques managers en los nodos con almacenamiento más ruidoso.

3) “Escalamos réplicas, pero fue más lento”

Síntoma: mayor latencia tras añadir réplicas; más 5xx.

Causa raíz: cuello de botella compartido: base de datos, NFS, conntrack o límites del LB; también estampida de caché.

Arreglo: mide la capa cuellodebotella primero; configura pools de conexión; limita tasas; escala la dependencia, no solo la capa sin estado.

4) “Resetes de conexión aleatorios durante picos”

Síntoma: fallos intermitentes que desaparecen cuando miras.

Causa raíz: tabla conntrack llena, agotamiento de puertos efímeros o churn del estado del firewall del host.

Arreglo: comprueba uso de nf_conntrack; aumenta límites; reduce churn de conexiones con keepalives; ajusta comportamiento del LB; asegura timeouts consistentes.

5) “Contenedor stateful reprogramado y perdió datos”

Síntoma: el servicio se reinicia en otro host y aparece “nuevo”.

Causa raíz: volumen local en el host anterior; el scheduler hizo exactamente lo que pediste, no lo que querías.

Arreglo: fija workloads stateful a nodos; usa replicación real (replicación de Postgres, etc.); o implementa almacenamiento compartido/distribuido deliberadamente.

6) “Actualizaciones rompen la red después de un reboot”

Síntoma: después de kernels/actualizaciones de firewall, las redes Docker no se inicializan.

Causa raíz: desajuste iptables vs nftables, reglas obsoletas, cambios de defaults.

Arreglo: estandariza builds de OS; valida el backend de firewall; prueba comportamiento de reboot; guarda un plan de rollback para componentes de red del host.

Listas de verificación / plan paso a paso

Paso a paso: elegir el enfoque no-Kubernetes correcto

  1. Inventario de tipos de carga: APIs sin estado, jobs en segundo plano, bases de datos stateful, almacenamiento de archivos compartidos.
  2. Decide tu contrato de fallo: reprogramación automática vs conmutación manual para las partes stateful.
  3. Elige nivel de orquestación:
    • Necesitas reprogramación y descubrimiento: Swarm o Nomad.
    • Necesitas “ejecutar lo mismo en estos hosts”: Compose + systemd.
  4. Diseña la red: overlay vs host networking; define puertos; confirma MTU y políticas de firewall.
  5. Diseña el almacenamiento: local fijo, NFS o bloque distribuido; escribe RPO/RTO y prueba restauraciones.
  6. Modelo de seguridad: distribución de secretos, TLS, controles de unión de nodos, menor privilegio.
  7. Base de observabilidad: logs centralizados, métricas, alertas sobre presión de nodos y un rastro de auditoría de despliegues.
  8. Realiza simulacros de fallo: mata un nodo, rompe un enlace, llena un disco y confirma que el comportamiento coincide con tus expectativas.

Checklist operativo: antes de ir multinodo

  • 3 nodos manager (si usas Swarm), ubicados en diferentes dominios de fallo.
  • Sincronización horaria (chrony/ntpd) en todos los nodos; la deriva causa rarezas en TLS y sistemas de consenso.
  • Reglas de firewall que permitan explícitamente los puertos requeridos entre nodos.
  • Versiones OS/kernel consistentes entre nodos (o al menos combinaciones probadas).
  • Topología de almacenamiento documentada: dónde viven los datos, cómo se respaldan y cómo se restauran.
  • Comportamiento del balanceador documentado: health checks, timeouts, reutilización de conexiones.
  • Runbooks para drenar nodos, rollback y recuperación de quórum de clúster.

Tareas prácticas: drenar y controles de rollback

Task 24: Drenar un nodo Swarm problemático de forma segura

cr0x@server:~$ docker node update --availability drain wrk-b
wrk-b

Qué significa: Swarm moverá tareas fuera de wrk-b (excepto servicios globales).
Decisión: Usa esto cuando un nodo tiene errores de disco, problemas de kernel o thrash de recursos. No lo dejes “Active” esperando que se comporte.

Task 25: Pausar un rollout malo de Swarm

cr0x@server:~$ docker service update --update-parallelism 0 api
api

Qué significa: Poner la paralelidad en 0 detiene efectivamente el progreso de una actualización.
Decisión: Usa esto cuando nuevas tareas están fallando y necesitas detener la hemorragia mientras inspeccionas logs y constraints.

Task 26: Revertir un servicio Swarm a la especificación previa

cr0x@server:~$ docker service update --rollback api
api

Qué significa: Swarm revierte el servicio a su configuración/imagen anterior.
Decisión: Si tu último cambio se correlaciona con errores, revierte pronto. Depura después. El orgullo no es un SLO.

Tres mini-historias desde la vida corporativa

Mini-historia 1: El incidente causado por una suposición equivocada

Una empresa SaaS mediana ejecutaba Docker Swarm para servicios sin estado y decidió “contenizarlo todo” para estandarizar despliegues.
El equipo de base de datos estuvo de acuerdo, con una condición: “Guardaremos los datos en un volumen.”
Crearon un volumen Docker local y siguieron adelante.

Un mes después, un worker murió de forma abrupta—fuente de alimentación, no un apagado ordenado. Swarm reprogramó la tarea de la base de datos en otro nodo.
El contenedor arrancó limpiamente. El health check pasó. La aplicación empezó a escribir en una base de datos totalmente nueva.
En minutos, los clientes vieron datos faltantes. Soporte escaló. Todos tuvieron el mismo pensamiento terrible: “¿Espera, acabamos de bifurcar la realidad?”

La suposición equivocada fue sutil: pensaron que “volumen” significaba “portable”. En Docker, un volumen local es local.
Swarm hizo su trabajo y movió la tarea. El almacenamiento hizo lo suyo y se quedó donde estaba—en los discos del servidor muerto.

La recuperación implicó traer el nodo de vuelta con una fuente de banco el tiempo suficiente para extraer datos, y luego restaurar en la instancia “nueva” de la base.
Más tarde reconstruyeron la arquitectura usando replicación a nivel de aplicación y fijando tareas stateful a nodos específicos con constraints explícitos.
La última frase del postmortem fue corta: “Tratamos el estado local como un recurso del clúster.”

Mini-historia 2: La optimización que salió mal

Un equipo de plataforma interna quiso despliegues más rápidos. Su clúster Swarm tiraba imágenes de un registry que a veces se enlentecía en horas punta.
Alguien sugirió: “Pre-pulleemos imágenes en cada nodo como tarea nocturna, así los despliegues nunca esperan la red.”
Sonaba sensato y muy DevOps.

La tarea nocturna ejecutó docker pull de una docena de imágenes grandes en cada nodo. Funcionó—los despliegues fueron más rápidos.
Entonces apareció un nuevo síntoma: alrededor de la 01:00, las APIs internas empezaron a fallar por timeouts y la cola de mensajes acumuló retraso.
No fue un outage total. Fue peor: un sistema lento y frágil que hacía discutir a los ingenieros.

El culpable no fue CPU. Fue contención de disco y red. Tirar capas mordió el almacenamiento, llenó la page cache con datos mayormente inútiles
y creó ráfagas de egress que chocaron con backups. Bajo presión, el kernel empezó a recuperar memoria agresivamente.
La latencia subió. Los reintentos se multiplicaron. El sistema entró en un bucle de retroalimentación.

La solución fue aburrida: dejar de pre-pullear en todos los nodos a la vez, escalonarlo, limitar ancho de banda y evitar que la “optimización de despliegue” compitiera con el tráfico de producción.
También midieron tamaños de imagen y los redujeron—porque la mejora de rendimiento más barata es no mover bytes que no necesitas.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Un equipo de servicios financieros ejecutaba Nomad con Docker para servicios internos. Nada ostentoso: tres servidores Nomad, un pool de clientes, Consul para descubrimiento.
El equipo era inusualmente estricto en dos cosas: runbooks documentados y pruebas rutinarias de restauración para componentes stateful.
La gente se burlaba. En silencio, todos también dependían de ellos.

Un viernes, el controlador de una matriz de almacenamiento empezó a fluctuar. La latencia no explotó de inmediato; tambaleó.
Las aplicaciones empezaron a tirar timeouts “aleatorios”. El ingeniero on-call ejecutó la rutina de diagnóstico rápido: plano de control sano, CPU bien, memoria bien, luego iostat.
El await del disco iba en aumento. Los logs mostraban reintentos en múltiples servicios. Era almacenamiento, no código.

Ejecutaron el runbook: drenaron los clientes más afectados, fallaron por error el servicio stateful a una réplica que usaba otro backing storage,
y redujeron la amplificación de escritura deteniendo un job por lotes. Mientras tanto, restauraron un backup reciente en un entorno limpio para validar integridad.
Ese último paso llevó trabajo, pero detuvo el pánico temprano: sabían que tenían una copia segura.

Cuando el incidente terminó, el postmortem fue casi decepcionantemente calmado. La mayor “lección aprendida” fue que las prácticas aburridas funcionaron.
Los ensayos de restauración no son glamorosos, pero convierten desastres en tardes desagradables.

Preguntas frecuentes

1) ¿Puedo ejecutar Docker Compose en varios servidores?

No como un “clúster” coherente con programación. Compose es por host. Puedes desplegar el mismo archivo Compose en múltiples hosts,
pero debes manejar balanceo, descubrimiento y conmutación por error por tu cuenta.

2) ¿Está Docker Swarm “muerto”?

Swarm no está de moda, y eso es distinto a estar muerto. Es estable, integrado y todavía se usa. El riesgo es la inercia del ecosistema:
menos patrones de terceros, menos contrataciones con experiencia directa y menos integraciones de proveedores.

3) ¿Cuál es el mayor límite de Docker multinodo sin Kubernetes?

Ciclo de vida de cargas stateful y portabilidad del almacenamiento. Programar un proceso es fácil. Programar datos de forma segura es lo difícil.
Si no diseñas el almacenamiento explícitamente, eventualmente perderás datos o disponibilidad.

4) ¿Debo usar redes overlay o host networking?

Para simplicidad y rendimiento, host networking más un balanceador real es a menudo más fácil de operar.
Los overlays ayudan con conectividad servicio-a-servicio y portabilidad, pero añaden modos de fallo (MTU, bloqueos UDP, conntrack).
Elige según el apetito de tu equipo para depurar.

5) ¿Cómo hago descubrimiento de servicios sin Kubernetes?

Swarm tiene DNS de servicio integrado en redes overlay. Con Nomad, Consul es una elección común.
En el mundo Compose+systemd puedes usar upstreams estáticos en un LB, registros DNS o un sistema de descubrimiento como Consul—solo mantenlo coherente.

6) ¿Y la gestión de secretos?

Los secretos de Swarm son buenos para muchos casos. Para entornos más grandes o con requisitos de cumplimiento, usa un gestor de secretos dedicado.
Evita variables de entorno para secretos cuando puedas; se filtran en demasiados lugares.

7) ¿Puedo ejecutar bases de datos en contenedores entre múltiples hosts de forma segura?

Sí, pero debes elegir un modelo de replicación/conmutación por error que coincida con la base y tu madurez operativa.
“Simplemente ponlo en un contenedor y deja que el scheduler lo mueva” es la forma de aprender arrepentimiento.

8) ¿Cuántos nodos manager necesito en Swarm?

Usa 3 o 5 managers. Un manager es un punto único de fallo. Dos managers no toleran una falla sin perder quórum.
Tres managers toleran una falla y aún progresan.

9) ¿Cuál es una buena base no-Kubernetes para un equipo pequeño?

Para servicios mayormente sin estado: Swarm con 3 managers, balanceador externo y una historia de almacenamiento clara para los pocos componentes stateful.
Para “colocación mayormente fija”: Compose + systemd + un balanceador, además de runbooks documentados.

Siguientes pasos prácticos

  1. Escribe tu contrato de fallo: qué ocurre cuando un nodo muere y quién/qué mueve las cargas.
  2. Elige un nivel de orquestación y comprométete por un año: Swarm, Nomad o Compose+systemd. Mezclarlos es la forma en que la complejidad se cuela con bigote falso.
  3. Diseña el almacenamiento primero para servicios stateful: local fijado + replicación, NFS para cargas adecuadas o bloque distribuido si puedes operarlo.
  4. Endurece la red: confirma MTU, capacidad de conntrack y puertos requeridos. Prueba antes de que la producción te obligue.
  5. Operativiza: procedimientos de drenar nodos, comandos de rollback, ejercicios de backup/restore y una checklist de diagnóstico rápido que se use realmente.

Docker multinodo sin Kubernetes es absolutamente factible. El movimiento ganador no es pretender que evitas la complejidad.
El movimiento ganador es elegir qué complejidad vas a pagar—y pagarla deliberadamente, a la luz del día, con monitoreo.

← Anterior
Estrategia de hot-swap en ZFS: cómo reemplazar discos sin entrar en pánico
Siguiente →
Bloques pequeños especiales de ZFS: Cómo acelerar cargas de trabajo con archivos pequeños

Deja un comentario