Problema
Mantener un homelab con decenas de contenedores Docker distribuidos en varios servidores físicos genera una serie de cuellos de botella que aparecen de forma intermitente: crecimiento inesperado del estado de la cadena, agotamiento de pools de bases de datos, pérdida de conectividad cuando la ruta primaria falla y dificultad para correlacionar logs provenientes de diferentes hosts. Cuando la infraestructura incluye un nodo Ethereum completo, la presión sobre el disco y el ancho de banda aumenta, y cualquier desalineamiento en la configuración de red o firewall puede romper la sincronización de la cadena y los servicios dependientes. El reto es diseñar una arquitectura que permita escalar el número de contenedores sin que la complejidad operacional se vuelva inmanejable.
Causa
-
Almacenamiento estático y crecimiento de la cadena
Los clientes de Ethereum (Nethermind, Lighthouse) guardan el estado de la cadena en disco. Un sync tipo snap puede pasar de 600 GB a más de 1 TB en pocos meses. Sin una política de rotación o archivado, el nodo se queda sin espacio y obliga a una re‑sync completa. -
Pool de conexiones de PostgreSQL insuficiente
Cuando varios micro‑servicios comparten una única base, PgBouncer se queda sin max_client_conn y default_pool_size. Cada petición adicional se bloquea, provocando latencias y errores 500 en APIs. -
Red de malla sin redundancia de túneles
Dependencia de un único túnel WireGuard para acceder a los servidores dedicados. Si el enlace de salida (fibra o 5G) se cae, la conectividad desaparece hasta que el túnel se restablece, lo que afecta a los runners de CI y a los exporters de métricas. -
Reglas de firewall demasiado restrictivas o conflictivas
nftables gestionado por un unit de systemd puede colisionar con el servicio nativo, dejando puertos críticos (30303, 9000) cerrados sin notificación. Además, la política drop sin logs de depuración dificulta la detección de bloqueos. -
Observabilidad fragmentada
Logs enviados a OpenSearch desde contenedores sin estandarizar el formato dificultan búsquedas rápidas. La retención indefinida de índices grandes consume espacio y ralentiza las consultas.
Solución
1. Arquitectura de almacenamiento con capas
- Separar datos de la cadena del resto de los volúmenes. Usa un disco NVMe dedicado para el datadir de Nethermind y otro para los logs y bases de datos.
- Implementar rotación de snapshots: programa un cron que, cada 30 días, copie el estado actual a un bucket S3 (o MinIO) y luego elimine los archivos locales más antiguos. Mantén al menos dos copias antes de borrar.
- Planificar una expansión: cuando el uso supere el 70 % del disco, provisiona un RAID0/1 adicional y migra el datadir con
rsync --partial.
2. Tuning de PgBouncer y PostgreSQL
- Aumenta
max_client_conna un valor que cubra la suma de max_connections de todos los micro‑servicios. Un buen punto de partida es200 + (n_services * 10). - Ajusta
default_pool_sizesegún la carga esperada por servicio; valores entre 20‑30 suelen ser estables. - Habilita
ignore_startup_parameters = extra_float_digitspara evitar reconexiones innecesarias. - Usa métricas de pgbouncer_exporter en Prometheus para alertar cuando el número de clientes activos se acerque al límite.
3. Redundancia de túneles WireGuard
- Crear dos peers en cada nodo: uno usando la IP pública del servidor principal y otro a través del VPS gateway. Configura
PersistentKeepalive = 25en ambos. - En Ansible, define una variable
wg_primaryywg_fallback. Un script de systemd verifica la latencia del peer primario cada 10 s y, si supera 500 ms, cambia a la ruta de fallback conwg setconf wg0 /etc/wireguard/fallback.conf. - Mantén una regla de firewall que permita tráfico UDP 51820 desde ambas IPs.
4. Gestión segura de nftables
- Evita que el unit de systemd sobrescriba la tabla existente. Usa
ExecStartPre=/usr/sbin/nft -f /etc/nftables.confyExecReload=/usr/sbin/nft -f /etc/nftables.conf. - Declara una cadena dedicada para Ethereum:
nft add table inet filter
nft 'add chain inet filter eth_chain { type filter hook input priority 0; policy drop; }'
nft add rule inet filter eth_chain ip protocol tcp ip dport {30303, 9000} ct state new accept
nft add rule inet filter eth_chain ip protocol udp ip dport {30303, 9000} ct state new accept
nft add rule inet filter eth_chain ct state established,related accept
nft add rule inet filter eth_chain iif "lo" accept
- Añade una regla de logging temporal para depurar bloqueos:
nft add rule inet filter input log prefix "nft-drop: " drop.
5. Centralizar logs con formato estructurado
- Instala Fluent Bit fuera de Docker en cada host. Configura
Parsers_Filepara JSON yTailpara los archivos de contenedor en/var/lib/docker/containers/*/*.log. - Usa un pipeline que añada etiquetas
host,container_nameyservice. - En OpenSearch, crea un ILM (Index Lifecycle Management) que rote índices diarios y elimine los que superen 30 días, manteniendo solo los últimos 7 días en hot storage.
- Si el volumen supera 10 GB/día, considera migrar a Loki; sin embargo, OpenSearch sigue siendo más flexible para búsquedas de texto completo.
6. Automatización con Ansible
- Define roles para:
wireguard,nftables,docker_compose,pgbouncer,fluent_bit. - Usa templates Jinja2 para generar los archivos de configuración con variables de entorno (p.ej.,
{{ wg_peer_ip }}). - Ejecuta
ansible-playbook site.yml --checkantes de aplicar cambios críticos.
Cuándo aplicar esta solución
- Síntomas: errores 500 en APIs que dependen de PostgreSQL, alertas de disco lleno en el nodo Ethereum, pérdida de conectividad tras caída de fibra, o demoras en la recolección de métricas.
- Entorno: homelabs con >50 contenedores distribuidos en al menos dos servidores físicos, uso de nodos blockchain y pipelines CI/CD auto‑hosted.
- No aplica: entornos con un solo contenedor o sin dependencias de base de datos compartida; en esos casos la complejidad de WireGuard y nftables es innecesaria.
Código
# nftables rules (ver bloque anterior)
# PgBouncer tuning (postgresql.conf)
cat <<EOF >> /etc/pgbouncer/pgbouncer.ini
max_client_conn = 500
default_pool_size = 30
ignore_startup_parameters = extra_float_digits
EOF
# WireGuard fallback script
cat <<'EOS' > /usr/local/bin/wg-fallback.sh
#!/bin/bash
PRIMARY=10.100.0.1
FALLBACK=10.100.0.2
PING=$(ping -c1 -W1 $PRIMARY >/dev/null && echo ok || echo fail)
if [[ $PING == fail ]]; then
wg setconf wg0 /etc/wireguard/fallback.conf
else
wg setconf wg0 /etc/wireguard/primary.conf
fi
EOS
chmod +x /usr/local/bin/wg-fallback.sh
systemctl enable --now wg-fallback.timer
Verificación
- Espacio en disco:
df -h /var/lib/netherminddebe mostrar al menos 20 % libre después de la rotación. - PgBouncer:
pgbouncer -Rmuestratotal_clients<max_client_conn. Configura una alerta en Prometheus parapgbouncer_pool_size > 0.8 * pgbouncer_max_client_conn. - WireGuard:
wg show wg0debe listar ambos peers ylatest handshakereciente (<30 s). Simula la caída de la fibra desconectando la interfaz primaria y verifica que el tráfico se redirige al fallback. - Firewall:
nft list chain inet filter eth_chaindebe contener las reglas de puerto 30303 y 9000. Genera tráfico de prueba connc -vz <node_ip> 30303. - Logs: En OpenSearch, busca un mensaje reciente con
host:"node-2"y verifica que aparecen los camposcontainer_nameyservice.
Notas adicionales
- Manejo de snapshots: evita usar
docker commitpara crear imágenes de estado; en su lugar, exporta el datadir y vuelve a montar en un contenedor nuevo. - Runners de GitHub Actions: configura
--rmen los contenedores de runner para que eliminen su workspace automáticamente y evita la acumulación de archivos temporales. - 5G APN: en MikroTik, mantén
apn=freey habilitaipv6-use-compressedpara que IPv4 y IPv6 coexistan sin interrupciones. - OpenSearch vs Loki: si la mayor parte de las consultas son búsquedas de texto libre (p.ej., “error 500 en API X”), OpenSearch sigue siendo la mejor opción. Loki brilla cuando los logs son exclusivamente de series temporales y se consultan por etiquetas.
- Documentación de Ansible: versiona los playbooks en un repositorio Git separado y usa tags semánticos para marcar cambios de infraestructura críticos.
Con esta arquitectura modular y los ajustes descritos, es posible escalar un homelab con decenas de contenedores y un nodo Ethereum sin que la complejidad operativa se convierta en un obstáculo. La clave está en separar responsabilidades (almacenamiento, red, base de datos, observabilidad) y automatizar cada capa con herramientas probadas.