Docs / DevOps / Containers & K8s

Containers & K8s

GuΓ­a exhaustiva de containerizaciΓ³n con Docker y orquestaciΓ³n con Kubernetes. Desde la construcciΓ³n de imΓ‘genes optimizadas hasta deployments en producciΓ³n con Helm, monitoring y logging centralizado.

Docker Fundamentals

Docker encapsula aplicaciones y sus dependencias en contenedores aislados que se ejecutan de forma identica en cualquier entorno. La capa de abstraccion se basa en namespaces y cgroups de Linux.

text
Arquitectura de Docker

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Docker Host                        β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚  App A  β”‚  β”‚  App B  β”‚  β”‚  App C  β”‚           β”‚
β”‚  β”‚ Python  β”‚  β”‚  Node  β”‚  β”‚  Go    β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜           β”‚
β”‚       β”‚            β”‚            β”‚                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”            β”‚
β”‚  β”‚         Docker Engine              β”‚            β”‚
β”‚  β”‚   (containerd + runc)             β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚                  β”‚                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚        Linux Kernel                β”‚            β”‚
β”‚  β”‚   namespaces β”‚ cgroups β”‚ overlay  β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Comandos esenciales

bash
# Construir imagen
docker build -t myapp:1.0.0 .

# Ejecutar contenedor
docker run -d --name myapp \
  -p 8000:8000 \
  -e DATABASE_URL=postgresql://... \
  -v ./data:/app/data \
  myapp:1.0.0

# Inspeccionar contenedor en ejecucion
docker logs -f myapp
docker exec -it myapp /bin/sh

# Limpiar recursos no usados
docker system prune -a --volumes

Dockerfile Best Practices

Un Dockerfile optimizado reduce el tamano de la imagen, acelera los builds y minimiza la superficie de ataque. Multi-stage builds son la clave.

Multi-stage build para Python

dockerfile
# ======= Stage 1: Builder =======
FROM python:3.12-slim AS builder

WORKDIR /build

# Instalar dependencias de compilacion
RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc libpq-dev && \
    rm -rf /var/lib/apt/lists/*

# Copiar SOLO requirements primero (mejor cache)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ======= Stage 2: Runtime =======
FROM python:3.12-slim AS runtime

# Crear usuario no-root
RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# Copiar dependencias pre-construidas
COPY --from=builder /install /usr/local

# Copiar codigo fuente
COPY . .

# Configurar usuario y puerto
USER appuser
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=3s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Reglas de oro

  1. Copiar requirements primero β€” aprovecha la cache de capas
  2. Multi-stage β€” la imagen final no incluye compiladores
  3. Usuario no-root β€” nunca ejecutar como root en produccion
  4. .dockerignore β€” excluir .git, pycache, .env, node_modules

.dockerignore

text
.git
.gitignore
__pycache__
*.pyc
.env
.venv
node_modules
*.md
tests/
.coverage

K8s Architecture

Kubernetes orquesta contenedores a escala. El Control Plane toma decisiones globales, y los Worker Nodes ejecutan los pods.

text
Arquitectura de Kubernetes

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       CONTROL PLANE                             β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚API Serverβ”‚  β”‚Controller Mgr  β”‚  β”‚ Scheduler β”‚  β”‚  etcd  β”‚  β”‚
β”‚  β”‚  :6443   β”‚  β”‚ Deployment Ctrlβ”‚  β”‚            β”‚  β”‚  (KV)  β”‚  β”‚
β”‚  β”‚          β”‚  β”‚ ReplicaSet Ctrlβ”‚  β”‚            β”‚  β”‚        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚       β”‚                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚ kubelet API
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      WORKER NODE 1                             β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ kubelet  │────▢│           Pod                            β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚  β”‚Container Aβ”‚  β”‚Container Bβ”‚          β”‚   β”‚
β”‚  β”‚kube-proxyβ”‚     β”‚  β”‚  (app)    β”‚  β”‚ (sidecar) β”‚          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚   β”‚
β”‚                   β”‚  Shared Network Β· Shared Volumes        β”‚   β”‚
β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
ComponenteResponsabilidadPuerto
API ServerGateway de todas las operaciones del cluster6443
etcdAlmacenamiento key-value del estado del cluster2379
SchedulerDecide en que nodo se ejecuta cada pod10251
kubeletAgente en cada nodo que gestiona los pods10250
kube-proxyReglas de red y balanceo en cada nodo-

Deployments & Services

Un Deployment declara el estado deseado de los pods, y el controller se encarga de mantenerlo. Un Service expone los pods a traves de una IP estable.

yaml
# deployment.yaml β€” Aplicacion backend
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-api
  labels:
    app: backend
    version: "1.4.2"
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: registry.io/backend:1.4.2
          ports:
            - containerPort: 8000
          envFrom:
            - secretRef:
                name: backend-secrets
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 5
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
spec:
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8000
  type: ClusterIP

Probes obligatorias

readinessProbe evita enviar trafico a pods que aun no estan listos. livenessProbe reinicia pods que se quedan colgados. Sin probes, K8s no puede distinguir un pod sano de uno muerto.

Helm Charts

Helm es el package manager de Kubernetes. Un chart empaqueta todos los manifiestos YAML con valores configurables via values.yaml.

text
Estructura de un Helm chart

mychart/
β”œβ”€β”€ Chart.yaml          # Metadatos del chart
β”œβ”€β”€ values.yaml         # Valores por defecto
β”œβ”€β”€ templates/
β”‚   β”œβ”€β”€ deployment.yaml  # Template con {{ .Values.* }}
β”‚   β”œβ”€β”€ service.yaml
β”‚   β”œβ”€β”€ ingress.yaml
β”‚   β”œβ”€β”€ configmap.yaml
β”‚   β”œβ”€β”€ secret.yaml
β”‚   β”œβ”€β”€ hpa.yaml           # Horizontal Pod Autoscaler
β”‚   └── _helpers.tpl      # Template helpers
└── charts/              # Sub-charts (dependencias)
bash
# Instalar chart
helm install backend ./mychart \
  -f values-production.yaml \
  -n production

# Actualizar release
helm upgrade backend ./mychart \
  --set image.tag=1.5.0 \
  -n production

# Rollback
helm rollback backend 1 -n production

# Ver historial de releases
helm history backend -n production

Monitoring & Logging

Un stack de observabilidad completo en K8s incluye metricas (Prometheus), dashboards (Grafana), y logs centralizados (Loki o EFK).

text
Stack de observabilidad

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Grafana Dashboard                       β”‚
β”‚              (metricas + logs + alertas)                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                                   β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
    β”‚ Prometheus  β”‚                    β”‚    Loki     β”‚
    β”‚  (metricas) β”‚                    β”‚   (logs)     β”‚
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
           β”‚ scrape /metrics                  β”‚ push logs
    β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
    β”‚              Kubernetes Pods                    β”‚
    β”‚   β”Œβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”         β”‚
    β”‚   β”‚App Aβ”‚  β”‚App Bβ”‚  β”‚App Cβ”‚  β”‚App Dβ”‚         β”‚
    β”‚   β””β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”˜         β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Prometheus alert rules

yaml
# prometheus-rules.yaml
groups:
  - name: app-alerts
    rules:
      - alert: HighErrorRate
        expr: |
          rate(http_requests_total{status=~"5.."}[5m])
          / rate(http_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Error rate > 5% for {{ $labels.service }}"

      - alert: PodCrashLooping
        expr: |
          rate(kube_pod_container_status_restarts_total[15m]) > 0
        for: 15m
        labels:
          severity: warning

Metricas clave a monitorear

RED method: Rate (requests/sec), Errors (5xx/sec), Duration (latency p99).
USE method: Utilization, Saturation, Errors por recurso (CPU, memoria, disco).

Docker Compose

Docker Compose permite definir y ejecutar aplicaciones multi-contenedor con un solo archivo YAML. Es la herramienta estandar para desarrollo local, testing de integracion y entornos de staging simples.

text
Arquitectura multi-servicio con Compose

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Docker Compose Network                   β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚  β”‚  nginx   │──▢│   app    │──▢│ postgres β”‚             β”‚
β”‚  β”‚  :80/443 β”‚   β”‚  :8000   β”‚   β”‚  :5432   β”‚             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β”‚                      β”‚                                    β”‚
β”‚                      β–Ό                                    β”‚
β”‚                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                               β”‚
β”‚                β”‚  redis   β”‚                               β”‚
β”‚                β”‚  :6379   β”‚                               β”‚
β”‚                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚
β”‚                                                          β”‚
β”‚  Volumes: postgres-data, redis-data, app-static          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Compose completo para proyecto backend

yaml
# docker-compose.yml β€” Backend con Postgres, Redis y Nginx
services:
  # ── Base de datos ──
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secretpass}
      POSTGRES_DB: appdb
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - backend-net

  # ── Cache y colas ──
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redispass}
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redispass}", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    networks:
      - backend-net

  # ── Aplicacion backend ──
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    environment:
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD:-secretpass}@postgres:5432/appdb
      REDIS_URL: redis://:${REDIS_PASSWORD:-redispass}@redis:6379/0
      SECRET_KEY: ${SECRET_KEY:-dev-secret-key}
      ENVIRONMENT: development
    ports:
      - "8000:8000"
    volumes:
      - ./src:/app/src          # Hot-reload en desarrollo
      - app-static:/app/static
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 20s
    networks:
      - backend-net

  # ── Reverse proxy ──
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - app-static:/var/www/static:ro
    depends_on:
      app:
        condition: service_healthy
    networks:
      - backend-net

  # ── Worker para tareas asincronas (opcional) ──
  worker:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    command: celery -A app.worker worker --loglevel=info --concurrency=4
    environment:
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD:-secretpass}@postgres:5432/appdb
      REDIS_URL: redis://:${REDIS_PASSWORD:-redispass}@redis:6379/0
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - backend-net

volumes:
  postgres-data:
  redis-data:
  app-static:

networks:
  backend-net:
    driver: bridge

Comandos esenciales de Compose

bash
# Arrancar todos los servicios en background
docker compose up -d

# Arrancar solo servicios de infraestructura
docker compose up -d postgres redis

# Ver logs en tiempo real
docker compose logs -f app worker

# Reconstruir imagen tras cambios en Dockerfile
docker compose up -d --build app

# Escalar workers
docker compose up -d --scale worker=4

# Parar y eliminar contenedores + volumenes
docker compose down -v

# Ejecutar comando dentro de un servicio
docker compose exec app python manage.py migrate
docker compose exec postgres psql -U appuser -d appdb

Archivos .env con Compose

Docker Compose lee automaticamente un archivo .env en el directorio del proyecto. Usa ${VARIABLE:-default} en el YAML para valores configurables. Nunca commitees el .env real β€” incluye un .env.example con valores de referencia.

Perfiles para separar servicios

yaml
# Usar profiles para servicios opcionales
services:
  app:
    # ... configuracion del app

  mailhog:
    image: mailhog/mailhog
    ports:
      - "8025:8025"
      - "1025:1025"
    profiles:
      - debug           # Solo arranca con --profile debug

  adminer:
    image: adminer
    ports:
      - "8081:8080"
    profiles:
      - debug

# Arrancar con perfil: docker compose --profile debug up -d

Secrets & ConfigMaps

Kubernetes separa la configuracion del codigo mediante ConfigMaps (datos no sensibles) y Secrets (credenciales, tokens, certificados). Ambos se pueden montar como variables de entorno o archivos en los pods.

text
Flujo de configuracion en K8s

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         Pod                              β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                  Container                         β”‚  β”‚
β”‚  β”‚                                                   β”‚  β”‚
β”‚  β”‚  ENV:                                             β”‚  β”‚
β”‚  β”‚    DB_HOST=postgres.prod.svc  ← ConfigMap         β”‚  β”‚
β”‚  β”‚    DB_PORT=5432               ← ConfigMap         β”‚  β”‚
β”‚  β”‚    DB_PASSWORD=*****          ← Secret            β”‚  β”‚
β”‚  β”‚    API_KEY=*****              ← Secret            β”‚  β”‚
β”‚  β”‚                                                   β”‚  β”‚
β”‚  β”‚  FILES:                                           β”‚  β”‚
β”‚  β”‚    /etc/app/config.yaml       ← ConfigMap volume  β”‚  β”‚
β”‚  β”‚    /etc/tls/tls.crt           ← Secret volume     β”‚  β”‚
β”‚  β”‚    /etc/tls/tls.key           ← Secret volume     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Crear y gestionar Secrets

yaml
# secret.yaml β€” Secret para credenciales de base de datos
apiVersion: v1
kind: Secret
metadata:
  name: backend-db-credentials
  namespace: production
type: Opaque
stringData:                    # stringData acepta texto plano (K8s lo codifica)
  DB_PASSWORD: "S3cur3P@ssw0rd!"
  DB_USERNAME: "backend_app"
---
# secret-tls.yaml β€” Secret para certificados TLS
apiVersion: v1
kind: Secret
metadata:
  name: backend-tls
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi...    # base64 encoded
  tls.key: LS0tLS1CRUdJTi...    # base64 encoded
bash
# Crear secret desde la linea de comandos
kubectl create secret generic backend-db-credentials \
  --from-literal=DB_PASSWORD='S3cur3P@ssw0rd!' \
  --from-literal=DB_USERNAME='backend_app' \
  -n production

# Crear secret desde archivo
kubectl create secret generic app-config \
  --from-file=config.json=./config/production.json \
  -n production

# Crear secret TLS desde certificados
kubectl create secret tls backend-tls \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key \
  -n production

# Ver secrets (valores codificados en base64)
kubectl get secret backend-db-credentials -n production -o yaml

# Decodificar un valor
kubectl get secret backend-db-credentials -n production \
  -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

Secrets NO son seguros por defecto

Los Secrets de K8s se almacenan en etcd sin cifrar por defecto. Habilita encryption at rest en el API server, y usa RBAC estricto para limitar quien puede leer secrets. En produccion, considera External Secrets Operator.

ConfigMaps para datos no sensibles

yaml
# configmap.yaml β€” Configuracion de la aplicacion
apiVersion: v1
kind: ConfigMap
metadata:
  name: backend-config
  namespace: production
data:
  # Valores simples como key-value
  DB_HOST: "postgres.production.svc.cluster.local"
  DB_PORT: "5432"
  DB_NAME: "appdb"
  LOG_LEVEL: "info"
  CACHE_TTL: "300"

  # Archivo de configuracion completo
  app-config.yaml: |
    server:
      host: 0.0.0.0
      port: 8000
      workers: 4
    cors:
      allowed_origins:
        - "https://app.example.com"
        - "https://admin.example.com"
    features:
      enable_cache: true
      enable_rate_limit: true
      rate_limit_rpm: 100

Montar en un Pod

yaml
# deployment.yaml β€” Pod usando Secret + ConfigMap
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: registry.io/backend:1.4.2
          ports:
            - containerPort: 8000

          # ── Variables de entorno desde ConfigMap ──
          envFrom:
            - configMapRef:
                name: backend-config

          # ── Variables de entorno desde Secret ──
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: backend-db-credentials
                  key: DB_PASSWORD
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: backend-db-credentials
                  key: DB_USERNAME

          # ── Montar archivos de configuracion ──
          volumeMounts:
            - name: app-config-volume
              mountPath: /etc/app
              readOnly: true
            - name: tls-certs
              mountPath: /etc/tls
              readOnly: true

      volumes:
        # ConfigMap como directorio de archivos
        - name: app-config-volume
          configMap:
            name: backend-config
            items:
              - key: app-config.yaml
                path: config.yaml

        # Secret como archivos
        - name: tls-certs
          secret:
            secretName: backend-tls

External Secrets Operator para produccion

El External Secrets Operator (ESO) sincroniza secrets desde proveedores externos (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault) hacia Kubernetes Secrets de forma automatica y segura.

yaml
# external-secret.yaml β€” Sincronizar desde AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: backend-db-credentials
  namespace: production
spec:
  refreshInterval: 1h              # Sincronizar cada hora
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: backend-db-credentials   # Secret de K8s que se creara
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/backend/db
        property: password
    - secretKey: DB_USERNAME
      remoteRef:
        key: production/backend/db
        property: username
---
# cluster-secret-store.yaml β€” Configurar el proveedor
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: eu-west-1
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

Rotacion automatica de secrets

Con External Secrets Operator, al rotar un secret en AWS/GCP/Vault, el operador actualiza automaticamente el Secret de K8s segun el refreshInterval. Los pods que montan el secret como volumen reciben la actualizacion sin reinicio (con un delay de ~1 min por kubelet sync). Los que usan env necesitan un rollout restart.

MetodoCaso de usoSeguridadRotacion
K8s Secret nativoDesarrollo, equipos pequenosBase64 (no cifrado)Manual
Sealed SecretsGitOps (secrets cifrados en Git)Cifrado asimetricoManual
External Secrets OperatorProduccion, complianceProveedor externo cifradoAutomatica
HashiCorp Vault + CSISeguridad avanzada, auditoriaVault policies + auditAutomatica + lease
END OF DOCUMENT

ΒΏNecesitas mΓ‘s? Volver a la LibrerΓ­a →