API Health CLI: Monitor de Endpoints en Go
Una herramienta de línea de comandos para monitorear la salud de APIs HTTP en entornos SRE y DevOps. Construida en Go para aprovechar la concurrencia nativa del lenguaje.
El código completo está en mi repositorio de GitHub.
El Problema
Los equipos de infraestructura necesitan verificar rápidamente el estado de múltiples endpoints — durante deployments, incidentes, o como parte de pipelines CI/CD. Las herramientas existentes son o demasiado pesadas o no permiten configuración flexible. Esta CLI resuelve:
- Checks concurrentes: verificar N endpoints en paralelo, no secuencialmente
- Configuración declarativa: archivo YAML con headers, timeouts y status codes por endpoint
- Reintentos con backoff: evitar falsos positivos por timeouts momentáneos
- Modo CI: exit code 1 si algún endpoint falla, ideal para pipelines
Arquitectura
El proyecto sigue una arquitectura en capas con paquetes bien definidos:
cmd/ → Comandos CLI (cobra): check y watch
internal/
checker/ → Lógica de checks HTTP concurrentes con reintentos
config/ → Parser de YAML con expansión de variables de entorno
notifier/ → Notificador Slack via webhook
output/ → Renderizado de resultados en terminal con color
Stack
| Componente | Tecnología |
|---|---|
| Lenguaje | Go 1.22 |
| CLI Framework | Cobra |
| Output con color | fatih/color |
| Config | gopkg.in/yaml.v3 |
| Tests | stdlib (net/http/httptest) |
Decisiones de Diseño
1. Concurrencia con goroutines y WaitGroup
Los checks se ejecutan en goroutines independientes y los resultados se almacenan en un slice pre-asignado por índice, garantizando el orden sin necesidad de sincronización adicional:
func (hc *HealthChecker) CheckAll(endpoints []Endpoint) []Result {
results := make([]Result, len(endpoints))
var wg sync.WaitGroup
for i, ep := range endpoints {
wg.Add(1)
go func(idx int, endpoint Endpoint) {
defer wg.Done()
results[idx] = hc.Check(endpoint)
}(i, ep)
}
wg.Wait()
return results
}
2. Reintentos con exponential backoff
Si un endpoint falla, reintenta con backoff creciente para evitar saturar el servidor:
for attempt := 0; attempt <= retries; attempt++ {
if attempt > 0 {
backoff := time.Duration(math.Pow(2, float64(attempt-1))) * 500 * time.Millisecond
time.Sleep(backoff)
}
lastResult = hc.doCheck(ep.URL, method, ep.Headers, expectedStatus)
if lastResult.Healthy {
return lastResult
}
}
3. Expansión de variables de entorno en config
Los tokens y URLs sensibles no viven en el archivo de configuración; se inyectan como variables de entorno:
endpoints:
- url: https://api.example.com/health
headers:
Authorization: "Bearer ${API_TOKEN}"
notifications:
slack:
webhook_url: "${SLACK_WEBHOOK_URL}"
4. Modo CI con exit code
En pipelines de CI/CD, el flag --ci hace que la herramienta retorne exit code 1 si algún endpoint falla:
healthcheck check https://api.example.com/health --ci
# Retorna exit 1 si el endpoint está caído → el pipeline falla correctamente
Uso
# Check rápido de URLs
healthcheck check https://api.example.com/health https://status.example.com
# Con config file
healthcheck check --config endpoints.yaml
# Con timeout y reintentos
healthcheck check https://api.example.com/health --timeout 3s --retries 2
# Monitoreo continuo (30s por defecto)
healthcheck watch --config endpoints.yaml --interval 10s
# Con notificaciones Slack
healthcheck check --config endpoints.yaml --slack "$SLACK_WEBHOOK_URL"
# Modo CI
healthcheck check --config endpoints.yaml --ci
Salida de ejemplo
ENDPOINT STATUS TIME
──────────────────────────────────────────────────────────────────
✔ https://api.example.com/health 200 OK 124ms
✔ https://status.example.com/ping 200 OK 89ms
✘ https://internal.example.com/readiness 503 Service U 2.1s
2/3 healthy — total time 2.3s
Testing
El proyecto tiene 24 tests en tres paquetes:
- checker (10 tests): checks healthy/unhealthy, status codes custom, headers, método por defecto, orden de resultados concurrentes y User-Agent
- config (9 tests): parsing de YAML, defaults, errores de formato, expansión de env vars, múltiples endpoints, carga desde archivo
- notifier (6 tests): envío de payload, lista vacía sin request, error 500, webhook no alcanzable, múltiples fallos en un request
func TestCheckAll_ReturnsResultsInOrder(t *testing.T) {
statuses := []int{200, 503, 200, 404, 200}
// ... crea un servidor por cada status
results := hc.CheckAll(endpoints)
for i, result := range results {
expectedHealthy := statuses[i] == 200
if result.Healthy != expectedHealthy {
t.Errorf("result[%d]: expected healthy=%v, got healthy=%v", i, expectedHealthy, result.Healthy)
}
}
}
Cobertura: checker 91.8% · config 100% · notifier 95.7%
Cómo Ejecutar
git clone https://github.com/enriquevaldivia1988/api-health-cli.git
cd api-health-cli
# Compilar
make build
# Ejecutar tests
make test
# Instalar globalmente
make install
# Usar directamente con go run
go run . check https://httpbin.org/status/200 https://httpbin.org/status/503
Conclusión
Este proyecto demuestra cómo construir una herramienta CLI de producción en Go: con concurrencia real, configuración flexible, tests con servidores HTTP embebidos y una experiencia de usuario cuidada en terminal.
Enrique Valdivia