Patrones de Kubernetes
Patrones de Kubernetes de nivel de producción para desplegar, gestionar y depurar cargas de trabajo de manera confiable.
Cuándo activarlo
- Escribir manifiestos de Kubernetes (Deployments, Services, Ingress, Jobs)
- Configurar solicitudes/límites de recursos, sondas de liveness/readiness
- Configurar RBAC, namespaces o ServiceAccounts
- Gestionar configuraciones y secretos en K8s
- Depurar CrashLoopBackOff, OOMKilled, pods pendientes o errores de extracción de imágenes
- Configurar HPA (Horizontal Pod Autoscaler) o PodDisruptionBudgets
- Revisar YAML de K8s por seguridad o corrección
Cuándo usarlo
Igual que Cuándo activarlo arriba. Este alias cumple con las convenciones de formato de skill del repositorio. Usa esta habilidad siempre que estés escribiendo, revisando o depurando YAML y cargas de trabajo de Kubernetes.
Cómo funciona
Esta habilidad proporciona patrones YAML copiables y de nivel de producción y comandos de depuración con kubectl organizados por tarea:
- Plantilla de Deployment — Un
Deploymentde producción completamente configurado con contexto de seguridad, estrategia de actualización rodante, los tres tipos de sondas, límites de recursos e inyección de entorno desde ConfigMap/Secret. - Sondas — Tabla de decisión para startup vs liveness vs readiness, con el cálculo correcto de
failureThreshold × periodSeconds. - Services e Ingress — Patrones de ClusterIP, LoadBalancer e Ingress con TLS y anotaciones de cert-manager.
- ConfigMaps y Secrets — Guía de
envFrom, montaje de archivos y secretos externos. - Gestión de recursos — Reglas generales de Requests vs Limits por tipo de carga de trabajo (API web, JVM, worker, sidecar).
- RBAC — Cadena de ServiceAccount → Role → RoleBinding con mínimos privilegios.
- HPA y PDB — Configuraciones de autoescalado y seguridad ante drenaje de nodos.
- Jobs y CronJobs — Patrones de cargas de trabajo únicas y programadas con
restartPolicycorrecta. - Hoja de referencia de kubectl — Comandos de logs, exec, rollback, port-forward, dry-run y diagnóstico de errores comunes.
- Antipatrones y lista de verificación — Qué NO hacer, y una lista de verificación de seguridad, fiabilidad y observabilidad.
Ejemplos
Consulta las secciones a continuación para ver ejemplos completos y ejecutables. Referencias rápidas:
| Tarea | Ir a |
|---|---|
| YAML completo de Deployment de producción | Patrones de carga de trabajo principales |
| Configuración de sondas | Sondas |
| Configuración RBAC de mínimos privilegios | RBAC |
| Depurar un CrashLoopBackOff | Hoja de referencia de depuración con kubectl |
| Autoescalado | HPA |
Patrones de carga de trabajo principales
Deployment — Plantilla de producción
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
labels:
app: my-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Permitir 1 pod extra durante la actualización
maxUnavailable: 0 # Nunca reducir por debajo del número deseado
template:
metadata:
labels:
app: my-app
version: "1.0.0"
spec:
# Contexto de seguridad a nivel de pod
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# Apagado gradual
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: ghcr.io/org/my-app:1.0.0 # Nunca uses :latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
# Tanto requests como limits son obligatorios
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# Contexto de seguridad del contenedor
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# Sondas (ver la sección de Sondas a continuación)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
# Entorno desde ConfigMap y Secret
envFrom:
- configMapRef:
name: my-app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
# Directorio tmp editable cuando readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Sondas — Liveness, Readiness, Startup
Entender cuándo usar cada sonda es fundamental:
| Sonda | Acción en fallo | Uso |
|---|---|---|
startupProbe | Mata el contenedor si tarda en iniciar | Aplicaciones de inicio lento (JVM, Python) |
livenessProbe | Reinicia el contenedor | Detección de interbloqueo / proceso colgado |
readinessProbe | Elimina de los endpoints del Service | Indisponibilidad temporal (reconexión a BD) |
# Patrón correcto: startupProbe cubre el inicio lento,
# luego liveness/readiness toman el control
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 * 5s = 150s de tiempo máximo de inicio
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30
failureThreshold: 3 # 3 * 30s = 90s antes de reiniciar
readinessProbe:
httpGet:
path: /ready # Endpoint separado: verifica BD, caché, etc.
port: 8080
periodSeconds: 10
failureThreshold: 2
# INCORRECTO: initialDelaySeconds sin startupProbe
# Si la aplicación tarda 60s en iniciar, usa startupProbe en su lugar
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # MAL: Espera arbitraria, condición de carrera
Services e Ingress
Tipos de Service
# ClusterIP (predeterminado) — solo interno
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-namespace
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
# LoadBalancer — tráfico externo (proveedores cloud)
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
Ingress con TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
ConfigMaps y Secrets
ConfigMap — Configuración no sensible
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: my-namespace
data:
LOG_LEVEL: "info"
APP_ENV: "production"
MAX_CONNECTIONS: "100"
# Montar como archivo para configuraciones complejas
app.yaml: |
server:
port: 8080
timeout: 30s
# Montar ConfigMap como archivo
volumes:
- name: config
configMap:
name: my-app-config
items:
- key: app.yaml
path: app.yaml
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
Secrets — Datos sensibles
# Crear secret a partir de literales (CLI, luego almacenar en Vault/SOPS)
kubectl create secret generic my-app-secrets \
--from-literal=db-password='s3cr3t' \
--namespace=my-namespace \
--dry-run=client -o yaml | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: my-namespace
type: Opaque
# Los valores están codificados en base64 (NO cifrados — usa Sealed Secrets o ESO para cifrado real)
data:
db-password: czNjcjN0 # base64 de 's3cr3t'
Importante: Los Secrets sin procesar de Kubernetes solo están codificados en base64, no cifrados en reposo a menos que tu clúster tenga cifrado configurado. Usa Sealed Secrets o External Secrets Operator para producción.
Solicitudes y límites de recursos
resources:
requests: # El scheduler usa esto para colocar el pod
cpu: "100m" # 100 milicores = 0.1 CPU
memory: "128Mi"
limits: # El contenedor se mata/acelera por encima de esto
cpu: "500m"
memory: "256Mi"
Reglas generales:
| Tipo de carga de trabajo | Solicitud de CPU | Solicitud de memoria | Notas |
|---|---|---|---|
| API web | 100–250m | 128–256Mi | Establecer límites 2-4x las solicitudes |
| Worker/consumidor | 250–500m | 256–512Mi | Límite de memoria = solicitud para previsibilidad |
| App JVM | 500m–1 | 512Mi–2Gi | Dejar margen sobre -Xmx para overhead de JVM |
| Sidecar | 10–50m | 32–64Mi | Mantener mínimo |
# INCORRECTO: Sin requests ni limits — programación impredecible, expulsiones OOM
containers:
- name: app
image: myapp:latest
# Falta resources: {} — esto es peligroso en producción
# INCORRECTO: Limits sin requests — requests por defecto a limits, sobre-reserva capacidad
resources:
limits:
cpu: "2"
memory: "1Gi"
# Falta requests — por defecto tomará los valores de limits
RBAC — Roles y ServiceAccounts
Principio de mínimo privilegio
Dos patrones según si la aplicación llama a la API de Kubernetes:
Patrón A — La aplicación NO necesita la API de Kubernetes (la mayoría de las apps)
Deshabilitar el montaje automático de tokens en el ServiceAccount. No se necesitan Role/RoleBinding.
# ServiceAccount con token deshabilitado — opción más segura por defecto
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: false # No se inyecta token de API de K8s en los pods
# Referencia en Deployment — sin token, sin acceso a la API
spec:
template:
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false # Medida adicional: también se establece a nivel de pod
Patrón B — La aplicación SÍ necesita la API de Kubernetes (operadores, controladores, vigilantes de configuración)
Habilitar el token y conceder solo los permisos realmente necesarios.
# 1. ServiceAccount — habilitar token para este SA
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: true # Token requerido: la app llama a la API de K8s
# 2. Role — conceder solo lo que la app necesita (ámbito de namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"] # Solo lectura, recurso específico
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secrets"] # Restringir a un secreto específico por nombre
verbs: ["get"]
# 3. Vincular Role al ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: my-namespace
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: my-app-role
# 4. Referenciar SA en Deployment
spec:
template:
spec:
serviceAccountName: my-app-sa
# automountServiceAccountToken por defecto a true desde SA — el token se inyecta
Horizontal Pod Autoscaler (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-namespace
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2 # Siempre al menos 2 para alta disponibilidad
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Escalar cuando CPU media > 70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
HPA requiere que
resources.requestsesté definido en todos los contenedores — calcula la utilización comoactual / solicitud.
PodDisruptionBudget (PDB)
Evita que demasiados pods caigan durante drenajes de nodos o actualizaciones rodantes:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-namespace
spec:
minAvailable: 2 # O usar maxUnavailable: 1
selector:
matchLabels:
app: my-app
Namespaces y multiinquilinos
# Crear namespace con cuotas de recursos
kubectl create namespace my-namespace
# Aplicar ResourceQuota para limitar el consumo del namespace
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
EOF
Jobs y CronJobs
# Job único (migración de BD, procesamiento de datos)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-namespace
spec:
backoffLimit: 3 # Reintentar hasta 3 veces en caso de fallo
ttlSecondsAfterFinished: 3600 # Auto-eliminar después de 1h
template:
spec:
restartPolicy: OnFailure # Never para Jobs (no Always)
containers:
- name: migrate
image: ghcr.io/org/my-app:1.0.0
command: ["python", "manage.py", "migrate"]
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
Hoja de referencia de depuración con kubectl
# Logs
kubectl logs deployment/my-app # Últimos logs
kubectl logs deployment/my-app --tail=50 # Últimas 50 líneas
kubectl logs deployment/my-app -f # Seguir logs
# Exec en contenedor en ejecución
kubectl exec -it deployment/my-app -- /bin/sh
# Rollback
kubectl rollout undo deployment/my-app # Retroceder al anterior
kubectl rollout undo deployment/my-app --to-revision=3 # Retroceder a la revisión 3
# Port-forward (depuración local)
kubectl port-forward deployment/my-app 8080:8080
# Dry-run (validar YAML)
kubectl apply -f manifest.yaml --dry-run=client -o yaml
# Eventos del pod
kubectl describe pod -l app=my-app | grep -A 10 Events
# Encontrar el nodo de un pod
echo "Los pods CrashLoopBackOff suelen necesitar logs o exec"
kubectl get pods | grep CrashLoopBackOff
kubectl logs pods/<nombre-del-pod-crascado>
# Diagnóstico de errores comunes
kubectl describe pod <nombre-del-pod> | grep -i error
kubectl get events --sort-by='.lastTimestamp'