Contactar →
01

Desplegar un cambio

Cada push a un repo que sigo pasa por la misma forma de pipeline, sea cual sea el proyecto: build, test, package, deploy, verify. Jenkins es lo que ejecuta todo esto de verdad, y siendo honesto sobre cómo se eligió, no fue el resultado de una evaluación de herramientas — ya lo había usado antes, sabía cómo llevarlo, y nunca me puse a mirar alternativas. A veces la herramienta aburrida y ya conocida es la decisión correcta precisamente porque ya se conoce. Harbor está al lado como registry de imágenes, el mío propio, así que un container construido nunca tiene que tocar el rate limit ni el uptime del Docker Hub de otra persona.

Los builds no corren en el propio host de Jenkins — dos agentes dedicados hacen el trabajo real, en su propio segmento de red con acceso de salida para descargar dependencias y subir imágenes, y nada más. Poder salir es un permiso completamente distinto a que se confíe en ti para entrar y administrar algo; el razonamiento completo detrás de esa separación vive en /system.

La forma general que sigue cada deployment: commit, build, package, deploy, verify.

Se espera que cualquier servicio desplegado así ya tenga un target de Prometheus, un monitor de Uptime Kuma, y a poder ser un dashboard de Grafana antes de desplegarse — esa expectativa es de lo que trata en realidad la siguiente sección.

Esta web — la que sirve la página que estás leyendo — es un buen ejemplo concreto, porque un único pipeline despliega dos tipos de artefacto genuinamente distintos desde el mismo monorepo.

El pipeline de este repo
git push → GitHub webhook → Jenkins
  → docs: mkdocs build --strict → rsync to the internal proxy
  → webpage: Docker build → Harbor push → Compose deploy on the public-facing host
  → health check both projects
  → notify Telegram per project, or once on pipeline failure
  → agent workspace cleaned either way
Una ejecución de pipeline en Jenkins — build, image push, deploy, health check.

Unas cuantas decisiones de diseño detrás de esto, que merece la pena conocer en lugar de simplemente asumir:

  • Los docs se despliegan por rsync, no por container. El sitio de docs es HTML estático; empaquetarlo como imagen solo para redesplegarlo a través de Harbor añadiría un round-trip al registry para nada. El proxy ya sirve los archivos directamente, así que Jenkins solo necesita hacer build y copiar.
  • La webpage se despliega como imagen. Se construye desde apps/webpage, se publica en Harbor etiquetada por commit corto y número de build, y se redespliega vía Compose en el host expuesto al público.
  • Los dos proyectos se redespliegan en cada push, con o sin detección de rutas. La lógica de detección de rutas se seguía rompiendo en escenarios de first-build y replay en Jenkins, de formas fáciles de pasar por alto. Los dos builds son lo bastante pequeños como para que reconstruirlo todo cada vez sea más simple y seguro que intentar ser listo con ello.
  • Telegram es toda la ruta de notificación. Un mensaje de éxito por proyecto desplegado, un mensaje de fallo con la stage que falló, el commit, y un enlace a la consola si el pipeline se rompe. Nada de email, ningún dashboard que tenga que acordarme de revisar.
  • El workspace del build siempre se limpia, tanto si hay éxito como si hay fallo. cleanWs() se ejecuta incondicionalmente en post { cleanup {} }, así que una ejecución fallida nunca deja artefactos a medio construir con los que tropiece la siguiente.
Harbor — el registry de imágenes al que hace push este pipeline.
02

Vigilar que funcione

La observability está dividida según qué pregunta responde, no agrupada en una sola herramienta que intenta hacerlo todo.

Cómo se hablan de verdad las piezas de abajo — exporters a Prometheus, Prometheus a Alertmanager, los dos hacia Grafana.
SeñalRespondeHerramientas
MétricasSalud de host, container, Proxmox, base de datos y appPrometheus + exporters
LogsQué pasó de verdad en un host o servicioGrafana Alloy + Loki
DisponibilidadSi los endpoints importantes responden, desde fuera hacia dentroUptime Kuma
DashboardsConsultar y visualizar las dos señales de arribaGrafana
AlertingEnterarme antes de que lo note un clienteAlertmanager + Telegram, Uptime Kuma + Telegram

El stack detrás de esa tabla se divide en cinco piezas, cada una haciendo exactamente un trabajo:

ComponenteRol
PrometheusHace scrape de métricas, evalúa reglas de alerta
LokiAlmacena los logs que envía Grafana Alloy
AlertmanagerEnruta las alertas de Prometheus a Telegram
GrafanaDashboards sobre Prometheus y Loki
Uptime KumaComprobaciones activas, independientes del pipeline de métricas

Prometheus hace scrape cada 15 segundos, y los targets que varían por host o segmento vienen de archivos JSON de file_sd_configs en lugar de la config principal — añadir una máquina suele ser editar un archivo de targets, no reescribir Prometheus en sí.

Grafana Alloy envía los logs de cada host que lo ejecuta a Loki, guardados 30 días en almacenamiento local en filesystem. El ruler de Loki ya apunta al mismo Alertmanager que usa el alerting de métricas, así que el alerting basado en logs puede reutilizar exactamente esa ruta en cuanto esas reglas se escriban de verdad — ahora mismo, todavía no existe ninguna.

Alertmanager y Uptime Kuma notifican los dos a través de Telegram, pero están respondiendo preguntas distintas. Alertmanager dispara según lo que dicen las métricas que va mal — CPU alta, un target que se queda callado, una base de datos sin conexiones libres — con las alertas críticas repitiéndose más rápido que las warnings, y una warning para un host suprimida mientras ya está disparada una alerta crítica para ese mismo host. Uptime Kuma no mira métricas en absoluto; es una comprobación de fuera hacia dentro, si este endpoint responde de verdad o no. El solape es deliberado: uno detecta degradación interna que el otro no puede ver, el otro detecta «simplemente inalcanzable» incluso cuando todas las métricas internas siguen pareciendo bien.

Uno de los dashboards de Grafana que reviso de verdad a diario.
Cómo se ve una notificación de Alertmanager al llegar a Telegram.

Un puñado de reglas evita que todo el stack se desmadre a medida que crece:

  • Todo host nuevo recibe node_exporter y Grafana Alloy. Sin excepciones — esa es la base mínima antes de instalar nada más en él.
  • cAdvisor solo donde Docker corre de verdad. No tiene sentido hacer scrape de métricas de containers en un host que no tiene ninguno.
  • Uptime Kuma solo donde importa la accesibilidad de cara al usuario. No todos los servicios internos necesitan una comprobación de fuera hacia dentro — solo los que la gente, o los clientes, usan de verdad.
  • Las reglas de alerta existen solo para condiciones sobre las que alguien debería actuar. Ninguna alerta sin una acción detrás, o es solo ruido que se acaba silenciando una semana después.
  • Las reglas de firewall siguen las rutas de scrape, no al revés. Las métricas no tienen un bypass por ser métricas — que Prometheus llegue a un exporter de base de datos sigue siendo una regla explícita y estrecha.
Los grupos de reglas reales detrás de ese último principio — apps, infra y Postgres, cada uno acotado a lo que alguien realmente actuaría.
La vista de estado de Uptime Kuma — la comprobación de fuera hacia dentro.
03

Mantener los hosts consistentes

Tres máquinas con tres historiales de hardware completamente distintos (ver /system) nunca consiguieron gratis un conjunto de hábitos de software a juego — dejado a su aire, eso se convierte en tres entornos operativos inconsistentes, con usuarios distintos, ajustes de SSH distintos, y cobertura de monitoring distinta según quién configuró cada una y cuándo. No quería que «quién lo configuró» fuera una variable, así que la solución es una secuencia de bootstrap fija que corre antes de que una VM o LXC nueva haga cualquier otra cosa: un usuario admin sin privilegios de root, SSH solo con clave con auth por contraseña y login como root desactivados los dos, y los agentes de observability estándar ya reportando antes de que la máquina cuente como «terminada».

Hardening de sshd
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no

Esa última parte — node_exporter y Grafana Alloy en cada host — es lo que mantiene honesta la sección de observability de arriba. Una máquina que no está reportando métricas dentro de un intervalo de scrape desde que se pone en marcha se trata como un bug de setup, no como una tarea de limpieza para después.

Bootstrap corriendo contra una VM nueva (ilustrativo)
$ ./bootstrap-host.sh vm-207
==> creating admin user, disabling root SSH login
==> disabling password authentication
==> installing node_exporter, grafana-alloy
==> registering Prometheus target: node-internal
[ok] vm-207 reporting metrics — 1 scrape interval elapsed

Reiniciar un nodo de Proxmox para una actualización de kernel o trabajo de hardware sigue la misma disciplina en la dirección contraria: primero drenarlo — hacer live-migrate de lo que se pueda mover, parar deliberadamente lo que no — confirmar que no queda nada corriendo en local, y luego reiniciar y vigilar que se reincorpore al cluster. Los nodos van de uno en uno, nunca más, porque el cluster necesita al menos dos de los tres sanos para mantener el quorum; el siguiente no empieza hasta que pvecm status muestra que el anterior ha vuelto del todo.

Drenando pve02 antes de un reinicio (ilustrativo)
$ qm list --node pve02        # live-migrate anything running
$ pct list --node pve02       # stop what can't move
$ qm list --node pve02 && pct list --node pve02
(empty — pve02 is clear)
$ reboot
...
$ pvecm status | grep Quorate
Quorate: Yes
04

Poner algo nuevo en marcha

Todo servicio que se une a esta infraestructura pasa por la misma checklist, específicamente para que nada se desvíe en silencio del modelo de red, proxy y observability descrito en /system:

Onboarding de un servicio nuevo
Service defined → VLAN chosen → address and DNS assigned
  → proxy/firewall path reviewed → observability added
  → persistence risk noted → documentation updated

Las dos decisiones que de verdad importan son en qué segmento aterriza y cuál de los tres proxies lo expone — las dos se derivan directamente de qué es el servicio, no de la comodidad:

Tipo de servicioUbicación por defecto
App o herramienta internaInternal
Base de datos o almacenamiento de objetosData
Build worker o automatización de CICI
Ingress de cara a internetPublic, normalmente solo como entrypoint de proxy
UI admin o de control-planeMgmt, detrás del proxy de management

Todo lo que viene después de esa ubicación sigue de forma mecánica: la regla de firewall más estrecha que encaje con la ruta real, nunca un allow amplio de segmento a segmento; los agentes de observability estándar; un monitor de Uptime Kuma si es de cara al usuario; y, si almacena algo, una nota explícita de que esos datos todavía no tienen backup — fingir lo contrario le quitaría el sentido a escribir nada de esto con honestidad.

Un servicio recién incorporado apareciendo como un scrape pool sano de Prometheus — el último paso de la checklist, hecho visible.
05

Cuando algo se rompe

La documentación aquí se divide en dos tipos, y la distinción importa lo bastante como para mantenerla explícita: un runbook son pasos exactos y repetibles para algo donde los comandos no cambian según el criterio — reinicia esto, migra aquello. Una guía es lo contrario, un marco de decisión para una elección estructural donde la respuesta correcta depende del contexto. Si ya sabes qué tiene que pasar, quieres un runbook; si estás decidiendo qué debería pasar, quieres una guía.

El propio proceso de disaster recovery está escrito deliberadamente como un proceso y no como un script, porque todavía no hay una restauración fuera del nodo probada a la que recurrir — la recuperación depende de lo que todavía exista en los hosts supervivientes y de lo que esté realmente documentado. En orden: averiguar qué falló de verdad — un servicio, una VM, un nodo entero, el storage, o la propia red — comprobar si el cluster de Proxmox sigue teniendo quorum y si DNS, el enrutado y los proxies siguen resolviendo, reconstruir lo que se pueda recuperar usando los docs como fuente de verdad (ubicaciones de servicios, asignaciones de direcciones, intención de las reglas de firewall), y validar el resultado de la misma forma que se valida un deployment nuevo: DNS resuelve, la ruta del proxy funciona, el target de Prometheus vuelve, Uptime Kuma se pone en verde. Lo que sea que el incidente haya dejado al descubierto como ausente — una ruta de config que nadie anotó, una dependencia que nadie mapeó — se añade después a los docs, para que la próxima vez sea un poco menos improvisado.

pvecm status — en mitad de 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 existen hoy, agrupados por lo que cubren:

CategoríaCubre
WireGuardAñadir un peer de acceso remoto, añadir un túnel
Hosts y clusterSetup base de máquinas, mantenimiento de nodos Proxmox, disaster recovery
RedCambios de zona DNS, poner en marcha un segmento nuevo
AppsHosting de sitios estáticos
06

Qué sigue siendo manual

No todo lo de arriba está tan ajustado como suena. Merece la pena decir dos carencias claramente en lugar de maquillarlas, ya que ese es todo el sentido de que esta página sea honesta en vez de aspiracional:

Carencia conocida

Todavía no hay ningún sistema de backup operativo. En la práctica: que un nodo muera con storage solo local, o un error que toque el bucket o la base de datos equivocada, no es recuperable más allá de lo que resulte que siga existiendo en un host superviviente. Es la misma carencia que /system señala en la sección de storage, reformulada aquí en términos operativos en lugar de arquitectónicos.

La segunda es la automatización, o la falta de ella ahora mismo. El setup base de máquinas — la secuencia de bootstrap cubierta antes, en mantener los hosts consistentes — todavía se escribe a mano en cada host nuevo, no se ejecuta desde Ansible o OpenTofu. Es consistente porque sigo el mismo runbook cada vez, no porque una máquina lo imponga, que es una garantía notablemente más débil.

Una más, pequeña: uno de los tres nodos todavía no tiene Wake-on-LAN configurado correctamente. La razón es tan trivial como pueda serlo — nadie ha conectado un monitor para cambiar el ajuste de BIOS correspondiente — y como ese nodo ya tiene gestión de energía out-of-band, no ha sido lo bastante urgente como para arreglarlo.

Las dos carencias grandes están registradas, no ignoradas — el backlog completo y actual (objetivos de backup, pruebas de restauración, rollout de identidad y secrets, infrastructure-as-code) vive en /roadmap, archivado como una lista de tareas honesta y no como una lista de deseos.

07

Qué haría distinto

Tres años después, hay dos cosas que destacan lo bastante claro como para decirlas en voz alta — una que ojalá hubiera hecho antes, otra en la que volvería a tomar la misma decisión sin dudarlo.

  • Ansible y OpenTofu deberían haber empezado el primer día, no ahora. Cuando esto eran dos máquinas, adoptar infrastructure-as-code casi no habría costado nada. Con el desparrame actual de hosts y servicios, incorporarlo ahora es fricción real y constante — tanta que sigo sin llegar a hacerlo, que es exactamente la carencia de automatización descrita arriba. Hacerlo pronto habría mantenido sincronizada la configuración de cada host sin tocar cada una a mano.
  • Segmentar la red mereció la pena, complejidad incluida. Ocho segmentos y las reglas de firewall que los acompañan hacen que los cambios del día a día sean genuinamente más trabajo de lo que sería con una red plana. Volvería a tomar la misma decisión — el aislamiento que consigue vale la fricción de mantenerlo.

Desde aquí: /system cubre lo que existe de verdad — el hardware, la red, los servicios que este pipeline despliega y que este stack vigila. /security cubre cómo se controla el acceso a todo ello. /roadmap es el backlog actual, en orden.