Kubernetes básico: Pod, Deployment y Service
1. Descripción general
Kubernetes tiene decenas de tipos de recursos, pero el 80% del trabajo diario gira en torno a tres conceptos: Pod, Deployment, Service. Este artículo te ayudará a entender la naturaleza de cada recurso, cómo se interconectan, y a escribir un conjunto de manifiestos YAML que desplieguen una aplicación real y funcional en tu cluster.
Después de leerlo, podrás desplegar por ti mismo una aplicación Spring Boot en Kubernetes, escalarla a múltiples réplicas y exponerla al exterior a través de un Service — todo simplemente con kubectl y unos pocos archivos YAML.
Nota: Este artículo asume que ya tienes un cluster (Minikube, Kind, o en la nube) y que
kubectlapunta al contexto correcto.
2. Pod — La unidad de despliegue más pequeña
2.1. ¿Qué es un Pod?
Un Pod es un grupo de uno o más contenedores que comparten el namespace de red (misma IP, mismo espacio de puertos) y volúmenes. En la práctica, la mayoría de los Pods contienen un único contenedor de aplicación.
apiVersion: v1
kind: Pod
metadata:
name: order-service-pod
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
Explicación:
metadata.labelsasigna la etiquetaapp: order-service— así es como Kubernetes “identifica” el Pod; los Services y Deployments usan esta etiqueta para seleccionar objetos relacionados.resources.requestses el recurso mínimo que el scheduler necesita disponible para colocar el Pod en un nodo;limitses el techo máximo, excederlo resultará en throttling (CPU) o terminación (memoria — OOMKilled).containerPort: 8080es solo una declaración informativa, no abre automáticamente el puerto al exterior.
Aplicar y verificar:
kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME READY STATUS RESTARTS AGE
order-service-pod 1/1 Running 0 5s
2.2. Por qué no crear Pods directamente
Un Pod creado “crudamente” como el anterior tiene un problema importante: si el nodo que lo contiene falla, o el Pod se bloquea, nadie creará uno nuevo automáticamente. Los Pods no tienen mecanismo de auto-sanación ni escalado.
Nota: En entornos de producción, prácticamente nunca crearás Pods directamente. Siempre usa Deployment (o StatefulSet, Job) para gestionar los Pods.
3. Deployment — Gestionar el ciclo de vida de los Pods
3.1. Declarar un Deployment
Un Deployment crea y gestiona un conjunto de Pods a través de un ReplicaSet, asegurando que siempre hay un número exacto de réplicas ejecutándose, y soporta actualizaciones rolling sin tiempo de inactividad.
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
Explicación:
replicas: 3requiere que el Deployment mantenga siempre 3 Pods ejecutándose — si uno falla, el Deployment crea automáticamente uno nuevo como reemplazo.selector.matchLabelsdebe coincidir contemplate.metadata.labels. Este es el vínculo obligatorio entre el Deployment y los Pods que gestiona.readinessProbedetermina si un Pod está listo para recibir tráfico (usado por el Service);livenessProbedetermina si el Pod necesita reiniciarse. Confundir estos dos probes es un error muy común para principiantes en Kubernetes.
Aplicar y observar:
kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME READY UP-TO-DATE AVAILABLE AGE
order-service 3/3 3 3 12s
NAME READY STATUS RESTARTS AGE
order-service-7d4f9c8b6d-2kx9p 1/1 Running 0 12s
order-service-7d4f9c8b6d-7h2mn 1/1 Running 0 12s
order-service-7d4f9c8b6d-xq1lw 1/1 Running 0 12s
3.2. Escalar y actualización rolling
Escalar el número de réplicas hacia arriba o abajo sin necesidad de editar el YAML:
kubectl scale deployment order-service --replicas=5
Actualización rolling cuando cambias la imagen — el Deployment reemplaza automáticamente los Pods antiguos por otros nuevos en forma de onda, sin tiempo de inactividad:
kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out
Si la nueva versión tiene errores, revierte a la versión anterior instantáneamente:
kubectl rollout undo deployment/order-service
3.3. Configurar la estrategia de actualización rolling
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
maxUnavailable: 1 permite que como máximo 1 Pod se “pierda” durante la actualización; maxSurge: 1 permite crear como máximo 1 Pod adicional más allá del número de réplicas declaradas. Con replicas: 3, el cluster ejecutará entre 2 y 4 Pods durante la transición, pero nunca menos de 2.
4. Service — Exponer Pods de forma estable
4.1. Por qué necesitas un Service
Los Pods tienen una IP propia, pero esta IP cambia cada vez que el Pod se recrea (crash, actualización rolling, escalado). Un Service proporciona una dirección estable (ClusterIP + nombre DNS) frente a un grupo de Pods, distribuye automáticamente el tráfico según el selector de etiquetas — sin preocuparse por cuántas veces cambien los Pods subyacentes.
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Explicación:
selector.app: order-servicecoincide con la etiqueta del Pod (que el Deployment asignó entemplate.metadata.labels) — el Service busca y monitorea automáticamente estos Pods a través de un objeto intermedio llamado Endpoints.port: 80es el puerto en el que el Service escucha dentro del cluster;targetPort: 8080es el puerto que el contenedor realmente expone. Estos dos números no tienen que ser iguales.type: ClusterIP(predeterminado) solo es accesible desde dentro del cluster — adecuado para servicios internos entre microservicios.
Verificar el Service y los Endpoints:
kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
order-service ClusterIP 10.96.142.201 <none> 80/TCP 4s
NAME ENDPOINTS AGE
order-service 10.244.1.5:8080,10.244.1.6:8080,+1 more 4s
4.2. Tipos de Service
| Tipo | Alcance de acceso | Cuándo usarlo |
|---|---|---|
ClusterIP |
Solo dentro del cluster | Comunicación entre microservicios internos |
NodePort |
Puerto fijo (30000-32767) en todos los nodos | Pruebas rápidas, entorno dev/on-prem |
LoadBalancer |
IP pública a través del proveedor de nube | Exponer servicio a Internet en la nube |
Ejemplo para exponer al exterior para pruebas rápidas en Minikube:
apiVersion: v1
kind: Service
metadata:
name: order-service-external
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
nodePort: 30080
type: NodePort
minikube service order-service-external --url
Nota: Usar
NodePorten producción es un anti-patrón — el rango de puertos es limitado, no hay terminación TLS ni enrutamiento basado en salud como con Ingress. En producción en la nube, priorizaLoadBalancero un Ingress Controller.
5. Combinar los tres en una aplicación real
El flujo completo: el cliente llama al Service → el Service distribuye el tráfico entre los Pods que gestiona el Deployment.
Cliente
│
▼
Service (order-service, ClusterIP 10.96.142.201:80)
│ (selector de etiquetas: app=order-service)
▼
┌─────────────┬─────────────┬─────────────┐
│ Pod #1 │ Pod #2 │ Pod #3 │
│ :8080 │ :8080 │ :8080 │
└─────────────┴─────────────┴─────────────┘
▲ gestionado por
Deployment (order-service, replicas: 3)
Para facilitar el despliegue en una sola operación, puedes combinar el Deployment y el Service en un mismo archivo, separándolos con ---:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
kubectl apply -f order-service-all.yaml
6. Escribir tests para el endpoint de readiness antes de desplegar
Antes de que el probe de Kubernetes se base en /actuator/health/readiness, debes asegurarte de que este endpoint devuelve el estado correcto en tu código Spring Boot.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void readinessProbe_shouldReturnUp() {
ResponseEntity<String> response =
restTemplate.getForEntity("/actuator/health/readiness", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("\"status\":\"UP\"");
}
}
Este test llama a TestRestTemplate en el endpoint de readiness real, verificando que devuelve HTTP 200 y que el body contiene UP. Este es exactamente el endpoint que el readinessProbe en el Deployment llamará periódicamente — si el test falla aquí, el Pod tampoco estará nunca “Ready” en el cluster.
7. Errores comunes
- Selector no coincide con la etiqueta: El Service o Deployment no encuentra ningún Pod (Endpoints vacío) porque
selectorylabelsestán escritos incorrectamente o son distintos. Siempre ejecutakubectl get endpoints <svc-name>para confirmar. - Falta readinessProbe: El Service sigue enrutando tráfico a Pods que aún no han iniciado la aplicación completamente, causando errores 502/503 justo después del despliegue.
- Requests/limits de recursos faltantes: El Pod podría ser colocado en el nodo incorrecto por el scheduler, o ser terminado inesperadamente (OOMKilled) cuando el tráfico aumenta.
- Usar Pods crudos en producción: Sin auto-sanación, perder un Pod es perderlo completamente — tendrías que recrearlo manualmente.
- Tag de imagen
latest: La actualización rolling no detecta cambios, Kubernetes cree que la imagen no ha cambiado y no la descarga de nuevo. Siempre usa una etiqueta de versión específica.
8. Conclusión
Un Pod es la unidad más pequeña para ejecutar un contenedor, un Deployment gestiona el ciclo de vida y el escalado de un conjunto de Pods, y un Service proporciona una dirección estable para acceder a ellos aunque los Pods subyacentes cambien constantemente. Dominar estos tres pilares es la base para avanzar después hacia Ingress, ConfigMap/Secret, o charts de Helm. El siguiente paso debería ser intentar desplegar una aplicación Spring Boot real, observar cómo funciona la actualización rolling, y probar a eliminar un Pod manualmente para ver cómo el Deployment se recupera automáticamente.