Cerrábamos el módulo 3 señalando que la red de Rutas Norte es completamente plana: cualquier pod de rutas-norte-pro puede abrir una conexión a postgres-reservas:5432, y ningún cliente de internet puede llegar todavía a www.rutasnorte.example. Antes de poner puertas (las políticas de red de 04-06) y ventanas (el Ingress de 04-04), hay que entender cómo está construida la casa. Esta lección es la que explica qué hay realmente debajo de esa 10.244.x.x que ves en kubectl get pods -o wide, quién crea esa interfaz de red, por qué la IP de un Service no aparece en ninguna tarjeta de red de ninguna máquina, y cómo un paquete que sale de api-reservas acaba entrando en postgres-reservas. Es la lección más "de fontanería" del curso, y también la que hace que todo lo demás deje de parecer magia.

Contenido

  1. El modelo de red de Kubernetes y sus cuatro reglas
  2. Por qué ese modelo simplifica todo lo demás
  3. Los cuatro planos de comunicación
  4. CNI: la interfaz que conecta el pod a la red
  5. Los plugins: Flannel, Calico, Cilium y Weave Net
  6. Superposición (VXLAN), enrutamiento nativo (BGP) y eBPF
  7. Los rangos de direcciones del clúster
  8. Cómo se implementa de verdad un ClusterIP: kube-proxy
  9. Recorrido completo de un paquete en Rutas Norte
  10. Comprobación práctica en minikube

  1. El modelo de red de Kubernetes y sus cuatro reglas

Kubernetes no implementa la red. Lo que hace es imponer un contrato: define un modelo que cualquier implementación de red debe cumplir, y deja que otros lo cumplan como quieran. El contrato tiene cuatro reglas.

# Regla Qué significa en la práctica
1 Cada pod tiene su propia dirección IP No se comparte la IP del nodo ni se hace mapeo de puertos. api-reservas escucha en el 3000 dentro de su propia IP, aunque haya cinco réplicas en el mismo nodo
2 Todo pod puede hablar con todo pod sin NAT Da igual si están en el mismo nodo o en nodos distintos: la conexión es directa, sin traducción de direcciones
3 Los agentes del nodo alcanzan a los pods de ese nodo El kubelet, y por tanto las sondas de salud de 07-01, pueden conectar con los pods sin trucos
4 La IP que el pod ve de sí mismo es la que ven los demás Si dentro del contenedor hostname -i dice 10.244.1.37, los demás pods lo ven exactamente como 10.244.1.37

La cuarta regla parece redundante, pero es la que prohíbe explícitamente el modelo de Docker clásico. En un docker run -p 8080:80, el contenedor cree que es 172.17.0.4:80 mientras el resto del mundo lo ve como 192.168.1.10:8080. Esa discrepancia rompe cualquier protocolo que anuncie su propia dirección: bases de datos en clúster, sistemas de descubrimiento, registros de servicio. Kubernetes la elimina de raíz.

Fíjate en lo que no dice el contrato: nada sobre cómo se enrutan los paquetes, si hay encapsulación, qué rango de IPs se usa o si hay cortafuegos. Todo eso es libertad de implementación.

  1. Por qué ese modelo simplifica todo lo demás

El modelo se resume en una frase: "IP por pod, red plana". Sus consecuencias son enormes.

  • Los puertos dejan de ser un recurso escaso. En un mundo de mapeo de puertos, desplegar tres réplicas de api-reservas en el mismo nodo obliga a asignarles 3000, 3001 y 3002, y a que alguien lleve la cuenta. Con IP por pod, las tres escuchan en el 3000. El manifiesto no cambia según dónde caiga el pod.
  • Las aplicaciones no necesitan saber que están en Kubernetes. Una aplicación que funciona en una máquina virtual funciona en un pod: abre su puerto y conecta a host:puerto. Esto es lo que permitió migrar tanto software sin reescribirlo.
  • El descubrimiento se vuelve trivial. Como no hay NAT, la IP que el EndpointSlice de 02-05 apunta es directamente conectable. Sin esa garantía, cada Service tendría que traducir direcciones y puertos por nodo.
  • La migración de pods es transparente. Un pod muere y otro nace con otra IP, pero la mecánica de conexión es idéntica.

El precio es que por defecto no hay ningún aislamiento. La regla 2 dice "todo pod puede hablar con todo pod", y eso es exactamente lo que significa: el pod más insignificante de rutas-norte-pro puede intentar conectar con postgres-reservas. Las NetworkPolicies de 04-06 existen precisamente para recortar ese permiso universal.

  1. Los cuatro planos de comunicación

Cuando alguien dice "la red de Kubernetes" suele estar mezclando cuatro problemas distintos, con soluciones distintas.

flowchart TB
    subgraph N1["Nodo 1"]
      subgraph P1["Pod api-reservas 10.244.1.5"]
        C1["contenedor api"]
        C2["sidecar de logs"]
      end
      P2["Pod redis-cache<br/>10.244.1.9"]
    end
    subgraph N2["Nodo 2"]
      P3["Pod postgres-reservas<br/>10.244.2.4"]
    end
    EXT["Cliente de internet"]
    SVC["Service ClusterIP<br/>10.96.31.72"]

    C1 -.->|"1. localhost"| C2
    P1 -->|"2. pod a pod, sin NAT"| P3
    P1 -->|"3. pod a Service"| SVC
    SVC --> P2
    EXT -->|"4. exterior a Service"| SVC
Plano Cómo se resuelve Dónde se estudia
1. Contenedor ↔ contenedor en el mismo pod Comparten el network namespace: se ven por localhost y compiten por los mismos puertos Ya visto en 02-01; patrones en 06-04
2. Pod ↔ pod Lo implementa el plugin CNI. Es el tema central de esta lección Aquí, apartados 4 a 7
3. Pod ↔ Service Lo implementa kube-proxy (o el CNI, si sustituye a kube-proxy) reescribiendo destinos Aquí, apartado 8
4. Exterior ↔ Service Tipos de Service NodePort/LoadBalancer e Ingress 04-02 y 04-04

Un error de diagnóstico muy común es atacar el plano equivocado. Si api-reservas no llega a postgres-reservas por IP de pod, el problema es del CNI. Si llega por IP de pod pero no por el nombre del Service, el problema es de kube-proxy o del DNS de 04-03. Separar los planos ahorra horas.

  1. CNI: la interfaz que conecta el pod a la red

CNI (Container Network Interface) es una especificación de la CNCF, muy corta y deliberadamente aburrida: define cómo un runtime de contenedores le pide a un programa externo que conecte o desconecte un contenedor de una red. No es un producto: es un contrato entre dos partes.

La cadena de invocación al crear un pod es esta:

sequenceDiagram
    participant K as kubelet
    participant CR as containerd
    participant CNI as Plugin CNI
    participant IPAM as IPAM
    K->>CR: crear sandbox del pod
    CR->>CR: crear network namespace vacio
    CR->>CNI: ADD (namespace, ID del pod)
    CNI->>IPAM: dame una IP del podCIDR del nodo
    IPAM-->>CNI: 10.244.1.37/24
    CNI->>CNI: crear veth, mover un extremo al ns
    CNI->>CNI: asignar IP, ruta por defecto
    CNI-->>CR: OK, IP 10.244.1.37
    CR-->>K: sandbox listo
    K->>CR: arrancar los contenedores del pod

Los puntos que conviene fijar:

  • Quien invoca al plugin es el kubelet a través del runtime (containerd, en nuestro clúster). El apiserver no toca la red.
  • El plugin se instala como un binario en /opt/cni/bin y se configura con un fichero JSON en /etc/cni/net.d. Por eso los plugins se despliegan casi siempre como un DaemonSet (06-02): un pod por nodo que copia el binario y el fichero de configuración al arrancar y luego se queda ejecutando su agente.
  • Las operaciones son ADD, DEL, CHECK y VERSION. Al borrar el pod se invoca DEL y la IP vuelve al pool.
  • IPAM (IP Address Management) es un subcomponente: decide qué IP concreta se asigna dentro del rango que le toca al nodo.

Lo que hace el plugin en un ADD típico, traducido a comandos que ya conoces de Linux:

  1. Crea un par veth (dos interfaces virtuales unidas como un cable).
  2. Deja un extremo en el network namespace del host (con nombre tipo veth3a7f2c1@if3) y mueve el otro dentro del namespace del pod, donde se llama eth0.
  3. Pide una IP al IPAM y se la asigna a eth0.
  4. Añade la ruta por defecto del pod, apuntando al puente o a la puerta de enlace del nodo.
  5. En el host, conecta su extremo al puente (cni0, docker0, cbr0) o crea la ruta correspondiente.
  6. Devuelve al runtime la IP asignada, que acaba en status.podIP del pod.

Si el CNI falla o no está instalado, verás el síntoma clásico:

NAME                            READY   STATUS                 RESTARTS   AGE
api-reservas-7d9f5c8b4-x2kp9    0/1     ContainerCreating      0          3m

Events:
  Warning  FailedCreatePodSandBox  kubelet  Failed to create pod sandbox:
  plugin type="bridge" failed (add): failed to set bridge addr: could not add IP

Pods eternamente en ContainerCreating con errores de sandbox casi siempre son problema de red del nodo, no de tu manifiesto.

  1. Los plugins: Flannel, Calico, Cilium y Weave Net

Elegir plugin es una de las decisiones más duraderas de un clúster: cambiarlo con carga en producción es una operación delicada. Estos son los cuatro históricamente más usados.

Flannel Calico Cilium Weave Net
Modelo de datos Superposición VXLAN (o host-gw) Enrutamiento nativo L3 con BGP; opción VXLAN/IPIP eBPF en el kernel; opcional VXLAN/geneve o nativo Superposición propia (VXLAN "fast datapath")
NetworkPolicy No la implementa Sí, y además políticas propias más ricas Sí, incluidas políticas L7 (HTTP, Kafka, DNS) Sí (soporte básico)
Rendimiento Bueno; penalización por encapsulación Muy bueno en modo nativo (sin encapsular) El mejor: puede sustituir a kube-proxy Aceptable; el menos rápido de los cuatro
Complejidad Mínima Media (BGP requiere entender la red física) Alta; requiere kernel moderno Baja
Cifrado en tránsito No WireGuard WireGuard o IPsec Sí, integrado
Observabilidad Ninguna Métricas Prometheus Hubble: flujos, mapas de servicio, L7 Básica
Cuándo elegirlo Aprender, laboratorio, clúster sin requisitos de aislamiento Producción con NetworkPolicy y red bajo tu control Producción exigente, observabilidad, políticas L7, malla sin sidecars Instalaciones sencillas donde ya está en uso

Consecuencia práctica para Rutas Norte, y hay que subrayarla: si el clúster usa Flannel, las NetworkPolicies que escribas en 04-06 se aplicarán sin error y no harán absolutamente nada. El objeto se crea, kubectl get networkpolicy lo lista, y el tráfico sigue pasando. Es el fallo silencioso más peligroso de esta parte del curso, y por eso lo verificaremos explícitamente.

Los clústeres gestionados de 10-06 traen su propio plugin (Amazon VPC CNI, Azure CNI, el de GKE), que suele asignar a los pods IPs de la red real de la nube. Eso hace que un pod sea directamente enrutable desde otras máquinas de la VPC, con la contrapartida de consumir direcciones del espacio corporativo.

  1. Superposición (VXLAN), enrutamiento nativo (BGP) y eBPF

El problema que resuelven todos es el mismo: el pod 10.244.1.5 está en el nodo A y quiere hablar con 10.244.2.4, que está en el nodo B. La red física entre nodos no sabe nada de 10.244.0.0/16: si le entregas ese paquete tal cual, lo descarta.

Red superpuesta (VXLAN)

Se construye una red virtual encima de la física. El nodo origen mete el paquete original entero dentro de un paquete UDP dirigido a la IP real del nodo destino (puerto 8472 en VXLAN), que lo desencapsula y lo entrega al pod.

[ IP nodoA -> IP nodoB | UDP 8472 | VXLAN | IP 10.244.1.5 -> 10.244.2.4 | TCP 5432 | datos ]
   \_______________ sobre exterior _______________/ \____________ paquete original ______/
  • Ventaja decisiva: funciona sobre cualquier red, sin tocar routers ni pedir nada al equipo de redes. Por eso es el modo por defecto de tanta instalación.
  • Coste: la cabecera añade unos 50 bytes, lo que reduce la MTU efectiva (de 1500 a 1450 típicamente) y provoca fragmentación si algo la ignora; hay trabajo extra de encapsular y desencapsular en cada paquete; y el tráfico es opaco para los cortafuegos y analizadores de la red física, que solo ven UDP entre nodos.

Enrutamiento nativo (BGP)

No se encapsula nada. Cada nodo anuncia por BGP a los routers de la red física: "el rango 10.244.2.0/24 se alcanza a través de mí". La red física aprende las rutas y entrega los paquetes de pod directamente.

  • Ventaja: sin sobrecoste, MTU íntegra, el tráfico de pods es visible y filtrable con las herramientas de red existentes.
  • Requisito: la red debe permitirlo. En un centro de datos propio con routers que hablen BGP, es lo ideal. En muchas nubes o en redes corporativas cerradas, no es posible y se cae de vuelta a encapsulación.

eBPF

eBPF permite cargar programas verificados dentro del kernel de Linux y engancharlos a puntos concretos de la pila de red. Cilium lo usa para tomar decisiones de enrutamiento, balanceo y política antes de que el paquete recorra toda la maquinaria de iptables.

  • Puede sustituir por completo a kube-proxy, eliminando las cadenas de iptables que veremos en el apartado 8.
  • Escala mucho mejor: donde iptables degrada con miles de Services, eBPF usa tablas hash de coste constante.
  • Habilita políticas con conocimiento de HTTP (por ruta y método) e identidad de carga de trabajo.
  • A cambio, exige un kernel razonablemente reciente y sube el listón de conocimiento para depurar.
Criterio VXLAN BGP nativo eBPF
Sobrecoste por paquete Alto (~50 B + encapsulado) Nulo Nulo o negativo
Requisitos de la red física Ninguno Debe enrutar/hablar BGP Ninguno
Visibilidad desde la red física Baja Alta Media
Escalado con muchos Services Depende de kube-proxy Depende de kube-proxy Excelente

  1. Los rangos de direcciones del clúster

En un clúster conviven tres espacios de direcciones que no hay que confundir nunca.

Rango Qué direcciona Ejemplo en minikube ¿Enrutable fuera?
Red de nodos Las máquinas físicas o virtuales 192.168.49.0/24 Sí, es la red real
podCIDR / cluster CIDR Las IPs de los pods 10.244.0.0/16, troceado en /24 por nodo Solo dentro del clúster
serviceCIDR Las IPs virtuales de los Services 10.96.0.0/12 No existe en ninguna interfaz

El cluster CIDR global se reparte: el kube-controller-manager, con --allocate-node-cidrs, asigna a cada nodo una porción (por defecto un /24, es decir 254 pods útiles por nodo) y la escribe en spec.podCIDR del objeto Node. El IPAM del plugin CNI reparte IPs dentro de esa porción. Así se garantiza que dos nodos nunca asignen la misma IP.

Dónde se configuran:

# Al crear el cluster con kubeadm (leccion 10-02)
kubeadm init --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/12

# En minikube, al crear el perfil
minikube start -p rutas-norte --extra-config=kubeadm.pod-network-cidr=10.244.0.0/16

Tres avisos que ahorran incidentes:

  1. No se pueden cambiar en caliente. Cambiar el serviceCIDR de un clúster vivo no es una operación soportada; se planifica antes de crearlo.
  2. No deben solaparse con la red corporativa. Si tu empresa usa 10.96.0.0/16 para servidores, los pods de Rutas Norte no podrán hablar con ellos: el nodo creerá que esas IPs son Services del clúster.
  3. El /24 por nodo es un límite real. Un nodo grande con 300 pods se queda sin direcciones aunque le sobre CPU y memoria.

  1. Cómo se implementa de verdad un ClusterIP: kube-proxy

Aquí está la revelación de la lección. Cuando en 02-05 creaste el Service postgres-reservas con clusterIP: 10.96.140.22, esa dirección no se asignó a ninguna interfaz de ninguna máquina. Es una ficción. No responde a ping. No hay ningún proceso escuchando en ella.

Lo que existe son reglas de reescritura de destino instaladas en cada nodo por kube-proxy, un DaemonSet del namespace kube-system que observa la API y, cada vez que cambia un Service o un EndpointSlice, actualiza esas reglas.

flowchart LR
    API["kube-apiserver<br/>Services + EndpointSlices"] -->|watch| KP["kube-proxy<br/>(un pod por nodo)"]
    KP -->|programa reglas| DP["iptables / IPVS<br/>del kernel del nodo"]
    POD["Pod origen"] -->|"conecta a 10.96.140.22:5432"| DP
    DP -->|"DNAT a 10.244.2.4:5432"| DEST["Pod postgres-reservas"]

Modo iptables (el más habitual)

Para cada Service, kube-proxy crea una cadena KUBE-SERVICES que casa por IP y puerto de destino, salta a una cadena KUBE-SVC-XXXX propia del Service, y de ahí reparte entre las cadenas KUBE-SEP-YYYY, una por endpoint, cada una con su regla DNAT.

# Cadena de entrada: captura el trafico hacia la IP del Service
-A KUBE-SERVICES -d 10.96.140.22/32 -p tcp --dport 5432
   -j KUBE-SVC-QW3RTY5432ABCD

# Reparto entre 2 endpoints con probabilidad estadistica
-A KUBE-SVC-QW3RTY5432ABCD -m statistic --mode random --probability 0.50000
   -j KUBE-SEP-AAA111
-A KUBE-SVC-QW3RTY5432ABCD -j KUBE-SEP-BBB222

# Cada endpoint: traduccion de destino a la IP real del pod
-A KUBE-SEP-AAA111 -p tcp -j DNAT --to-destination 10.244.2.4:5432
-A KUBE-SEP-BBB222 -p tcp -j DNAT --to-destination 10.244.3.7:5432

De aquí salen tres hechos importantes:

  • El balanceo es aleatorio por conexión, no por petición. Una conexión TCP larga (una sesión de PostgreSQL, un WebSocket) queda pegada a un pod hasta que se cierra. Por eso una API que multiplexa peticiones sobre pocas conexiones puede repartir mal la carga.
  • La probabilidad es en cascada: con 3 endpoints, las reglas llevan 1/3, luego 1/2, luego el resto. Así sale uniforme.
  • El coste es lineal: iptables evalúa reglas en orden. Con decenas de miles de Services, cada actualización obliga a reprogramar tablas enormes y aparecen latencias de sincronización. Es el motivo de que existan IPVS y eBPF.

Modo IPVS

IPVS es el balanceador de carga L4 del kernel de Linux, con tablas hash en lugar de listas de reglas.

iptables IPVS
Estructura Lista de reglas evaluada en orden Tabla hash, coste constante
Escalado Degrada con miles de Services Estable con decenas de miles
Algoritmos Solo aleatorio rr, lc, dh, sh, sed, nq
Depuración iptables-save ipvsadm -Ln
Requisitos Ninguno Módulos ip_vs* cargados

En modo IPVS sí aparece una interfaz kube-ipvs0 en el nodo con las IPs de los Services asignadas, pero es una interfaz dummy: sirve para que el kernel acepte los paquetes, no para responder.

La regla mental que hay que llevarse: el Service es una regla, no una máquina. Si un ClusterIP no responde, no busques un proceso caído: busca endpoints vacíos (selector que no casa, como viste en 02-05) o un kube-proxy que no está sincronizando.

  1. Recorrido completo de un paquete en Rutas Norte

Sigamos una conexión real: el pod api-reservas-7d9f5c8b4-x2kp9 (10.244.1.5, nodo 1) abre una conexión a postgres-reservas:5432 (ClusterIP 10.96.140.22), cuyo único endpoint es 10.244.2.4 en el nodo 2. El clúster usa VXLAN.

sequenceDiagram
    participant APP as api-reservas (10.244.1.5)
    participant DNS as CoreDNS
    participant NS1 as Kernel nodo 1
    participant NET as Red fisica
    participant NS2 as Kernel nodo 2
    participant PG as postgres-reservas (10.244.2.4)

    APP->>DNS: postgres-reservas.rutas-norte-pro.svc.cluster.local?
    DNS-->>APP: 10.96.140.22
    APP->>NS1: SYN a 10.96.140.22:5432 (sale por eth0/veth)
    NS1->>NS1: KUBE-SERVICES -> KUBE-SVC -> KUBE-SEP<br/>DNAT destino = 10.244.2.4:5432
    NS1->>NS1: ruta: 10.244.2.0/24 esta via VXLAN en nodo2
    NS1->>NET: encapsula UDP 8472 nodo1 -> nodo2
    NET->>NS2: entrega el paquete exterior
    NS2->>NS2: desencapsula: IP 10.244.1.5 -> 10.244.2.4
    NS2->>PG: SYN por el veth del pod
    PG-->>NS2: SYN-ACK a 10.244.1.5
    NS2->>NET: vuelta encapsulada
    NET->>NS1: entrega
    NS1->>NS1: conntrack deshace el DNAT:<br/>origen pasa a 10.96.140.22:5432
    NS1-->>APP: SYN-ACK aparentemente del ClusterIP

Los detalles que importan:

  • La resolución del nombre ocurre primero y es independiente de todo lo demás (04-03).
  • El DNAT ocurre en el nodo de origen, antes de enrutar. El nodo 2 nunca ve la IP del Service.
  • postgres-reservas ve como origen 10.244.1.5, la IP real del pod cliente, no la del nodo. Esto es la regla 2 (sin NAT de origen) y es lo que permite que las NetworkPolicies de 04-06 puedan identificar al emisor por sus etiquetas. Ojo: esto cambia cuando el tráfico entra desde fuera con externalTrafficPolicy: Cluster (04-02).
  • conntrack es imprescindible: el kernel recuerda la traducción para deshacerla en la respuesta. Una tabla conntrack llena provoca conexiones que se pierden aleatoriamente bajo carga, un síntoma clásico en los picos de puentes de Rutas Norte.

  1. Comprobación práctica en minikube

Todo lo anterior se puede tocar con las manos en el perfil rutas-norte.

Los rangos del clúster

# podCIDR asignado a cada nodo
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
rutas-norte     10.244.0.0/24
# El serviceCIDR no se expone como campo; se deduce provocando un error
kubectl create svc clusterip prueba-cidr --tcp=80:80 --dry-run=server \
  -o yaml --clusterip=1.2.3.4
The Service "prueba-cidr" is invalid: spec.clusterIPs[0]:
Invalid value: []string{"1.2.3.4"}: failed to allocate IP 1.2.3.4:
the provided IP (1.2.3.4) is not in the valid range. The range of valid IPs is 10.96.0.0/12

Es un truco muy útil: el mensaje de error te revela el rango exacto.

Las IPs reales de la plataforma

kubectl get pods -n rutas-norte-pro -o wide
kubectl get svc  -n rutas-norte-pro
NAME                             READY  STATUS   IP            NODE
api-reservas-7d9f5c8b4-x2kp9     1/1    Running  10.244.0.31   rutas-norte
postgres-reservas-5c9d7f-4nm2q   1/1    Running  10.244.0.14   rutas-norte

NAME                TYPE        CLUSTER-IP      PORT(S)
postgres-reservas   ClusterIP   10.96.140.22    5432/TCP

Las IPs de pod están en 10.244.0.x (el podCIDR del único nodo) y la del Service en 10.96.x.x. Rangos distintos, naturalezas distintas.

Las reglas que no existen y las que sí

# La IP del Service no esta en ninguna interfaz del nodo
minikube -p rutas-norte ssh -- ip -4 addr show | grep -E "inet "
    inet 127.0.0.1/8 scope host lo
    inet 192.168.49.2/24 brd 192.168.49.255 scope global eth0
    inet 10.244.0.1/24 brd 10.244.0.255 scope global bridge

Ni rastro de 10.96.140.22. Ahora las reglas:

minikube -p rutas-norte ssh -- \
  "sudo iptables-save -t nat | grep 10.96.140.22"
-A KUBE-SERVICES -d 10.96.140.22/32 -p tcp -m comment
   --comment "rutas-norte-pro/postgres-reservas cluster IP" -m tcp --dport 5432
   -j KUBE-SVC-6GHJ2KLM4NOPQRST

El comentario incluye <namespace>/<servicio>: es la forma más rápida de localizar las reglas de un Service concreto.

Un pod efímero con herramientas de red

La imagen nicolaka/netshoot trae dig, curl, nc, tcpdump, traceroute e ipvsadm. Es la navaja suiza para depurar redes en Kubernetes.

kubectl run netshoot --rm -it --restart=Never \
  -n rutas-norte-pro --image=nicolaka/netshoot -- bash

Dentro del pod:

# 1. Mi propia IP: debe estar en el podCIDR del nodo
ip -4 addr show eth0 | grep inet
# inet 10.244.0.42/24 scope global eth0

# 2. Conectividad pod a pod DIRECTA (plano 2, salta el Service)
nc -zv 10.244.0.14 5432
# Connection to 10.244.0.14 5432 port [tcp/postgresql] succeeded!

# 3. Conectividad pod a Service (plano 3)
nc -zv postgres-reservas 5432
# Connection to postgres-reservas 5432 port [tcp/postgresql] succeeded!

# 4. Lo que demuestra el aviso del modulo 3: un pod cualquiera,
#    sin ninguna relacion con la plataforma, llega a la base de datos
#    con los datos personales de los clientes. Eso lo arregla 04-06.

Si el paso 2 funciona y el 3 no, el CNI está bien y el problema es de Service o DNS. Si el 2 tampoco funciona, es el CNI o una política de red. Esa bifurcación es el 80 % del diagnóstico de red en Kubernetes.

Errores Comunes y Consejos

  • Confundir los tres rangos. Ver 10.96.x.x y buscar en qué nodo está ese pod es un clásico. Regla: 10.244.x.x es un pod (existe), 10.96.x.x es un Service (es una regla), 192.168.x.x es un nodo.
  • Hacer ping a un ClusterIP. No responde y eso es normal: las reglas de kube-proxy solo casan TCP/UDP en el puerto declarado, no ICMP. Un ping fallido a un Service no prueba nada. Usa nc -zv o curl.
  • Solapar el podCIDR o el serviceCIDR con la red corporativa. Provoca fallos incomprensibles al hablar con servicios externos, como la pasarela de pagos. Compruébalo antes de crear el clúster, no después.
  • Ignorar la MTU con VXLAN. Conexiones que abren bien pero se cuelgan al transferir bloques grandes (una restauración de PostgreSQL, una respuesta JSON de 2 MB) suelen ser MTU mal ajustada. Diagnóstico: ping -M do -s 1400 <ip>.
  • Elegir Flannel y escribir NetworkPolicies. Se aplican sin error y no filtran nada. Antes de confiar en una política, verifica que el CNI la implementa con una prueba real.
  • Culpar al CNI de todo. Antes de eso, comprueba los planos por orden: localhost dentro del pod, IP de pod, ClusterIP, nombre DNS. El primero que falle señala la capa culpable.
  • Consejo: guarda un alias para el pod de depuración. alias kshoot='kubectl run netshoot-$RANDOM --rm -it --restart=Never --image=nicolaka/netshoot -- bash'. Con --rm no dejas basura en el clúster.
  • Consejo: en producción, revisa las métricas de conntrack (nf_conntrack_count frente a nf_conntrack_max). En los picos de puentes de Rutas Norte, la tabla llena se manifiesta como errores de conexión intermitentes sin ningún pod caído.

Ejercicios

Ejercicio 1: Cartografiar la red del clúster

Sobre el perfil rutas-norte, documenta: el podCIDR del nodo, el serviceCIDR, la IP del nodo, y las IPs de los pods y Services de rutas-norte-pro. Clasifica cada dirección en su rango y explica cuáles existen físicamente y cuáles no.

Ejercicio 2: Demostrar que el ClusterIP es una ficción

Elige el Service redis-cache. Demuestra con tres comprobaciones que su IP no existe en ninguna interfaz, que sí existen reglas de iptables para ella, y que aun así se puede conectar. Explica quién hace la traducción y en qué nodo ocurre.

Ejercicio 3: Separar los planos ante una avería

Un compañero dice que api-reservas "no ve la base de datos". Diseña una secuencia de comprobaciones, de la capa más baja a la más alta, que permita decidir si el problema es del CNI, del Service, del DNS o de la propia aplicación. Ejecútala con netshoot y anota qué salida esperarías en cada caso de fallo.

Soluciones

Ejercicio 1

kubectl get nodes -o custom-columns=NODO:.metadata.name,\
POD_CIDR:.spec.podCIDR,IP:.status.addresses[0].address

kubectl create svc clusterip x --tcp=80:80 --clusterip=1.2.3.4 --dry-run=server 2>&1 | tail -1

kubectl get pods -n rutas-norte-pro -o custom-columns=POD:.metadata.name,IP:.status.podIP
kubectl get svc  -n rutas-norte-pro -o custom-columns=SVC:.metadata.name,CLUSTERIP:.spec.clusterIP

Clasificación esperada:

Dirección Rango ¿Existe?
192.168.49.2 Red de nodos Sí, es eth0 del nodo
10.244.0.31 podCIDR Sí, es eth0 dentro del pod
10.96.140.22 serviceCIDR No: solo reglas en el kernel
10.244.0.1 podCIDR Sí, es el puente del nodo (puerta de enlace de los pods)

Ejercicio 2

IP=$(kubectl get svc redis-cache -n rutas-norte-pro -o jsonpath='{.spec.clusterIP}')

# 1. No esta en ninguna interfaz
minikube -p rutas-norte ssh -- "ip -4 addr | grep $IP || echo 'NO APARECE: correcto'"

# 2. Si hay reglas
minikube -p rutas-norte ssh -- "sudo iptables-save -t nat | grep $IP"

# 3. Y sin embargo conecta
kubectl run t --rm -it --restart=Never -n rutas-norte-pro \
  --image=nicolaka/netshoot -- nc -zv redis-cache 6379

La traducción (DNAT) la hace el kernel del nodo donde corre el pod cliente, usando las reglas que kube-proxy programó a partir del EndpointSlice. El paquete sale del nodo ya con la IP real del pod destino.

Ejercicio 3

Secuencia de menor a mayor nivel, con la conclusión de cada fallo:

kubectl exec -it deploy/api-reservas -n rutas-norte-pro -- sh

# Plano 1: el proceso local escucha?
nc -zv localhost 3000            # falla -> la app no arranco: no es red

# Plano 2: IP de pod directa
PGIP=$(kubectl get pod -l app=postgres-reservas -n rutas-norte-pro \
        -o jsonpath='{.items[0].status.podIP}')
nc -zv $PGIP 5432                # falla -> CNI o NetworkPolicy

# Plano 3: ClusterIP
nc -zv 10.96.140.22 5432         # falla (y el anterior OK) -> kube-proxy o endpoints vacios
kubectl get endpointslices -n rutas-norte-pro -l kubernetes.io/service-name=postgres-reservas

# Plano DNS
nslookup postgres-reservas       # falla (y el anterior OK) -> CoreDNS o resolv.conf

# Plano aplicacion
psql -h postgres-reservas -U reservas -c 'select 1'   # falla -> credenciales o base de datos

Cada escalón que funciona descarta una capa entera. El primero que falla nombra al culpable.

Conclusión

Ya sabes qué hay debajo de la red de Kubernetes. El modelo se apoya en cuatro reglas obligatorias —IP por pod, comunicación pod a pod sin NAT, los agentes del nodo alcanzan a sus pods, y la IP que el pod ve de sí mismo es la que ven los demás— y es esa uniformidad la que permite que las aplicaciones no sepan que están en un clúster y que los puertos dejen de ser un recurso a administrar. Distingues los cuatro planos de comunicación y sabes cuál diagnosticar primero cuando algo falla.

Has visto que Kubernetes delega la implementación en un plugin CNI, que el kubelet invoca a través del runtime al crear cada pod, y que hace un trabajo muy concreto: crear un par veth, pedir una IP al IPAM dentro del podCIDR del nodo y programar las rutas. Conoces las diferencias reales entre Flannel, Calico, Cilium y Weave Net, y en particular que Flannel no implementa NetworkPolicy, un detalle que condicionará la lección 04-06. Entiendes el compromiso entre encapsular con VXLAN (funciona en cualquier sitio, cuesta 50 bytes y MTU) y enrutar de forma nativa con BGP (gratis, pero exige colaboración de la red física), y por qué eBPF está desplazando a ambos enfoques en los clústeres exigentes.

Y sobre todo, has desmontado la ficción más útil de Kubernetes: el ClusterIP no existe. No está en ninguna interfaz, no responde a ping y no hay ningún proceso detrás. Solo hay reglas de iptables o entradas de IPVS que kube-proxy mantiene sincronizadas con los EndpointSlices, haciendo DNAT en el nodo de origen y deshaciéndolo en la respuesta gracias a conntrack. Has seguido un paquete de api-reservas a postgres-reservas de principio a fin y lo has comprobado en tu propio minikube.

Con la fontanería entendida, toca subir un piso. En 04-02 veremos que ClusterIP es solo uno de los cuatro tipos de Service, que cada uno se construye sobre el anterior, y cómo NodePort, LoadBalancer y ExternalName empiezan a abrir la plataforma al exterior: el primer paso para que un cliente que quiere comprar un billete de autobús pueda, por fin, llegar a tienda-web.

Curso de Kubernetes

Módulo 1: Introducción a Kubernetes

Módulo 2: Componentes Principales de Kubernetes

Módulo 3: Gestión de Configuración y Secretos

Módulo 4: Redes en Kubernetes

Módulo 5: Almacenamiento en Kubernetes

Módulo 6: Conceptos Avanzados de Kubernetes

Módulo 7: Monitoreo y Registro

Módulo 8: Seguridad en Kubernetes

Módulo 9: Escalado y Rendimiento

Módulo 10: Ecosistema y Herramientas de Kubernetes

Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real

Módulo 12: Preparación para la Certificación de Kubernetes

© Copyright 2026. Todos los derechos reservados