Patron de Monitoreo de Endpoints de Salud
Expone endpoints de salud ligeros para que orquestadores, balanceadores de carga y herramientas de monitoreo verifiquen la disponibilidad del servicio.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
El Patron de Monitoreo de Endpoints de Salud expone endpoints ligeros que reportan si un servicio esta vivo y listo para recibir trafico. Los balanceadores de carga, orquestadores de contenedores y herramientas de monitoreo pueden consultar estos endpoints para decidir si enrutar trafico hacia una instancia o reiniciarla.
Este patron es la base de los sistemas auto-curativos y es esencial para cualquier servicio que se ejecute en un entorno dinamico donde las instancias pueden fallar o reiniciarse en cualquier momento.
Cuándo Usar
- For alternatives, see Complete Guide to Observability with the Grafana Stack.
Usa este patron cuando:
- Ejecutes servicios en contenedores o detras de un balanceador de carga
- Quieras que un orquestador reinicie instancias no saludables automaticamente
- Necesites distinguir entre “el proceso esta corriendo” y “el servicio es usable”
- Quieras agregar health checks de dependencias sin modificar el codigo cliente
- Necesites mostrar datos de salud en un dashboard de monitoreo o sistema de alertas
Solución
// Endpoints de salud Express con probes de liveness y readiness
const express = require('express');
const app = express();
app.get('/health/live', (req, res) => {
res.status(200).json({ status: 'alive' });
});
app.get('/health/ready', async (req, res) => {
const dbHealthy = await checkDatabaseConnection();
const cacheHealthy = await checkCacheConnection();
if (dbHealthy && cacheHealthy) {
res.status(200).json({ status: 'ready' });
} else {
res.status(503).json({ status: 'not ready' });
}
});
app.listen(3000);
# Probes de liveness y readiness en Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
template:
spec:
containers:
- name: api
image: api:latest
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
Explicación
Los endpoints de salud separan dos preocupaciones:
- Liveness: el proceso esta corriendo y no deberia reiniciarse. Si el liveness falla, el orquestador mata el contenedor e inicia uno nuevo.
- Readiness: el servicio esta listo para recibir trafico. Si el readiness falla, el balanceador deja de enviar solicitudes pero no reinicia la instancia.
Al verificar dependencias como bases de datos, caches y colas de mensajes, los probes de readiness evitan que el trafico llegue a una instancia que no puede atender solicitudes correctamente. Esto mejora la confiabilidad y reduce las tasas de error durante despliegues o interrupciones.
Variantes
| Endpoint | Proposito | Respuesta |
|---|---|---|
| Liveness | El proceso esta vivo? | 200 cuando corre, 500 en caso contrario |
| Readiness | Puede atender trafico? | 200 cuando las dependencias estan saludables, 503 en caso contrario |
| Startup | Ha terminado de iniciar? | 200 cuando la inicializacion completa |
| Deep health | Estado detallado de subsistemas | JSON con salud por dependencia |
Lo que Funciona
- Manten el probe de liveness ligero y libre de dependencias
- Haz que el probe de readiness refleje la capacidad real de atender solicitudes
- Devuelve codigos de estado consistentes (
200saludable,503no saludable) - Evita operaciones pesadas en los health checks para prevenir falsos fallos
- Agrega timeouts y presupuestos de reintentos para verificaciones de dependencias
- Registra los fallos de health checks para debugging pero no satures los logs en cada llamada
Errores Comunes
- Usar un unico endpoint que devuelve OK incluso cuando el servicio esta roto
- Hacer que los health checks dependan de servicios externos que no son criticos
- Devolver
500para liveness, causando reinicios innecesarios - Olvidar probar los readiness probes durante los despliegues
- Exponer endpoints de salud publicamente sin autenticacion o rate limiting
Soluciones Avanzadas
Endpoint de salud profundo con verificaciones de dependencias
Implementa un endpoint de salud comprehensivo que verifica todas las dependencias:
const express = require('express');
const app = express();
const healthChecks = {
database: async () => {
try {
await pool.query('SELECT 1');
return { status: 'healthy', latency: Date.now() - start };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
},
cache: async () => {
try {
await cache.ping();
return { status: 'healthy' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
},
messageQueue: async () => {
try {
await channel.checkQueue();
return { status: 'healthy' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
}
};
app.get('/health/deep', async (req, res) => {
const results = {};
let overallHealthy = true;
for (const [name, check] of Object.entries(healthChecks)) {
try {
const start = Date.now();
const result = await check();
results[name] = { ...result, checkTime: Date.now() - start };
if (result.status !== 'healthy') {
overallHealthy = false;
}
} catch (error) {
results[name] = { status: 'error', error: error.message };
overallHealthy = false;
}
}
res.status(overallHealthy ? 200 : 503).json({
status: overallHealthy ? 'healthy' : 'unhealthy',
checks: results,
timestamp: new Date().toISOString()
});
});
Probe de startup para servicios de inicializacion lenta
Usa un probe de startup para servicios que toman tiempo en inicializar:
# Deployment de Kubernetes con probe de startup
apiVersion: apps/v1
kind: Deployment
metadata:
name: slow-startup-service
spec:
template:
spec:
containers:
- name: app
image: app:latest
startupProbe:
httpGet:
path: /health/startup
port: 3000
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30 # Permitir hasta 5 minutos para iniciar
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 30
periodSeconds: 15
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
// Endpoint de startup que devuelve exito solo despues de inicializacion
let isInitialized = false;
async function initialize() {
// Realizar tareas de inicializacion lentas
await loadConfiguration();
await warmUpCache();
await connectToExternalServices();
isInitialized = true;
}
app.get('/health/startup', (req, res) => {
if (isInitialized) {
res.status(200).json({ status: 'initialized' });
} else {
res.status(503).json({ status: 'initializing' });
}
});
// Iniciar inicializacion en background
initialize();
Endpoint de salud con circuit breaker
Agrega el patron de circuit breaker para prevenir tormentas de health checks:
class HealthCheckCircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.lastFailureTime = null;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'closed'; // closed, open, half-open
}
recordSuccess() {
this.failureCount = 0;
this.state = 'closed';
}
recordFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.threshold) {
this.state = 'open';
}
}
shouldAllowCheck() {
if (this.state === 'closed') return true;
if (this.state === 'open') {
const timeSinceLastFailure = Date.now() - this.lastFailureTime;
if (timeSinceLastFailure > this.timeout) {
this.state = 'half-open';
return true;
}
return false;
}
return true;
}
}
const circuitBreaker = new HealthCheckCircuitBreaker();
app.get('/health/ready', async (req, res) => {
if (!circuitBreaker.shouldAllowCheck()) {
return res.status(503).json({ status: 'circuit open' });
}
try {
const dbHealthy = await checkDatabaseConnection();
const cacheHealthy = await checkCacheConnection();
if (dbHealthy && cacheHealthy) {
circuitBreaker.recordSuccess();
res.status(200).json({ status: 'ready' });
} else {
circuitBreaker.recordFailure();
res.status(503).json({ status: 'not ready' });
}
} catch (error) {
circuitBreaker.recordFailure();
res.status(503).json({ status: 'error', message: error.message });
}
}); Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Patron de Enrutamiento de Gateway
Enruta solicitudes a multiples servicios backend a traves de un unico punto de entrada que gestiona preocupaciones transversales.
PatternPatrón Anti-Corruption Layer
Cómo isolatar legacy systems con translation adapters. Cubre ACL facade, domain translation, bidirectional mapping, y gradual legacy replacement.
PatternPatrón Content Delivery Network (CDN)
Distribuye contenido estático y en vivo a través de servidores edge geográficamente dispersos para reducir latencia, mejorar disponibilidad y descargar infraestructura de origen.