Instalación de Gatus: Dashboard de Salud para Endpoints

Gatus es una herramienta de monitoreo de salud de endpoints ligera y declarativa que comprueba periódicamente URLs, APIs, bases de datos y servicios TCP/UDP, generando una página de estado pública y enviando alertas cuando algo falla. Con su configuración en YAML y su imagen Docker de menos de 20 MB, Gatus es fácil de desplegar y mantener sin las complejidades de soluciones más pesadas como Nagios o Zabbix. Esta guía cubre la instalación, configuración de checks, condiciones de salud y la integración con Slack, PagerDuty y email.

Requisitos Previos

  • Ubuntu 20.04/22.04 o CentOS 8+/Rocky Linux 8+
  • Docker (recomendado) o binario Go
  • Puerto 8080 disponible para la interfaz web
  • Acceso a los endpoints que quieres monitorear

Instalación de Gatus

Instalación con Docker

# Crear directorio de configuración
mkdir -p /opt/gatus

# Crear el archivo de configuración (ver siguiente sección)
nano /opt/gatus/config.yaml

# Ejecutar Gatus con Docker
docker run -d \
  --name gatus \
  -p 8080:8080 \
  -v /opt/gatus/config.yaml:/config/config.yaml:ro \
  twinproduction/gatus:latest

# Verificar que está corriendo
docker logs gatus
curl http://localhost:8080/health

Instalación con Docker Compose

# /opt/gatus/docker-compose.yml
version: "3.8"
services:
  gatus:
    image: twinproduction/gatus:latest
    container_name: gatus
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./config.yaml:/config/config.yaml:ro
    environment:
      SLACK_WEBHOOK_URL: https://hooks.slack.com/services/TU/WEBHOOK/URL
      PAGERDUTY_INTEGRATION_KEY: tu_integration_key
docker compose up -d

Instalación del binario

# Descargar el binario precompilado
GATUS_VERSION=5.10.0
wget "https://github.com/TwiN/gatus/releases/download/v${GATUS_VERSION}/gatus_linux_amd64.tar.gz" -P /tmp
cd /tmp && tar xzf gatus_linux_amd64.tar.gz

sudo mv gatus /usr/local/bin/
sudo chmod +x /usr/local/bin/gatus

# Crear servicio systemd
sudo tee /etc/systemd/system/gatus.service > /dev/null <<EOF
[Unit]
Description=Gatus Health Dashboard
After=network.target

[Service]
Type=simple
User=gatus
ExecStart=/usr/local/bin/gatus --config=/opt/gatus/config.yaml
Restart=on-failure
Environment="GATUS_CONFIG_PATH=/opt/gatus/config.yaml"

[Install]
WantedBy=multi-user.target
EOF

sudo useradd -r -s /bin/false gatus
sudo mkdir -p /opt/gatus
sudo chown -R gatus:gatus /opt/gatus
sudo systemctl daemon-reload
sudo systemctl enable gatus --now

Configuración Básica

El archivo de configuración /opt/gatus/config.yaml:

# /opt/gatus/config.yaml

# Configuración de almacenamiento (para historial de uptime)
storage:
  type: sqlite
  path: /data/gatus.db

# Configuración del servidor web
web:
  port: 8080

# Métricas de Prometheus
metrics: true

# Configuración de alertas globales
alerting:
  slack:
    webhook-url: "$SLACK_WEBHOOK_URL"
    default-alert:
      description: "Un endpoint está fallando"
      send-on-resolved: true
      failure-threshold: 3
      success-threshold: 2

# Definición de los endpoints a monitorear
endpoints:
  - name: Sitio Web Principal
    url: https://www.tudominio.com
    interval: 1m
    conditions:
      - "[STATUS] == 200"
      - "[RESPONSE_TIME] < 2000"
    alerts:
      - type: slack

  - name: API de Producción
    url: https://api.tudominio.com/health
    interval: 30s
    conditions:
      - "[STATUS] == 200"
      - "[BODY].status == healthy"
      - "[RESPONSE_TIME] < 500"
    alerts:
      - type: slack
        failure-threshold: 2

Endpoints y Condiciones

Gatus evalúa condiciones para determinar si un endpoint está saludable:

endpoints:
  # Check HTTP básico
  - name: API REST
    url: https://api.ejemplo.com/v1/status
    method: GET
    interval: 30s
    headers:
      Authorization: "Bearer tu-token"
      Content-Type: "application/json"
    conditions:
      - "[STATUS] == 200"                      # Código HTTP debe ser 200
      - "[RESPONSE_TIME] < 1000"               # Tiempo de respuesta < 1 segundo
      - "[BODY].status == ok"                  # Campo 'status' en JSON = 'ok'
      - "[BODY].version != "                   # El campo 'version' no debe estar vacío
      - "[CERTIFICATE_EXPIRATION] > 168h"      # Certificado SSL válido > 7 días

  # Check POST con body JSON
  - name: API de Autenticación
    url: https://auth.ejemplo.com/api/check
    method: POST
    body: |
      {"ping": true}
    headers:
      Content-Type: "application/json"
    interval: 1m
    conditions:
      - "[STATUS] == 200"
      - "[BODY].pong == true"

  # Check TCP (para servicios que no son HTTP)
  - name: PostgreSQL
    url: tcp://postgres.interno:5432
    interval: 30s
    conditions:
      - "[CONNECTED] == true"

  # Check DNS
  - name: DNS externo
    url: https://8.8.8.8
    dns:
      query-name: api.ejemplo.com
      query-type: A
    interval: 5m
    conditions:
      - "[DNS_RCODE] == NOERROR"
      - "[BODY] == 93.184.216.34"  # IP esperada

  # Check ICMP (ping)
  - name: Ping al servidor de base de datos
    url: icmp://192.168.1.10
    interval: 30s
    conditions:
      - "[CONNECTED] == true"

Condiciones disponibles:

CondiciónDescripción
[STATUS]Código de respuesta HTTP
[RESPONSE_TIME]Tiempo de respuesta en ms
[BODY]Cuerpo de la respuesta (soporta JSONPath)
[BODY].campoCampo específico del JSON de respuesta
[CONNECTED]Si se pudo conectar (TCP/ICMP)
[CERTIFICATE_EXPIRATION]Tiempo hasta expirar el certificado SSL
[DNS_RCODE]Código de respuesta DNS

Checks Avanzados

endpoints:
  # Verificar que el cuerpo contiene una cadena
  - name: Página de Login
    url: https://app.ejemplo.com/login
    interval: 2m
    conditions:
      - "[STATUS] == 200"
      - "[BODY] pat (Login|Sign in)"  # Expresión regular

  # Check con autenticación básica
  - name: Panel de Admin (protegido)
    url: https://admin.ejemplo.com/health
    interval: 5m
    headers:
      Authorization: "Basic dXNlcjpwYXNz"  # base64 de "user:pass"
    conditions:
      - "[STATUS] == 200"

  # Agrupar endpoints relacionados
  - name: API Gateway
    group: Infraestructura
    url: https://gateway.ejemplo.com/health
    interval: 30s
    conditions:
      - "[STATUS] == 200"

  - name: Servicio de Pagos
    group: Infraestructura
    url: https://pagos.ejemplo.com/health
    interval: 30s
    conditions:
      - "[STATUS] == 200"
      - "[RESPONSE_TIME] < 500"

  # Reintentos antes de marcar como fallido
  - name: Endpoint inestable
    url: https://servicio-inestable.ejemplo.com/health
    interval: 1m
    ui:
      description: "Servicio con alta latencia ocasional"
    conditions:
      - "[STATUS] == 200"
    alerts:
      - type: slack
        failure-threshold: 5    # Fallar 5 veces antes de alertar
        success-threshold: 3    # 3 éxitos consecutivos para resolver

Alertas y Notificaciones

alerting:
  # Slack
  slack:
    webhook-url: "$SLACK_WEBHOOK_URL"
    default-alert:
      send-on-resolved: true
      failure-threshold: 3
      success-threshold: 2

  # PagerDuty
  pagerduty:
    integration-key: "$PAGERDUTY_INTEGRATION_KEY"
    default-alert:
      send-on-resolved: true
      failure-threshold: 2
      success-threshold: 1

  # Email via SMTP
  email:
    from: "[email protected]"
    username: "[email protected]"
    password: "$EMAIL_PASSWORD"
    host: smtp.gmail.com
    port: 587
    to: "[email protected]"
    default-alert:
      send-on-resolved: true
      failure-threshold: 3

  # Telegram
  telegram:
    token: "$TELEGRAM_BOT_TOKEN"
    id: "$TELEGRAM_CHAT_ID"
    default-alert:
      send-on-resolved: true
      failure-threshold: 2

  # Webhook genérico
  custom:
    url: "https://webhook.tudominio.com/alertas"
    method: POST
    body: |
      {
        "text": "[ALERT_TRIGGERED] - [ENDPOINT_NAME] is [RESULT_CONDITION]"
      }
    headers:
      Content-Type: "application/json"
      Authorization: "Bearer TU_TOKEN"

Página de Estado

Gatus genera automáticamente una página de estado pública en http://tu-ip:8080.

Personalización de la interfaz:

# En config.yaml
ui:
  title: "Estado de Servicios - Mi Empresa"
  description: "Monitoreo en tiempo real de todos nuestros servicios"
  header: "Mi Empresa - Status Page"
  logo: "https://tudominio.com/logo.png"
  link: "https://tudominio.com"
  buttons:
    - name: "Portal"
      link: "https://portal.tudominio.com"
    - name: "Documentación"
      link: "https://docs.tudominio.com"
# Configurar Nginx para servir Gatus con dominio propio
server {
    listen 443 ssl;
    server_name status.tudominio.com;

    ssl_certificate /etc/letsencrypt/live/status.tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/status.tudominio.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Métricas de Prometheus

Con metrics: true en la configuración, Gatus expone métricas en /metrics:

# Ver métricas de Gatus
curl http://localhost:8080/metrics | grep gatus

# Métricas disponibles:
# gatus_results_total - Número total de checks ejecutados
# gatus_results_duration_seconds - Tiempo de respuesta de los checks
# gatus_results_connected - Si el endpoint está conectado (1/0)
# gatus_results_code_xxx_total - Conteo de respuestas por código HTTP
# Configuración de Prometheus para scrapear Gatus
# En /etc/prometheus/prometheus.yml
scrape_configs:
  - job_name: 'gatus'
    static_configs:
      - targets: ['gatus:8080']
    metrics_path: /metrics

Solución de Problemas

Gatus no arranca:

# Ver logs del contenedor
docker logs gatus --tail 50

# Verificar la sintaxis del archivo de configuración
docker run --rm \
  -v /opt/gatus/config.yaml:/config/config.yaml:ro \
  twinproduction/gatus validate

# El error más común es una indentación incorrecta en el YAML
python3 -c "import yaml; yaml.safe_load(open('/opt/gatus/config.yaml'))"

Los endpoints siempre aparecen como fallidos:

# Verificar conectividad desde el contenedor/servidor de Gatus
docker exec gatus curl -v https://tu-endpoint.com/health

# Si el endpoint requiere certificados específicos:
docker exec gatus curl --cacert /ruta/ca.pem https://endpoint-interno.com

Las alertas no llegan:

# Verificar las variables de entorno de Slack
docker exec gatus env | grep SLACK

# Verificar el webhook de Slack manualmente
curl -X POST "$SLACK_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"text": "Prueba de Gatus"}'

# Revisar los logs de Gatus para errores de alerta
docker logs gatus | grep -i "alert\|error"

La página de estado muestra datos incorrectos:

# Ver el estado actual de todos los endpoints via API
curl http://localhost:8080/api/v1/endpoints/statuses | python3 -m json.tool

# Forzar un check inmediato (no disponible via API, reiniciar el servicio)
docker restart gatus

Conclusión

Gatus es una solución de monitoreo de endpoints elegante que prioriza la simplicidad sin sacrificar funcionalidades esenciales como las alertas multicanel, el historial de uptime y la página de estado pública. La configuración declarativa en YAML facilita el versionado en Git y la gestión como código, mientras que el uso de variables de entorno para datos sensibles mantiene las credenciales fuera del repositorio. Para la mayoría de equipos pequeños y medianos, Gatus cubre el 90% de las necesidades de monitoreo de disponibilidad con una fracción del esfuerzo operativo de soluciones más complejas.