Script bash servicios-check — sonda de servicios internos
Descripción
Sonda de vigilancia de servicios internos que UptimeRobot no puede alcanzar (están tras NetBird, solo accesibles desde OPS). Corre en cron cada 5 minutos.
Servicios vigilados (targets):
prod-web(prod): Web interna:8000, con cabeceraHost: crearack.com— sano=301/302 (redirección; hasta el 28-08-2026 se pedía por IP desnuda y Django devolvía 400 — contaba como “vivo” pero dejaba un tracebackDisallowedHosten el log de PROD en cada tick, ver footgun abajo)prod-vm(prod): VictoriaMetrics:8428/backend-health— almacén de métricas (hasta el 25-09-2026 era:8428/health; ver footgun B-45 abajo)prod-alertmanager(prod): Alertmanager de PROD:9093/backend-health— sistema de avisos (hasta el 25-09-2026 la sonda medía por error el Alertmanager de STAGE; ver footgun B-45 abajo)stage-alertmanager(stage, desde 25-09-2026): Alertmanager de STAGE:9093/backend-health— sistema de avisos del entorno de pruebasstage-web(stage): Web de STAGE:8000— entorno de pruebasstage-mirror(stage): Espejo DR:8090— copia de emergencia (sano=401)dokploy-stage(stage): Panel Dokploy STAGE:3000— gestión desplieguesdokploy-prod(prod): Panel Dokploy PROD:3000— gestión desplieguesforgejo(ops): Forgejo local:3000— servidor de códigodca-web(dca): Web de DCA (100.96.6.166, no nombre NetBird) — único ojo sobre ese servidor — sano=301runner-ci-1(ops, desde 01-09-2026): Runner 1 del CI (actions.runner.CreaRackSL.crearack-ops-1.service) — sondeounit://, no HTTPrunner-ci-2(ops, desde 01-09-2026): Runner 2 del CI (actions.runner.CreaRackSL.crearack-ops-2.service) — sondeounit://, no HTTP
Lista de targets según el diff de la mega-auditoría B-45 (commit
f90cb92, 25-09-2026); no re-verificada aquí la numeración de targets anteriores a ese cambio (p. ej. la estación 24/7pc-ia-claude, documentada enfeature--infra--servicios-check-sonda-internos).
Lógica de chequeo
Timeout / caído: se cuenta como “down” un timeout OR código ≥500.
Definición de caída: 2 ticks consecutivos (10 minutos, 5 min por tick) → se marca como down y avisa a infra@ con email HTML + lista con luces 🟢/🔴.
Detalle técnico: curl sin || echo 000 (bug cazado en simulacro 11-08: duplicaba el código HTTP a “000000”, dejando la sonda ciega). El curl ya imprime %{http_code} de por sí.
Sondeo unit:// (servicio systemd local, 01-09-2026)
Para targets que no son HTTP ni TCP, sino un servicio systemd de la propia máquina OPS: url con prefijo unit://<nombre-unidad>. El chequeo es systemctl is-active <unidad> — active = vivo (código interno unit-ok); cualquier otro estado (failed, inactive…) = caído (código 000, entra por el mismo camino de conteo que un fallo HTTP/TCP). Hoy solo lo usan runner-ci-1 y runner-ci-2.
Operación
Estructura del array TARGETS
Cada entrada: "<name>|<server>|<url>|<label>|<host>"
name: ID único (ej:stage-web)server: categoría (prod,stage,ops,dca) — usado para agrupar por origen en el dashboardurl: endpoint a chequear (HTTP GET, leyendo solo status)label: nombre humano con contextohost(opcional, desde 28-08-2026): si está presente, se manda como cabeceraHost: <host>en el curl (-H "Host: $host"). Sirve para servicios que rechazan la IP desnuda como Host (DjangoALLOWED_HOSTS) — hoy solo lo usaprod-web(Host: crearack.com).
Ciclo por pasada
- Itera todos los targets.
- Ejecuta curl con timeout y captura código HTTP (añadiendo
-H "Host: ..."si el target trae 5º campo). - Actualiza estado acumulativo en array asociativo
ST[](name → status). - Si alguno cae por segunda vez seguida → lo añade a array
down[]. - Si hay caídas: construye email HTML con lista de luces y envía a
infra@. - Desde 11-08 tarde: publica la foto completa vía MCP
report_service_status(todos los targets, independientemente de estado).
Publicación al workspace (s263, 11-08 tarde)
Nueva función publish_estado():
- Itera todos los targets.
- Construye JSON: array con
{name, server, label, status, http_code}. - Llama a MCP con Bearer token (
.token), CF Access headers (.cf-access-id,.cf-access-secret). - Timeout: 60s.
- Si falla: se loguea en
$LOGcomo WARN, pero la sonda sigue (best-effort, no critical path). - Si éxito: log OK con recuento de servicios.
Integración: [ "$MODE" != "--dry-run" ] && publish_estado → se ejecuta antes de decidir alertas.
Vigilancia
- Registro: logs en
/var/log/servicios-check.log(con timestamp UTC). - Heartbeat: monitoreado por
cron-heartbeatcon ventana de 1 hora. Si no se ejecuta en 1 h → alerta de infra. - Email: solo en transiciones (down→up o up→down), no en cada pasada.
Historias técnicas y footguns
- curl 000000 bug (11-08):
|| echo 000tras curl producía dos códigos. Descubierto en simulacro; arreglado. - DisallowedHost en prod-web (28-08-2026, commit
2cff0939): la sonda pedíaprod-webpor su IP NetBird desnuda (http://100.96.156.31:8000/), sin cabecera Host. Django respondía 400 — contaba como “vivo” para la sonda — pero dejaba un tracebackDisallowedHosten el log de PROD cada 5 minutos (cada tick del cron), ensuciando el log real de la app. Fix: 5º campo opcionalhosten el target (|crearack.com), la sonda mandaHost: crearack.comy Django ahora responde 301/302 sin traceback. - Sondas ciegas al portero vmauth (25-09-2026, mega-auditoría B-45, commit
f90cb92): desde ese día los puertos 8428 y 9093 de NetBird pasan porvmauth([[decision—20260925—vmauth-auth-netbird-b45]]), que contesta/y/healthcon 200/401 aunque el servicio de detrás esté caído — la sonda se habría quedado ciega justo cuando más falta hace. Fix: las dos sondas miran/backend-health, que sí llega al servicio real (502 si cae). De paso se corrigió queprod-alertmanagermedía por error el Alertmanager de STAGE (100.96.254.204) en vez del de PROD, y se añadióstage-alertmanagercomo sonda propia de ese servicio. - Staleness: el endpoint
/api/servicesmarca datos >15 min como rancio. Los LEDs pasan a “sin datos” solos. - Red-alert 24h (anterior a s263): sonda anterior era ciega; se mejoró la robustez.
- Punto ciego de los runners del CI (28-08 → 01-09-2026, commit
3aa6436b): el runnercrearack-ops-1murió por OOM el 28-08 y estuvo cuatro días caído sin que nadie se enterara — el CI seguía en verde porquecrearack-ops-2absorbía todo el trabajo, solo que a mitad de velocidad. Ni UptimeRobot (no ve dentro de la máquina) ni esta sonda (hasta entonces solo HTTP/TCP) lo detectaban. Fix: nuevo tipo de sondeounit://(ver arriba) más los dos runners como targets. Simulacro de caída verificado en el servidor real el 01-09: parar el runner →DOWN: runner-ci-1→ correo de aviso (rojo) → restaurar → correo de recuperación (verde). Script instalado en/opt/servicios-check(copia del anterior conservada en.bak-20260901).
Véase también
- [[entity—functions—tool—report-service-status]]
- [[entity—migrations—table—service-status]]
- [[entity—functions—endpoint—api-services]]
- [[feature—monitoring—led-servicios-onoff]]
- [[concept—infra—health-checks]]
- [[decision—20260925—vmauth-auth-netbird-b45]]