Pods y Deployments de Kubernetes: Una introducción práctica
Una guía práctica sobre Pods, Deployments y Services de Kubernetes que cubre las abstracciones básicas necesarias para ejecutar cargas de trabajo en producción.

La documentación de Kubernetes es inmensa. La superficie de su API es enorme. Los ingenieros que recién empiezan se quedan mirando un muro de YAML sin saber por dónde empezar. La respuesta son tres primitivas: Pods, Deployments y Services. Estas tres abstracciones cubren el 90 % de lo que necesitas para ejecutar una aplicación web en producción.
Esta guía explica qué hace cada primitiva, cómo interactúan entre sí y qué configuraciones realmente importan en cargas de trabajo reales.
Pods: la unidad más pequeña
Un Pod es uno o más contenedores que se ejecutan juntos en el mismo nodo, compartiendo red y almacenamiento. La mayoría de los Pods ejecutan un único contenedor. El patrón multicontenedor se usa para sidecars: un agente de logging, un proxy o un init container que realiza tareas de configuración antes de que arranque el proceso principal.
# A minimal Pod definition — rarely used directly in production
apiVersion: v1
kind: Pod
metadata:
name: api-server
labels:
app: api
version: v1
spec:
containers:
- name: api
image: myregistry/api-server:1.4.2
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"Casi nunca se crean Pods directamente. Si un Pod muere, nada lo reinicia. Los Deployments gestionan el ciclo de vida de los Pods, por eso son la verdadera unidad de trabajo en producción.
Deployments: gestión del ciclo de vida de los Pods
Un Deployment declara el estado deseado: qué imagen de contenedor usar, cuántas réplicas mantener y cómo desplegar las actualizaciones. El controlador del Deployment reconcilia continuamente la realidad con ese estado deseado.
# ❌ Running pods directly — no restart, no scaling, no rollback
apiVersion: v1
kind: Pod
metadata:
name: api-server-1
spec:
containers:
- name: api
image: myregistry/api-server:1.4.2
---
apiVersion: v1
kind: Pod
metadata:
name: api-server-2
spec:
containers:
- name: api
image: myregistry/api-server:1.4.2# ✅ A Deployment manages pods with automatic restart, scaling, and rollback
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
labels:
app: api
spec:
replicas: 3
selector:
matchLabels:
app: api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api
version: v1
spec:
containers:
- name: api
image: myregistry/api-server:1.4.2
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20Campos clave de este Deployment:
- replicas: 3: ejecuta tres instancias del Pod
- maxSurge: 1: durante las actualizaciones, permite un Pod adicional por encima de la cantidad deseada
- maxUnavailable: 0: nunca cae por debajo de la cantidad de réplicas deseada durante las actualizaciones
- readinessProbe: indica a Kubernetes cuándo el Pod puede aceptar tráfico
- livenessProbe: indica a Kubernetes cuándo el Pod está bloqueado y debe reiniciarse
Actualizaciones progresivas y reversiones
Los Deployments gestionan las actualizaciones reemplazando gradualmente los Pods antiguos por otros nuevos. Los parámetros maxSurge y maxUnavailable controlan la velocidad de la actualización y la disponibilidad durante el proceso.
# Update the image — triggers a rolling update
kubectl set image deployment/api-server \
api=myregistry/api-server:1.5.0
# Watch the rollout progress
kubectl rollout status deployment/api-server
# Check rollout history
kubectl rollout history deployment/api-server
# Rollback to the previous version
kubectl rollout undo deployment/api-server
# Rollback to a specific revision
kubectl rollout undo deployment/api-server --to-revision=3El proceso de actualización progresiva con maxSurge: 1, maxUnavailable: 0 y 3 réplicas funciona así:
Step 1: [v1] [v1] [v1] [v2] ← new Pod created (4 total)
Step 2: [v1] [v1] [v2] [v2] ← old Pod removed, new created
Step 3: [v1] [v2] [v2] [v2] ← continuing replacement
Step 4: [v2] [v2] [v2] ← rollout complete (3 total)
El tráfico solo se enruta hacia los Pods que superan la readiness probe, de modo que los usuarios nunca llegan a un Pod que todavía está iniciando.
Services: cómo exponer los Pods
Los Pods reciben direcciones IP efímeras que cambian con cada reinicio. Un Service proporciona una identidad de red estable: un nombre DNS y una IP fijos que enrutan el tráfico hacia los Pods saludables que coinciden con un selector de etiquetas.
# ClusterIP Service — internal access only
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: ClusterIP
selector:
app: api
ports:
- protocol: TCP
port: 80
targetPort: 3000
---
# Other pods can now reach the API at http://api-service:80
# Kubernetes DNS resolves api-service to the ClusterIP# LoadBalancer Service — external access via cloud load balancer
apiVersion: v1
kind: Service
metadata:
name: api-external
spec:
type: LoadBalancer
selector:
app: api
ports:
- protocol: TCP
port: 443
targetPort: 3000Los distintos tipos de Service cumplen propósitos diferentes:
- ClusterIP: solo acceso interno. Para que los microservicios se comuniquen entre sí.
- NodePort: expone el servicio en la IP de cada nodo a través de un puerto estático. Útil para desarrollo.
- LoadBalancer: aprovisiona un balanceador de carga en la nube. El estándar para servicios de cara al público.
Requests y limits de recursos
Sin una configuración de recursos, un solo Pod puede consumir todos los recursos del nodo y dejar sin recursos a las demás cargas de trabajo. Los requests garantizan una asignación mínima. Los limits establecen el máximo permitido.
resources:
# Requests: minimum guaranteed resources
# Used by scheduler to place pods on nodes
requests:
memory: "128Mi"
cpu: "100m" # 100 millicores = 0.1 CPU cores
# Limits: maximum allowed resources
# Pod is killed (OOMKill) if it exceeds memory limit
# Pod is throttled if it exceeds CPU limit
limits:
memory: "256Mi"
cpu: "500m" # 500 millicores = 0.5 CPU cores# Check actual resource usage vs. requests/limits
kubectl top pods -n default
# Example output:
# NAME CPU(cores) MEMORY(bytes)
# api-server-7d5f8b6c4-abc12 45m 98Mi
# api-server-7d5f8b6c4-def34 52m 102Mi
# api-server-7d5f8b6c4-ghi56 38m 95MiConfigurar requests demasiado altos desperdicia recursos del clúster. Configurarlos demasiado bajos provoca fallos de scheduling cuando los nodos parecen estar llenos. Empieza con requests basados en el uso observado (con kubectl top) y limits equivalentes al doble del request.
Health checks que realmente funcionan
Las readiness probes y las liveness probes son las configuraciones que más se configuran mal. Una mala configuración de los probes provoca fallos en cascada.
# ❌ Common mistake: same endpoint, same timing for both probes
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5# ✅ Different timing, different thresholds
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
# Pod removed from Service after 3 consecutive failures (30s)
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 30
periodSeconds: 20
failureThreshold: 5
# Pod restarted after 5 consecutive failures (100s)
startupProbe:
httpGet:
path: /health/live
port: 3000
failureThreshold: 30
periodSeconds: 10
# Allows up to 300s for slow-starting appsLa liveness probe debería tener un initialDelaySeconds más largo y un failureThreshold más alto que la readiness probe. Una liveness probe demasiado agresiva reinicia Pods que solo están sobrecargados temporalmente, lo que genera un bucle de reinicios que empeora la interrupción.
El endpoint /health/ready de la readiness probe debería verificar las dependencias externas (conexión a la base de datos, disponibilidad de la caché). El endpoint /health/live de la liveness probe debería ser una simple comprobación de que "el proceso sigue vivo": nunca incluyas verificaciones de dependencias en las liveness probes.
ConfigMaps y Secrets
La configuración y los secretos deben vivir fuera de la imagen del contenedor, gestionados como recursos de Kubernetes.
# ConfigMap for non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
LOG_LEVEL: "info"
CACHE_TTL: "300"
FEATURE_NEW_UI: "true"
---
# Secret for sensitive values (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
url: cG9zdGdyZXM6Ly91c2VyOnBhc3NAZGIuZXhhbXBsZS5jb206NTQzMi9teWRi
password: c3VwZXJzZWNyZXQ=# Reference in Deployment
spec:
containers:
- name: api
image: myregistry/api-server:1.4.2
envFrom:
- configMapRef:
name: api-config
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: urlLos ConfigMaps permiten actualizar la configuración sin reconstruir las imágenes del contenedor. Modificar un ConfigMap y reiniciar los Pods aplica un cambio de configuración en cuestión de segundos.
Puntos clave
- Nunca ejecutes Pods sueltos: usa siempre Deployments para el reinicio automático y la gestión de despliegues
- Define los resource requests según el uso observado:
kubectl topda cifras reales, no estimaciones - Separa las readiness probes de las liveness probes: endpoints distintos, umbrales distintos, propósitos distintos
- Usa actualizaciones progresivas con maxUnavailable: 0: mantén la capacidad completa durante los despliegues
- Empieza con services de tipo ClusterIP: expón externamente solo lo que realmente necesita acceso externo
- Guarda los secretos en Secrets y la configuración en ConfigMaps: nunca incrustes valores específicos de cada entorno en las imágenes


