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.
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
# 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
# ======= 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
- Copiar requirements primero β aprovecha la cache de capas
- Multi-stage β la imagen final no incluye compiladores
- Usuario no-root β nunca ejecutar como root en produccion
- .dockerignore β excluir .git, pycache, .env, node_modules
.dockerignore
.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.
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 β β
β βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | Componente | Responsabilidad | Puerto |
|---|---|---|
| API Server | Gateway de todas las operaciones del cluster | 6443 |
| etcd | Almacenamiento key-value del estado del cluster | 2379 |
| Scheduler | Decide en que nodo se ejecuta cada pod | 10251 |
| kubelet | Agente en cada nodo que gestiona los pods | 10250 |
| kube-proxy | Reglas 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.
# 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.
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) # 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).
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
# 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.
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
# 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
# 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
# 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.
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
# 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 # 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
# 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
# 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.
# 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.
| Metodo | Caso de uso | Seguridad | Rotacion |
|---|---|---|---|
| K8s Secret nativo | Desarrollo, equipos pequenos | Base64 (no cifrado) | Manual |
| Sealed Secrets | GitOps (secrets cifrados en Git) | Cifrado asimetrico | Manual |
| External Secrets Operator | Produccion, compliance | Proveedor externo cifrado | Automatica |
| HashiCorp Vault + CSI | Seguridad avanzada, auditoria | Vault policies + audit | Automatica + lease |