Exponer la aplicación con un Service
Ya sabes que los Pods de un Deployment aparecen y desaparecen constantemente: cada rollout, cada fallo, cada escalado los reemplaza por otros con IPs nuevas. Nadie puede construir nada serio sobre direcciones que cambian. La solución de Kubernetes es el Service: una IP virtual estable y un nombre DNS que reparten el tráfico entre los Pods que casan con un selector de labels.
El capítulo de red del libro recorre Service, Ingress, Gateway API, NetworkPolicy y CoreDNS. Este módulo los construye en ese mismo orden, empezando por la pieza sobre la que se apoyan todas las demás.
Trabaja desde la pestaña dev-machine. La pestaña Explorer te enseñará visualmente cómo el Service se conecta a sus Pods.
Paso 1: El backend
Primero necesitas algo que exponer. A estas alturas ya sabes hacerlo solo: crea un Deployment llamado web con 2 réplicas de la imagen ghcr.io/iximiuz/labs/nginx:alpine, cuyos Pods lleven la etiqueta app: web y expongan el puerto 80. Puedes partir del deployment.yaml de la lección anterior.
Apunta las IPs de los Pods con kubectl get pods -o wide -l app=web. Enseguida verás por qué no vas a necesitarlas nunca.
Paso 2: El Service ClusterIP
Crea el archivo service.yaml:
cat << 'EOF' > service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80
EOF
El YAML, explicado con preguntas y respuestas
¿Qué hace exactamente selector aquí?
Elige dinámicamente los Pods destino: todos los que tengan la etiqueta app: web, ahora y en el futuro. Aquí cierra el círculo la etiqueta que llevas poniendo desde la primera lección: el Service no conoce Deployments ni ReplicaSets, solo labels de Pods.
¿Qué diferencia hay entre port y targetPort?
port es el puerto en el que escucha el Service (su IP virtual), y targetPort el puerto del contenedor al que reenvía. Aquí coinciden, pero es habitual exponer el 80 del Service hacia un 8080 del contenedor.
¿Qué significa type: ClusterIP?
Es el tipo por defecto: el Service recibe una IP virtual alcanzable solo desde dentro del clúster. Es la opción correcta para comunicación entre servicios internos.
¿Dónde están los Pods en este YAML?
En ningún sitio, y esa es la gracia. La lista de IPs reales vive en objetos aparte llamados EndpointSlice, que Kubernetes mantiene automáticamente a partir del selector. Míralos con kubectl get endpointslice -l kubernetes.io/service-name=web: reconocerás las IPs de tus 2 Pods, cada una con sus condiciones ready, serving y terminating. (Hasta hace poco esto vivía en un único objeto Endpoints, hoy deprecado; te lo cruzarás en clústeres antiguos y en medio Stack Overflow.)
Aplícalo y comprueba la maquinaria:
kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslice -l kubernetes.io/service-name=web
Paso 3: DNS interno y la prueba del cliente
La IP del ClusterIP es estable, pero lo que se usa en la práctica es su nombre DNS. Cada Service recibe automáticamente el nombre <service>.<namespace>.svc.cluster.local, y dentro del mismo namespace basta con el nombre corto.
Compruébalo desde donde importa: dentro de otro Pod. Crea un cliente de larga duración:
kubectl run cliente --image=ghcr.io/iximiuz/labs/nginx:alpine --command -- sleep 100000
Y desde él, primero resuelve el nombre y luego haz la petición HTTP:
kubectl exec cliente -- nslookup web.tienda.svc.cluster.local
kubectl exec cliente -- wget -qO- http://web
La respuesta es la página de bienvenida de nginx, servida por uno de los 2 Pods. Repite el wget varias veces: el Service reparte las peticiones entre ambos.
Y ahora, el experimento que da sentido a toda la lección: borra uno de los Pods del backend (kubectl delete pod <nombre>) y repite el wget desde el cliente. Sigue funcionando. El ReplicaSet creó un Pod nuevo con otra IP, el EndpointSlice se actualizó solo, y ni el cliente ni tú tuvisteis que enteraros.
Paso 4: Abrirse al exterior con NodePort
El ClusterIP no existe fuera del clúster. El tipo NodePort abre además un puerto (entre 30000 y 32767) en todos los nodos. Crea service-np.yaml:
cat << 'EOF' > service-np.yaml
apiVersion: v1
kind: Service
metadata:
name: web-np
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080
EOF
El YAML, en una pregunta
¿Qué añade nodePort a lo que ya había?
Un tercer puerto en la cadena: nodePort (30080, abierto en la IP de cada nodo) reenvía a port (80, el Service) que reenvía a targetPort (80, el contenedor). Si no lo declaras, Kubernetes elige uno del rango al azar; fijarlo hace el laboratorio predecible.
Aplícalo y pruébalo desde fuera del clúster, es decir, desde la propia dev-machine:
kubectl apply -f service-np.yaml
curl http://cplane-01:30080
Y ahora la comprobación que de verdad cierra la lección: abre la pestaña
Aplicacion. Es ese mismo puerto 30080 de cplane-01,
solo que visto desde un navegador de verdad. Hasta hace un momento esa pestaña daba
error, porque no había nada escuchando ahí; el web-np que acabas de aplicar es lo
que la ha encendido.
Resumen
- Un Service da IP virtual y nombre DNS estables a un conjunto cambiante de Pods, elegidos por selector de labels.
- Los EndpointSlice son la lista viva de IPs reales, con el estado de cada una; Kubernetes la mantiene sola.
- ClusterIP es para tráfico interno; NodePort abre el mismo Service en un puerto de todos los nodos.
- El DNS interno (
<service>.<namespace>.svc.cluster.local) es la forma canónica de que un Pod encuentre a otro servicio.
El NodePort te ha abierto la puerta, pero exponer un puerto raro por cada aplicación no escala. La siguiente lección presenta la solución HTTP de verdad: el Ingress.
- Previous lesson
- Vertical Pod Autoscaler
- Next lesson
- Ingress