Docker en producción: gestión de secretos, healthchecks y estrategias de despliegue cero downtime
Guía práctica sobre implementación segura de contenedores Docker en producción, cubriendo gestión de secretos, monitoreo de salud y despliegues sin interrupciones.
Docker en Producción: Gestión de Secretos, Healthchecks y Estrategias de Despliegue Cero Downtime
Introducción
Llevar Docker a producción requiere más que containerizar una aplicación. Los tres pilares que separan un despliegue frágil de una infraestructura robusta son: la gestión segura de secretos, la capacidad de detectar fallos antes que los usuarios, y la capacidad de actualizar servicios sin interrupciones. Este artículo aborda estos aspectos desde la perspectiva de quien ha visto contenedores caer en prod a las 3 de la mañana.
Gestión de Secretos en Docker
El problema real
Hardcodear credenciales en imágenes Docker es un riesgo de seguridad crítico. Las imágenes se distribuyen, se almacenan en registros y se inspeccionan en múltiples puntos. Un secreto en el Dockerfile es accesible a cualquiera con acceso a la imagen.
Soluciones prácticas
Docker Secrets (en Swarm) permite almacenar datos sensibles de forma cifrada:
# docker-compose.yml con gestión de secretos
version: '3.8'
services:
api:
image: myapp:latest
secrets:
- db_password
- api_key
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
deploy:
replicas: 3
secrets:
db_password:
external: true
api_key:
external: true
Para entornos Kubernetes (más frecuente en producción moderna):
# Crear secretos en Kubernetes
kubectl create secret generic db-credentials \
--from-literal=username=dbuser \
--from-literal=password=$(openssl rand -base64 32)
# Verificar que el secreto existe
kubectl get secrets db-credentials -o yaml
Best practices críticas
- Nunca loguear secretos: Configurar tu aplicación para no imprimir credenciales incluso en debug
- Rotación periódica: Implementar un ciclo de 90 días mínimo para credenciales críticas
- RBAC estricto: Solo los pods que necesitan acceso deben poder leerlos
- Cifrado en tránsito: Usar TLS para todo lo que cruza la red
Healthchecks: Más allá del “está levantado”
Por qué importa
Un contenedor “running” no significa que la aplicación funcione. He visto casos donde un servicio respondía 503 a todos los requests pero Docker lo reportaba como healthy. Los healthchecks previenen eso.
Implementación en Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# Healthcheck que verifica el endpoint /health
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
EXPOSE 8000
CMD ["python", "app.py"]
Configuración avanzada en orquestadores
En Kubernetes, los liveness, readiness y startup probes proporcionan control granular:
apiVersion: v1
kind: Pod
metadata:
name: api-pod
spec:
containers:
- name: api
image: myapp:latest
ports:
- containerPort: 8000
# Detecta si el contenedor está vivo
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
# Detecta si está listo para recibir tráfico
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2
# Tiempo que espera antes de aplicar liveness
startupProbe:
httpGet:
path: /startup
port: 8000
failureThreshold: 30
periodSeconds: 10
Punto crítico: El endpoint /ready debe verificar dependencias (BD, caché). Si la BD cae, el pod debe volverse no-ready para que el load balancer lo excluya.
Despliegues Cero Downtime
Estrategia Rolling Deployment
La opción más común y eficaz para la mayoría de casos:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Máximo un pod extra durante la actualización
maxUnavailable: 0 # Ningún pod unavailable
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
terminationGracePeriodSeconds: 30
containers:
- name: api
image: myapp:v2.0.1
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
La clave: terminationGracePeriodSeconds permite que las conexiones en vuelo se completen. El preStop adicional crea un buffer para que el load balancer retire el pod de rotación.
Validación de despliegue
No basta con que Kubernetes reporte “ready”. Necesitas validación real:
#!/bin/bash
# Script de validación post-despliegue
DEPLOYMENT="api-deployment"
NAMESPACE="production"
TIMEOUT=300
echo "Verificando rollout..."
kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE --timeout=${TIMEOUT}s
if [ $? -ne 0 ]; then
echo "ERROR: Rollout falló"
kubectl logs -n $NAMESPACE -l app=api --tail=50
exit 1
fi
echo "Verificando endpoints activos..."
ENDPOINTS=$(kubectl get endpoints $DEPLOYMENT -n $NAMESPACE -o jsonpath='{.subsets[0].addresses[*].ip}' | wc -w)
REPLICAS=$(kubectl get deployment $DEPLOYMENT -n $NAMESPACE -o jsonpath='{.spec.replicas}')
if [ $ENDPOINTS -ne $REPLICAS ]; then
echo "ERROR: No todos los pods están listos ($ENDPOINTS/$REPLICAS)"
exit 1
fi
echo "Verificando salud de aplicación..."
HEALTH=$(curl -s -f -o /dev/null -w "%{http_code}" http://api-service/health)
if [ "$HEALTH" != "200" ]; then
echo "ERROR: Health check retornó $HEALTH"
exit 1
fi
echo "Despliegue exitoso"
exit 0
Errores Comunes en Producción
- No esperar a readiness antes de enviar tráfico: Causa picos de error en despliegues
- Healthchecks demasiado agresivos: Restarts innecesarios consumen recursos