CKA · Módulo 6 K8s v1.34 Services & Networking 20%

Services: la IP estable frente a pods efímeros

Los pods nacen, mueren y cambian de IP. El Service es la dirección fija que no cambia: un nombre DNS y una IP virtual que enrutan a los pods vivos que matcheen un selector. Este módulo abre el dominio más grande después de Troubleshooting.

Duración70 min
DominioServices & Networking
Peso20% (1 de 2)
RequiereM4 (labels) · M5
Ya sabés crear pods (M4) y decidir dónde corren (M5). Falta el problema que eso deja abierto: ¿cómo le hablás a un pod? Su IP es efímera — se reinicia y cambia; escalás y hay tres IPs nuevas. Hardcodear IPs de pods no funciona nunca. El Service resuelve exactamente eso: una IP virtual estable + un nombre DNS + balanceo entre los pods que matcheen un selector de labels. Y como todo en Kubernetes, el Service no "contiene" pods: los selecciona. Entender esa indirección es entender el 80% del troubleshooting de red.
Modelo mental del módulo
Service = SELECTOR + PUERTOS → EndpointSlice
El selector define a quién apunta (labels de pods), los puertos definen cómo (port → targetPort). El resultado es una lista de IP:puerto de pods Ready: el EndpointSlice. Si esa lista está vacía, el Service no sirve a nadie — y ese es el primer lugar donde mirás cuando algo de red no funciona.
6.1
El Service y el modelo de red
IP virtual, selector, endpoints, port vs targetPort.
6.2
Los tipos de Service
ClusterIP, NodePort, LoadBalancer, ExternalName, headless.
6.3
Crear services rápido
expose, create service, edición de puertos.
6.4
CoreDNS: descubrimiento
FQDN, resolución cross-namespace, DNS de StatefulSet.
6.5
kube-proxy y EndpointSlices
Cómo se implementa realmente. Debug del camino.
6.1
El primitivo · 15 min

El Service y el modelo de red

Una IP que no cambia, delante de pods que sí cambian.
Práctica · Crear y verificar

El problema, en vivo

# Las IPs de los pods son efímeras: mirá y volvé a mirar
k create deploy web --image=nginx --replicas=3
k get pods -o wide          # anotá una IP
k delete pod -l app=web
k get pods -o wide          # IPs nuevas, otro pod

El Service, imperativo

# expose: crea el Service tomando el selector del deploy
k expose deploy web --port=80 --target-port=80

k get svc web
# NAME  TYPE       CLUSTER-IP     PORT(S)
# web   ClusterIP  10.96.140.22   80/TCP

La verificación que importa: ¿tiene endpoints?

# La lista real de pods detrás del Service
k get endpointslices -l kubernetes.io/service-name=web
k describe svc web       # campo Endpoints

# Probarlo desde dentro del cluster
k run tmp --rm -it --image=nginx -- curl -s web:80

El YAML, para leerlo bien

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:              # A QUIÉN apunta (labels de POD)
    app: web
  ports:
  - port: 80             # puerto DEL SERVICE
    targetPort: 8080     # puerto DEL CONTAINER
    protocol: TCP
El selector matchea labels de pods, no del Deployment. Si le pasás el label del Deployment y los pods tienen otro, el Service queda sin endpoints y todo falla en silencio: la IP responde, pero nadie contesta.
Teoría · La indirección

El modelo de red de Kubernetes

Tres reglas que el CNI (Calico, en tu cluster) garantiza:

  • Cada pod tiene su propia IP, única en el cluster.
  • Todo pod puede hablar con todo pod sin NAT, esté donde esté.
  • Los nodos pueden hablar con los pods sin NAT.

Es una red plana. Por eso no hay "port mapping" tipo Docker: cada pod tiene sus puertos propios.

Entonces, ¿para qué el Service?

La red plana resuelve conectividad, no descubrimiento. El Service agrega tres cosas que la red no da: una IP estable, un nombre DNS y balanceo entre réplicas.

Los tres puertos (fuente eterna de confusión)

CampoEs el puerto de…
portel Service (por donde le hablás)
targetPortel container (a donde reenvía)
nodePortel nodo (solo tipo NodePort/LB)

Si omitís targetPort, asume el mismo valor que port. Fuente clásica de "conecta pero da error".

Solo pods Ready entran

El endpoint incluye únicamente pods que pasan su readinessProbe. Es la conexión directa con el M4: una probe mal configurada saca al pod del balanceo aunque esté Running.

Mantra de troubleshooting de red: Service sin endpoints = selector que no matchea, o pods no Ready. Son el 90% de los casos. Siempre empezás por k describe svc.
6.2
Exposición · 20 min

Los tipos de Service

Misma mecánica, distinto alcance: interno, por nodo, o externo.
Práctica · Cada tipo

ClusterIP (el default) — solo interno

k expose deploy web --port=80
# Accesible solo desde dentro: web, web.default.svc.cluster.local

NodePort — puerto en TODOS los nodos

k expose deploy web --port=80 --type=NodePort
k get svc web
# PORT(S): 80:31234/TCP   <- 31234 es el nodePort

# Desde tu máquina, contra la IP de CUALQUIER nodo
k get nodes -o wide
curl http://<IP-DE-CUALQUIER-NODO>:31234

Rango por defecto: 30000-32767. Podés fijarlo con nodePort: 30080 en el YAML.

LoadBalancer — IP externa del proveedor

k expose deploy web --port=80 --type=LoadBalancer
k get svc web
# EXTERNAL-IP: <pending>   <- en kubeadm bare-metal queda así PARA SIEMPRE

ExternalName — un CNAME, sin proxy

spec:
  type: ExternalName
  externalName: db.ejemplo.com
# Sin selector, sin ClusterIP: DNS devuelve un CNAME y listo

Headless — sin IP virtual, DNS directo a pods

spec:
  clusterIP: None       # esto lo vuelve headless
  selector:
    app: db

# nslookup devuelve las IPs de los pods, no una VIP
En el examen, "exponer para acceso externo" con un cluster kubeadm significa NodePort o Ingress (M7) — nunca LoadBalancer, que se queda en <pending> sin un controller como MetalLB. Leé bien qué te piden.
Teoría · Alcance y anidamiento

Los tipos, en una tabla

TipoAlcanceUso típico
ClusterIPInternoComunicación entre servicios
NodePortIP nodo + puertoAcceso externo básico / labs
LoadBalancerIP públicaProducción en la nube
ExternalNameSolo DNSApuntar a algo de afuera

Se anidan (esto ordena todo)

NodePort incluye ClusterIP. LoadBalancer incluye NodePort, que incluye ClusterIP. No son alternativas excluyentes: son capas. Un Service NodePort sigue teniendo su ClusterIP y su nombre DNS interno funcionando igual.

Headless: para qué existe

Con clusterIP: None no hay VIP ni balanceo: el DNS devuelve todas las IPs de los pods. Sirve cuando el cliente necesita elegir instancia — bases de datos con réplicas, y sobre todo StatefulSets, donde cada pod tiene identidad propia (M4).

externalTrafficPolicy

  • Cluster (default): el tráfico que entra por un nodo puede reenviarse a un pod en otro nodo. Balancea mejor, pierde la IP de origen.
  • Local: solo a pods del mismo nodo. Preserva la IP de origen, pero si ese nodo no tiene pods, se cae.
Un NodePort abre el puerto en todos los nodos, no solo en los que tienen pods. Pegale a cualquiera y kube-proxy enruta al pod correcto, esté donde esté.
6.3
Velocidad · 10 min

Crear services rápido

Dos caminos imperativos, y cuándo usar cada uno.
Práctica · Los dos caminos

Camino 1: expose (recurso existente)

# Toma el selector del recurso automáticamente
k expose deploy web --port=80 --target-port=8080
k expose pod mypod --port=80 --name=mypod-svc
k expose deploy web --port=80 --type=NodePort

# Generar el YAML sin crear (para editar el nodePort)
k expose deploy web --port=80 --type=NodePort $do > svc.yaml

Camino 2: create service (desde cero)

# Formato de puertos: --tcp=PORT:TARGETPORT
k create service clusterip mysvc --tcp=80:8080
k create service nodeport mysvc --tcp=80:8080 --node-port=30080
k create service externalname ext --external-name=db.ejemplo.com

El atajo que te ahorra un Service

# run + expose en un comando (crea pod Y ClusterIP)
k run web --image=nginx --port=80 --expose

Editar puertos después

k edit svc web                        # puertos y tipo: editables en vivo
k patch svc web -p '{"spec":{"type":"NodePort"}}'
create service arma su propio selector (app: mysvc) que probablemente no matchee tus pods. Si lo usás, verificá los endpoints o corregí el selector a mano. Con expose ese problema no existe.
Teoría · Cuál elegir

La regla

expose cuando el workload ya existe — hereda el selector correcto y es imposible equivocarse. create service solo cuando necesitás un Service sin workload detrás (o un ExternalName).

Multi-puerto: los nombres son obligatorios

Si un Service expone más de un puerto, cada entrada debe tener name. Con un solo puerto es opcional. La API rechaza el objeto si te lo olvidás.

targetPort puede ser un nombre

targetPort: http resuelve al containerPort llamado http en el pod. Más robusto: el Service no se rompe si cambia el número.

Recordá el $do del M0 (--dry-run=client -o yaml): para cualquier cosa que expose no soporte por flag (nodePort fijo, sessionAffinity, headless), generás el YAML, editás una línea y aplicás. Sigue siendo más rápido que escribirlo desde cero.
6.4
Descubrimiento · 15 min

CoreDNS: hablarle por nombre

La IP del Service tampoco se hardcodea. Se usa el nombre.
Práctica · Resolver nombres

CoreDNS vive en kube-system

k get pods -n kube-system -l k8s-app=kube-dns
k get svc -n kube-system kube-dns   # normalmente 10.96.0.10

# Su config es un ConfigMap (el Corefile)
k -n kube-system get cm coredns -o yaml

Probar resolución desde un pod

# busybox:1.28 — las versiones nuevas tienen nslookup roto
k run dns --rm -it --image=busybox:1.28 -- sh

# dentro del pod:
nslookup web                         # mismo namespace
nslookup web.default                 # cross-namespace
nslookup web.default.svc.cluster.local
cat /etc/resolv.conf                 # nameserver + search domains

Lo que ves en resolv.conf

nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

Por eso web solo funciona: los search domains completan el FQDN.

DNS de StatefulSet (headless)

# Cada pod tiene su nombre estable:
#   POD-N.SERVICIO.NAMESPACE.svc.cluster.local
nslookup db-0.db.default.svc.cluster.local
Si el DNS no resuelve: 1) ¿corren los pods de CoreDNS? 2) ¿el Service kube-dns tiene endpoints? 3) ¿el nameserver del pod apunta a esa IP? En tu cluster, CoreDNS quedó Pending hasta que instalaste el CNI — sin red de pods, no hay DNS.
Teoría · El esquema de nombres

El FQDN de un Service

ParteQué es
webnombre del Service
defaultnamespace
svctipo de recurso
cluster.localdominio del cluster

Completo: web.default.svc.cluster.local

La regla que hay que tener automatizada

  • Mismo namespace → alcanza web.
  • Otro namespaceweb.otrons como mínimo.
  • Un pod en dev pidiendo web busca web.dev..., NO web.default....

Qué se resuelve y qué no

CoreDNS crea registros A para todos los Services. Los pods también tienen registro (10-244-1-5.default.pod.cluster.local, con guiones), pero casi nunca se usa: a los pods se les habla vía Service.

ndots:5, el detalle raro

Un nombre con menos de 5 puntos se prueba primero contra los search domains. Es la razón por la que los nombres cortos funcionan, y también de latencia extra al resolver dominios externos.

"El pod A no puede hablarle al pod B" en el examen casi siempre es: Service sin endpoints, nombre DNS mal escrito (falta el namespace) o una NetworkPolicy (M7). Verificás en ese orden.
6.5
Bajo el capó · 10 min

kube-proxy y EndpointSlices

La ClusterIP no existe en ninguna interfaz. Entender por qué.
Práctica · Seguir el camino

kube-proxy es un DaemonSet

k get ds -n kube-system kube-proxy
k logs -n kube-system -l k8s-app=kube-proxy --tail=20

# Su modo está en el ConfigMap
k -n kube-system get cm kube-proxy -o yaml | grep -i mode

EndpointSlices: la fuente de verdad

# Lo moderno
k get endpointslices
k get endpointslices -l kubernetes.io/service-name=web -o yaml

# Lo viejo (deprecado, pero todavía responde)
k get endpoints web

La secuencia de diagnóstico completa

# 1. ¿Existe el Service y qué selector tiene?
k get svc web -o wide

# 2. ¿Hay pods con ESE label?
k get pods -l app=web

# 3. ¿Están Ready? (solo los Ready entran)
k get pods -l app=web -o wide

# 4. ¿El Service los tomó?
k describe svc web    # Endpoints: vacío = ahí está el problema

# 5. Saltear el Service: pegarle al pod directo
k run t --rm -it --image=nginx -- curl -s <IP-DEL-POD>:8080
El paso 5 es el que divide el problema: si la IP del pod responde y la del Service no, el problema es Service/kube-proxy. Si tampoco responde el pod, es la app o el CNI. Bisecás en vez de adivinar.
Teoría · Cómo funciona de verdad

La ClusterIP es ficción

No hay ninguna interfaz de red con esa IP, ni un proceso escuchando ahí. Es una entrada en las reglas de packet filtering del kernel de cada nodo: cuando un paquete sale hacia esa IP, el kernel le reescribe el destino (DNAT) a la IP de uno de los pods del endpoint. Por eso no podés hacer ping a una ClusterIP.

El trabajo de kube-proxy

Corre en cada nodo (DaemonSet), observa Services y EndpointSlices en la API, y programa esas reglas. No procesa tráfico: lo configura y se sale del camino. Si muere, lo ya programado sigue funcionando; lo nuevo no se aplica.

Modos (v1.34)

ModoEstado
iptablesDefault histórico, el más común
nftablesGA desde 1.33, mejor a gran escala
ipvsMás algoritmos de balanceo

Algunos CNI (Calico, Cilium) pueden reemplazar kube-proxy con eBPF. Para el examen: asumí modo iptables salvo que te digan otra cosa.

Endpoints → EndpointSlices

El objeto Endpoints original guardaba TODOS los backends en un solo objeto: con miles de pods, cada cambio reescribía todo. EndpointSlice los parte en trozos (100 por defecto). La API Endpoints está deprecada desde v1.33 — sigue respondiendo por compatibilidad, pero lo canónico es EndpointSlice.

Inconsistencia para cazar: casi todo el material de CKA (y muchos cursos) todavía enseña k get endpoints. Funciona y no te lo van a penalizar, pero si el enunciado dice "EndpointSlice", el objeto que tenés que mirar es ese. Verificalo siempre en la doc oficial de la versión evaluada.
Cierre del módulo

Lo que te llevás del Módulo 6

Resumen · Comandos clave

Para tu agenda

# Crear (el 90% de las veces: expose)
k expose deploy web --port=80 --target-port=8080
k expose deploy web --port=80 --type=NodePort
k run web --image=nginx --port=80 --expose
k create service clusterip s --tcp=80:8080

# Verificar (SIEMPRE, después de crear)
k get svc -o wide            # muestra SELECTOR
k describe svc web            # muestra Endpoints
k get endpointslices -l kubernetes.io/service-name=web

# Probar desde dentro
k run tmp --rm -it --image=nginx -- curl -s web:80
k run dns --rm -it --image=busybox:1.28 -- nslookup web

# Puentes rápidos (debug, no producción)
k port-forward svc/web 8080:80
k port-forward pod/web-abc 8080:80

# DNS y proxy
k get pods -n kube-system -l k8s-app=kube-dns
k get ds -n kube-system kube-proxy

# FQDN: SERVICIO.NAMESPACE.svc.cluster.local
Teoría · Lo esencial

Las 5 ideas que no se negocian

  • El Service no contiene pods: los selecciona por labels. El resultado de esa selección es el EndpointSlice.
  • Sin endpoints = no sirve. Y eso es selector que no matchea, o pods no Ready. Es la primera cosa que verificás.
  • port es del Service, targetPort del container, nodePort del nodo. Tres cosas distintas.
  • Los tipos se anidan: LoadBalancer ⊃ NodePort ⊃ ClusterIP. En bare-metal, LoadBalancer no resuelve.
  • Se habla por nombre, no por IP. Cross-namespace exige el namespace en el FQDN.
El hilo del módulo: el Service es indirección por labels — el mismo patrón del Deployment sobre los pods (M4). Kubernetes casi nunca referencia por nombre o IP; referencia por selector. Interiorizá eso y la mitad del troubleshooting se vuelve mecánico.
Checkpoint Módulo 6: Services (los 4 tipos + headless), la relación selector→endpoints, CoreDNS y el FQDN, y el rol de kube-proxy. Mitad del dominio Services & Networking (20%). Siguiente: Módulo 7 — Ingress, Gateway API y NetworkPolicies.
Módulo 6 completado · Services & Networking, parte 1 de 2 · Siguiente: Ingress, Gateway API y NetworkPolicies