← Volver al blog

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

  1. Nunca loguear secretos: Configurar tu aplicación para no imprimir credenciales incluso en debug
  2. Rotación periódica: Implementar un ciclo de 90 días mínimo para credenciales críticas
  3. RBAC estricto: Solo los pods que necesitan acceso deben poder leerlos
  4. 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

  1. No esperar a readiness antes de enviar tráfico: Causa picos de error en despliegues
  2. Healthchecks demasiado agresivos: Restarts innecesarios consumen recursos