Contactar →
01

Desplegar un cambio

Cada push a un repositorio que despliego pasa por los mismos pasos: build, tests, empaquetado, despliegue y verificación. Jenkins lo ejecuta todo. No lo elegí tras comparar herramientas; ya lo había usado y sabía operarlo, y a veces ese es el motivo correcto. Harbor está a su lado como mi propio registro de imágenes, así que un build nunca depende de los límites ni de la disponibilidad de Docker Hub.

Los builds no se ejecutan en el host de Jenkins. Dos agentes en la VLAN de CI hacen el trabajo, uno en pve01 y otro en pve03. Pueden descargar dependencias y subir imágenes, y no pueden administrar nada. El razonamiento de esa separación está en /system.

Qué pasa después de un git push, desde el webhook de GitHub hasta el mensaje de Telegram.

Todo lo que se despliega así debería tener ya un target en Prometheus, un monitor en Uptime Kuma y, idealmente, un dashboard en Grafana antes de salir. De eso va la siguiente sección.

Esta web se despliega igual, con un pipeline que publica dos tipos de artefacto distintos desde el mismo monorepo:

El pipeline de este repositorio
git push → webhook de GitHub → Jenkins
  → docs: mkdocs build --strict → rsync al proxy interno
  → web: build de Docker → push a Harbor → despliegue con Compose en el host público
  → health check de los dos proyectos
  → un mensaje de Telegram por proyecto, o uno si falla
  → el workspace del agente se limpia siempre
Una ejecución del pipeline en Jenkins: build, subida de imagen, despliegue, health check.

Las decisiones que hay detrás:

  • La documentación se despliega con rsync, no como contenedor. La web de documentación es HTML estático generado con mkdocs --strict, así que un enlace roto hace fallar el build. Empaquetarla como imagen añadiría un viaje por Harbor para nada: Caddy ya sirve los ficheros directamente, así que Jenkins los genera y los copia con un usuario de despliegue que solo puede escribir en ese directorio.
  • La web se despliega como imagen. Se construye desde apps/webpage, se sube a Harbor etiquetada con el commit corto y el número de build, y se vuelve a desplegar con Docker Compose en el host público.
  • Los dos proyectos se despliegan en cada push. Antes detectaba qué rutas habían cambiado. Fallaba en los primeros builds y en los replays de Jenkins, de formas fáciles de pasar por alto. Los dos builds son pequeños, así que reconstruirlo todo cada vez es más sencillo y más seguro.
  • Telegram es el único canal de avisos. Un mensaje por proyecto desplegado si todo va bien, y un mensaje con la etapa que falló, el commit y un enlace a la consola si algo falla. Sin email y sin un dashboard que tenga que acordarme de mirar.
  • El workspace se limpia siempre. cleanWs() se ejecuta en post { cleanup {} } sea cual sea el resultado, así que un build fallido nunca deja ficheros a medias con los que tropiece el siguiente.
Harbor, el registro al que sube imágenes este pipeline.
02

Vigilarlo en marcha

Durante años, monitorizar significaba abrir dashboards y cruzar los dedos. Ahora está dividido según la pregunta que responde cada herramienta:

Los exporters alimentan a Prometheus, Alloy a Loki, Grafana lee de los dos y todas las alertas acaban en el mismo chat de Telegram.
SeñalResponde aHerramientas
MétricasSalud de hosts, contenedores, Proxmox, bases de datos y aplicacionesPrometheus + exporters
LogsQué ha pasado en un host o servicioGrafana Alloy + Loki
DisponibilidadSi los endpoints importantes responden, vistos desde fueraUptime Kuma
DashboardsConsultar y visualizar métricas y logsGrafana
AlertasAvisarme antes de que lo note un clienteAlertmanager + Telegram, Uptime Kuma + Telegram

Cinco componentes, cada uno con una sola tarea:

ComponenteFunción
PrometheusRecoge métricas y evalúa las reglas de alerta
LokiGuarda los logs que envía Grafana Alloy
AlertmanagerEnvía las alertas de Prometheus a Telegram
GrafanaDashboards sobre Prometheus y Loki
Uptime KumaComprobaciones activas, independientes de las métricas

Prometheus recoge métricas cada 15 segundos. Los targets que cambian según el host o la VLAN salen de ficheros JSON con file_sd_configs, uno por zona, así que añadir una máquina es editar un fichero de targets y no la configuración principal. Además de node_exporter, recoge cAdvisor en los hosts Docker (descartando algunas series de alta cardinalidad antes de guardarlas para que el número de series no se dispare), pve_exporter para el clúster, postgres_exporter para las dos bases de datos y las métricas nativas de Authentik, Zitadel y Grafana.

Grafana Alloy envía los logs de cada host a Loki, que los guarda 30 días en almacenamiento local con el esquema TSDB. El ruler de Loki ya apunta al mismo Alertmanager, así que las alertas basadas en logs usarán el mismo camino cuando las escriba. Ahora mismo no hay ninguna.

Alertmanager y Uptime Kuma avisan los dos por Telegram, pero responden a preguntas distintas. Alertmanager salta con lo que dicen las métricas: CPU alta, un target que ha dejado de responder, una base de datos sin conexiones libres. Las alertas críticas esperan menos y se repiten antes que los avisos, y los avisos de un host se silencian mientras haya una alerta crítica activa para él, así que un fallo no me manda cinco mensajes. Uptime Kuma ignora por completo las métricas y solo pregunta si un endpoint responde. Quiero las dos: una detecta la degradación interna lenta y la otra detecta el "está caído y punto" aunque todas las métricas internas parezcan bien.

Uno de los dashboards de Grafana que miro cada día.
Una notificación de Alertmanager llegando a Telegram.

Unas pocas reglas evitan que el stack se descontrole al crecer:

  • Cada host nuevo lleva node_exporter y Grafana Alloy. Sin excepciones. Es la base antes de instalar cualquier otra cosa.
  • cAdvisor solo donde hay Docker. No tiene sentido recoger métricas de contenedores en un host sin contenedores.
  • Uptime Kuma solo donde la disponibilidad le importa a alguien. No todos los servicios internos necesitan una comprobación desde fuera, solo los que usan personas o clientes.
  • Una alerta solo existe si alguien debería actuar. Una alerta sin una acción detrás es ruido, y el ruido acaba silenciado a la semana.
  • Las reglas del firewall siguen los caminos de recogida de métricas. Las métricas no tienen atajo. Que Prometheus llegue al exporter de una base de datos sigue siendo una regla explícita y acotada.
Los grupos de reglas de alerta para aplicaciones, infraestructura y PostgreSQL, cada uno limitado a lo que alguien atendería.
La vista de estado de Uptime Kuma, la comprobación desde fuera.
03

Hosts coherentes

Tres máquinas con tres historias distintas (ver /system) no acaban con los mismos hábitos por sí solas. Si las dejo, cada host tiene usuarios distintos, una configuración de SSH distinta y una monitorización distinta según cuándo lo monté. No quería que eso fuera una variable, así que cada VM o LXC nueva pasa por la misma secuencia antes de hacer nada: un usuario administrador que no es root, SSH solo con clave, sin login de root ni contraseñas, y los agentes de observabilidad ya reportando antes de darla por terminada.

Bastionado de sshd
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no

El último paso, node_exporter y Grafana Alloy en cada host, es lo que hace fiable la monitorización de arriba. Si una máquina no reporta métricas un intervalo de recogida después de arrancar, lo trato como un error de configuración, no como algo para limpiar más adelante. Un detalle del runbook que merece la pena repetir: probar el login con clave desde una segunda terminal antes de cerrar la primera, o puedes quedarte fuera.

Setup base de una VM nueva, tecleado a mano desde el runbook
$ adduser alberto && usermod -aG sudo alberto
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub alberto@<new-host>
$ ssh alberto@<new-host>        # segunda terminal, antes de cerrar la primera
$ sudoedit /etc/ssh/sshd_config && sudo systemctl restart sshd
$ sudo systemctl enable --now node_exporter alloy
$ curl -s localhost:9100/metrics | head -n 3

Reiniciar un nodo Proxmox por una actualización de kernel o por trabajo de hardware sigue la misma disciplina al revés. Primero lo vacío: migro en vivo las VM que pueden moverse, apago con cuidado lo que no puede y compruebo que no queda nada corriendo. Después reinicio y miro cómo vuelve al clúster. Siempre un nodo cada vez, porque el clúster necesita dos de tres nodos sanos para mantener el quórum, y el siguiente no empieza hasta que pvecm status muestra el anterior completamente de vuelta. pve01 tiene una trampa extra: su kernel está fijado, porque un kernel nuevo arrancaría sin el driver de NVIDIA que necesita la GPU.

Vaciar pve02 antes de reiniciarlo (ilustrativo)
# en pve02
$ qm list                          # VM que siguen aquí
$ qm migrate 105 pve03 --online
$ pct list                         # contenedores
$ pct shutdown 210                 # almacenamiento local, se para limpio
$ qm list && pct list              # nada corriendo: pve02 está libre
$ reboot
...
$ pvecm status | grep Quorate
Quorate:          Yes
04

Poner algo nuevo en marcha

Cada servicio que entra en la plataforma pasa por la misma lista, para que nada se aleje del modelo de red, proxies y monitorización descrito en /system:

Alta de un servicio nuevo
Servicio definido → VLAN elegida → dirección y DNS asignados
  → ruta de proxy/firewall revisada → observabilidad añadida
  → riesgo de persistencia anotado → documentación actualizada

Las dos decisiones que importan son en qué VLAN cae y cuál de los tres proxies lo publica. Las dos se deducen de lo que es el servicio:

Tipo de servicioUbicación por defecto
Aplicación o herramienta internaInternal
Base de datos o almacenamiento de objetosData
Agente de build o automatización de CICI
Entrada desde internetPublic, normalmente solo como entrada del proxy
Interfaz de administración o del plano de controlMgmt, detrás del proxy de gestión

Todo lo demás es mecánico: la regla de firewall más estrecha que encaje con el camino real (nunca un permiso amplio de VLAN a VLAN), el firewall del host, los agentes de observabilidad, un monitor en Uptime Kuma si lo usa gente y, si guarda algo, una nota escrita de que sus datos todavía no tienen copia de seguridad.

Un servicio recién añadido apareciendo como targets sanos en Prometheus, el último paso de la lista.
05

Cuando algo se rompe

Mi documentación tiene dos tipos de páginas, y las mantengo separadas a propósito. Un runbook son pasos exactos para algo cuyos comandos no dependen del criterio: reinicia esto, migra aquello. Una guía es una forma de tomar una decisión estructural en la que la respuesta correcta depende del contexto, como aislar a un cliente nuevo. Si sabes lo que hay que hacer, quieres un runbook. Si estás decidiendo qué debería pasar, quieres una guía.

La recuperación ante desastres está escrita como un proceso, no como un script, porque todavía no hay una restauración probada fuera de los nodos. Recuperar depende de lo que siga existiendo en los hosts supervivientes y de lo que esté documentado. Por orden: averiguar qué ha fallado (un servicio, una VM, un nodo entero, un disco o la red); comprobar si el clúster mantiene el quórum y si el DNS, el enrutamiento y los proxies siguen funcionando; reconstruir lo que se pueda usando la documentación como fuente de verdad; y validarlo como un despliegue nuevo, de forma que el DNS resuelva, la ruta del proxy funcione, el target de Prometheus vuelva y Uptime Kuma se ponga en verde. Lo que el incidente demuestre que faltaba, como una ruta de configuración que nadie apuntó, va después a la documentación para que el siguiente sea un poco menos improvisado.

El proceso de recuperación en cinco pasos, y el hueco que todavía no cubre.
pvecm status durante un incidente (ilustrativo)
$ pvecm status
Quorum information
------------------
Nodes:            3
Quorate:          Yes

Membership information
----------------------
    Nodeid      Votes Name
0x00000001          1 pve01 (local)
0x00000002          1 pve02
0x00000003          1 pve03

Los runbooks que tengo hoy, por área:

CategoríaCubre
WireGuardAñadir un peer de acceso remoto, añadir un túnel
Hosts y clústerSetup base de máquinas, mantenimiento de nodos Proxmox, el driver de la GPU de pve01, recuperación ante desastres
RedCambios en zonas DNS, poner en marcha una VLAN nueva
AplicacionesAlojamiento de webs estáticas
06

Lo que sigue siendo manual

Hay dos cosas que todavía se hacen a mano, o no se hacen:

Carencia conocida

Todavía no hay copias de seguridad funcionando. En la práctica, si muere un nodo con almacenamiento solo local, o me equivoco de bucket o de base de datos, solo puedo recuperar lo que siga existiendo en otro host. Es el mismo hueco que señala la sección de almacenamiento de /system, visto desde el lado operativo.

El segundo es la automatización, o la falta de ella. El setup base de las máquinas que he descrito antes se sigue tecleando a mano en cada host nuevo, no se ejecuta con Ansible ni con OpenTofu. Es coherente porque sigo el mismo runbook cada vez, no porque una herramienta lo imponga, y esa es una garantía mucho más débil.

Uno más pequeño: pve03 todavía no tiene Wake-on-LAN configurado. El motivo es de lo más mundano: no le he conectado un monitor para cambiar la opción de la BIOS. Como de todos modos tiene un iDRAC para encenderlo en remoto, no ha corrido prisa.

Los dos grandes están en la lista. El backlog completo (destino de copias, pruebas de restauración, despliegue del SSO y los secretos, infraestructura como código) está en /roadmap.

07

Qué haría distinto

Tres años después, hay dos cosas claras: una que ojalá hubiera hecho antes y otra que repetiría sin pensarlo.

  • Ansible y OpenTofu tendrían que haber llegado el primer día. Cuando esto eran dos máquinas, adoptar infraestructura como código no habría costado casi nada. Con el número actual de hosts y servicios, añadirlo después es una fricción real y constante, tanta que lo sigo aplazando. Es justo el hueco de automatización de arriba. Empezar pronto habría mantenido sincronizada la configuración de cada host sin tocarlos uno a uno.
  • Dividir la red mereció la pena. Ocho VLAN y sus reglas de firewall hacen que cada cambio cueste más que en una red plana. Volvería a tomar la misma decisión: el aislamiento compensa el esfuerzo de mantenerlo.

Lo siguiente: /system explica qué existe (el hardware, la red y los servicios que despliega este pipeline y vigila este stack), /security cómo se controla el acceso y /roadmap es el backlog, en orden.