La lección anterior dejó un cabo suelto deliberado: ExternalName funciona porque CoreDNS devuelve un CNAME, los servicios headless funcionan porque CoreDNS publica una entrada A por pod, y api-reservas encuentra a postgres-reservas porque alguien resuelve ese nombre. Llevamos tres módulos escribiendo postgres-reservas:5432 en la configuración sin haber explicado nunca quién traduce eso a una dirección IP. Esta lección abre la caja: qué es CoreDNS, cómo se configura, qué nombres genera para cada tipo de objeto, qué hay realmente en el /etc/resolv.conf de un pod, por qué el parámetro ndots: 5 puede multiplicar por cinco las consultas DNS cuando api-reservas llama a la pasarela de pagos, y cómo diagnosticar los tres fallos de DNS que te vas a encontrar una y otra vez.

Contenido

  1. Por qué el descubrimiento se hace por DNS
  2. CoreDNS: qué es y dónde vive
  3. El Corefile: la configuración del DNS del clúster
  4. El esquema de nombres y las formas cortas
  5. Registros de Services, Services headless y pods
  6. Registros SRV y puertos con nombre
  7. El /etc/resolv.conf del pod y el efecto de ndots: 5
  8. dnsPolicy y dnsConfig
  9. Las variables de entorno heredadas de Docker
  10. Diagnóstico: los tres fallos típicos
  11. Escala: NodeLocal DNSCache y réplicas de CoreDNS

  1. Por qué el descubrimiento se hace por DNS

La alternativa sería configurar direcciones IP, y no funciona: las IPs de los pods son efímeras (02-05). Un rollout, un desalojo o un reinicio del nodo la cambian. Podríamos fijar la IP del Service, que sí es estable mientras el Service exista, pero eso tampoco resiste:

Problema de configurar el ClusterIP Consecuencia
Se asigna al crear el Service El manifiesto de api-reservas no puede escribirse hasta que exista el de postgres-reservas
Es distinta en cada entorno El ConfigMap de dev, pre y pro diverge sin necesidad
Se pierde al borrar y recrear el Service Una reconstrucción del namespace rompe la plataforma
No sobrevive a un clúster nuevo Recuperación ante desastres imposible sin editar configuración

Con DNS, api-reservas lleva escrito postgres-reservas en su ConfigMap desde 03-01, y ese nombre es válido en los tres entornos, en un clúster nuevo y tras recrear el Service: es la única referencia estable que existe en Kubernetes. Además, el DNS es universal: no requiere biblioteca, ni SDK, ni saber que estás en Kubernetes.

  1. CoreDNS: qué es y dónde vive

CoreDNS es un servidor DNS escrito en Go, modular a base de plugins, y es el DNS por defecto de Kubernetes desde la versión 1.13 (sustituyó a kube-dns). Corre como un Deployment normal en el namespace kube-system, expuesto por un Service llamado kube-dns (el nombre se conservó por compatibilidad).

kubectl get deploy,svc,pods -n kube-system -l k8s-app=kube-dns
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/coredns   1/1     1            1           21d

NAME               TYPE        CLUSTER-IP   PORT(S)                  AGE
service/kube-dns   ClusterIP   10.96.0.10   53/UDP,53/TCP,9153/TCP   21d

NAME                          READY   STATUS    RESTARTS   AGE
pod/coredns-668d6bf9bc-hn2fk  1/1     Running   0          3d

Tres observaciones que resuelven muchas dudas:

  • CoreDNS es una carga de trabajo como cualquier otra. Puede caerse, quedarse sin recursos, ser desalojada. Cuando CoreDNS sufre, toda la plataforma parece rota simultáneamente, y ese síntoma global es el que debe hacerte mirar aquí.
  • La IP 10.96.0.10 es la décima dirección del serviceCIDR por convención, y es la que el kubelet inyecta en cada pod.
  • El puerto 9153 expone métricas Prometheus (07-03): tasa de consultas, latencia, errores. Es de lo primero que conviene monitorizar.
flowchart LR
    POD["Pod api-reservas"] -->|"consulta a 10.96.0.10:53"| SVC["Service kube-dns"]
    SVC -->|"kube-proxy DNAT"| CD["Pod CoreDNS"]
    CD -->|"plugin kubernetes:<br/>watch de Services y EndpointSlices"| API["kube-apiserver"]
    CD -->|"plugin forward:<br/>nombres externos"| UP["Resolutor del nodo<br/>(/etc/resolv.conf del host)"]
    UP --> INT["Internet:<br/>pagos.proveedorexterno.example"]

Fíjate en la circularidad aparente: los pods llegan a CoreDNS a través de un ClusterIP, que necesita kube-proxy pero no necesita DNS. No hay problema del huevo y la gallina porque la IP 10.96.0.10 va inyectada literalmente en el resolv.conf.

  1. El Corefile: la configuración del DNS del clúster

CoreDNS se configura con un fichero llamado Corefile, que vive en un ConfigMap y se monta en el pod. Puedes leerlo tal cual:

kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}'
.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}

Lee esto como una cadena de plugins que se evalúan en orden para toda consulta que llegue al puerto 53.

Plugin Qué hace
errors Registra los errores en el log
health / ready Puntos para las sondas del Deployment; lameduck espera 5 s antes de morir para no perder consultas
kubernetes cluster.local El corazón. Observa Services y EndpointSlices en la API y responde los nombres del dominio cluster.local
pods insecure Habilita los registros de pods por IP (apartado 5)
ttl 30 Vida de las respuestas del clúster: 30 segundos
prometheus :9153 Expone métricas
forward . /etc/resolv.conf Todo lo que no sea cluster.local se reenvía al DNS que use el nodo. Así se resuelve pagos.proveedorexterno.example
cache 30 Caché de respuestas, 30 s
loop Detecta bucles de reenvío y aborta el arranque
reload Recarga el Corefile sin reiniciar el pod (tarda ~2 min en detectar el cambio)
loadbalance Baraja el orden de los registros A en cada respuesta

Un caso práctico de Rutas Norte: la oficina tiene un DNS interno en 10.20.30.5 que resuelve *.interno.rutasnorte.example, donde vive la base de datos de facturación heredada. Se añade un bloque específico con kubectl edit configmap coredns -n kube-system:

interno.rutasnorte.example:53 {
    errors
    cache 30
    forward . 10.20.30.5
}

El plugin reload lo recoge solo. Cuidado: es configuración de todo el clúster; un error de sintaxis deja a CoreDNS en CrashLoopBackOff y con él a toda la plataforma. Revisa el log tras cada cambio.

  1. El esquema de nombres y las formas cortas

El nombre canónico de un Service tiene siempre esta estructura:

<servicio>.<namespace>.svc.<dominio-del-cluster>
postgres-reservas.rutas-norte-pro.svc.cluster.local
       |                 |          |        |
       |                 |          |        +-- dominio del clúster (configurable)
       |                 |          +----------- tipo de objeto: svc
       |                 +---------------------- namespace
       +---------------------------------------- nombre del Service

Las formas cortas funcionan gracias a la lista search del resolv.conf (apartado 7). Desde un pod de rutas-norte-pro:

Escribes ¿Funciona? Qué resuelve
postgres-reservas El del mismo namespace
postgres-reservas.rutas-norte-pro Explícito; sirve entre namespaces
postgres-reservas.rutas-norte-pro.svc Explícito
postgres-reservas.rutas-norte-pro.svc.cluster.local Canónico y sin ambigüedad
postgres-reservas.rutas-norte-dev Otro namespace: los namespaces no aíslan la red (02-06)

Ese último caso merece énfasis. Un pod de rutas-norte-dev puede resolver y conectar a postgres-reservas.rutas-norte-pro. La separación por namespace es organizativa, no de seguridad. Solo las NetworkPolicies de 04-06 lo impiden de verdad.

Regla práctica para Rutas Norte:

  • Dentro del mismo entorno, usa el nombre corto: postgres-reservas, redis-cache. El mismo ConfigMap sirve para dev, pre y pro sin cambios.
  • Para cruzar namespaces, usa siempre la forma completa con .svc.cluster.local. Es explícita y, como veremos, más rápida.

  1. Registros de Services, Services headless y pods

Service normal: una A con el ClusterIP

kubectl run t --rm -it --restart=Never -n rutas-norte-pro \
  --image=nicolaka/netshoot -- dig +short postgres-reservas.rutas-norte-pro.svc.cluster.local
10.96.140.22

Una sola respuesta: la IP virtual. El balanceo entre pods lo hace kube-proxy después, como viste en 04-01. El DNS no interviene en el reparto.

Service headless: una A por pod

$ dig +short postgres-reservas-headless.rutas-norte-pro.svc.cluster.local
10.244.0.14
10.244.0.18
10.244.0.23

Tres registros A, uno por pod listo (ready: true en el EndpointSlice). Aquí el DNS sí participa en el descubrimiento, y quien elige es el cliente. El plugin loadbalance baraja el orden en cada respuesta para que los clientes que solo miran el primer registro repartan algo.

Un pod que no está listo desaparece de esta lista, salvo que el Service tenga publishNotReadyAddresses: true (necesario para que las réplicas de una base de datos se descubran entre sí durante el arranque, algo que veremos en 06-01).

Registros de pod

Con pods insecure en el Corefile, todo pod tiene un nombre derivado de su IP con los puntos sustituidos por guiones: 10-244-0-14.rutas-norte-pro.pod.cluster.local resuelve a 10.244.0.14. Fíjate en pod en vez de svc. Es de uso muy limitado: nadie escribe eso a mano. Su importancia real llega con los StatefulSets, donde cada pod obtiene un nombre estable y legible dentro de un Service headless:

postgres-reservas-0.postgres-reservas-headless.rutas-norte-pro.svc.cluster.local
postgres-reservas-1.postgres-reservas-headless.rutas-norte-pro.svc.cluster.local

Esa es la identidad de red que hace posible una base de datos en clúster, y llega en 06-01.

Objeto Formato Devuelve
Service normal <svc>.<ns>.svc.cluster.local 1 A: el ClusterIP
Service headless <svc>.<ns>.svc.cluster.local N A: una por pod listo
Pod en headless <pod>.<svc>.<ns>.svc.cluster.local 1 A: la IP del pod
Pod por IP <ip-con-guiones>.<ns>.pod.cluster.local 1 A: esa misma IP
ExternalName <svc>.<ns>.svc.cluster.local CNAME al dominio externo

  1. Registros SRV y puertos con nombre

Los registros SRV publican, además de la dirección, el puerto. Se generan para todo puerto con nombre de un Service, con el formato _<puerto>._<protocolo>.<servicio>.<ns>.svc.cluster.local.

Recuperando el api-reservas multipuerto de 04-02, con puertos http (3000) y metrics (9090):

dig +short SRV _http._tcp.api-reservas.rutas-norte-pro.svc.cluster.local
0 100 3000 api-reservas.rutas-norte-pro.svc.cluster.local.

El formato es prioridad peso puerto destino. Un cliente que sepa leer SRV descubre el puerto sin tenerlo configurado. En un Service headless, un SRV devuelve una línea por pod, con el puerto de cada uno:

0 33 5432 10-244-0-14.postgres-reservas-headless.rutas-norte-pro.svc.cluster.local.
0 33 5432 10-244-0-18.postgres-reservas-headless.rutas-norte-pro.svc.cluster.local.
0 33 5432 10-244-0-23.postgres-reservas-headless.rutas-norte-pro.svc.cluster.local.

Pocas aplicaciones consultan SRV; la mayoría configura el puerto explícitamente. Pero es el mecanismo que usan varios operadores y bibliotecas de descubrimiento, y es la razón concreta por la que insistimos en nombrar siempre los puertos.

  1. El /etc/resolv.conf del pod y el efecto de ndots: 5

El kubelet escribe este fichero dentro de cada contenedor:

kubectl exec -n rutas-norte-pro deploy/api-reservas -- cat /etc/resolv.conf
nameserver 10.96.0.10
search rutas-norte-pro.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

Tres líneas, tres decisiones:

  • nameserver 10.96.0.10: el ClusterIP de kube-dns. Es lo que hace que las formas cortas se resuelvan.
  • search: los sufijos que el resolutor probará. El primero es el propio namespace: por eso postgres-reservas a secas funciona dentro de rutas-norte-pro.
  • options ndots:5: aquí está la trampa.

Qué significa ndots

ndots:5 le dice al resolutor: "si el nombre que te piden tiene menos de 5 puntos, pruébalo primero con cada sufijo de search, y solo al final tal cual".

Cuenta los puntos de pagos.proveedorexterno.example: dos. Menos de cinco. Así que el resolutor hace esto:

1. pagos.proveedorexterno.example.rutas-norte-pro.svc.cluster.local  -> NXDOMAIN
2. pagos.proveedorexterno.example.svc.cluster.local                  -> NXDOMAIN
3. pagos.proveedorexterno.example.cluster.local                      -> NXDOMAIN
4. pagos.proveedorexterno.example                                    -> 198.51.100.44  OK

Cuatro consultas, tres inútiles. Y peor: cada una se hace para A y para AAAA, así que en la práctica son 8 consultas cuando bastaba con 2. Las tres primeras además viajan a CoreDNS, que consulta la API y devuelve NXDOMAIN.

Ahora traslada esto a Rutas Norte en un puente de agosto: api-reservas llama a la pasarela de pagos en cada compra. Con 2.000 compras por minuto y sin caché de resolución en la aplicación, son 16.000 consultas por minuto donde deberían ser 4.000. CoreDNS se satura, la latencia de resolución sube, y todo el clúster empieza a ir despacio por una causa que nadie relaciona con el DNS.

La solución: el punto final

Un nombre terminado en punto es un FQDN absoluto: el resolutor no le añade sufijos.

# ConfigMap de api-reservas: fijate en el punto final
PASARELA_PAGOS_URL: "https://pagos.proveedorexterno.example./cobros"
1. pagos.proveedorexterno.example.  -> 198.51.100.44  OK

Una consulta. Fin.

Enfoque Consultas por resolución Cuándo usarlo
Nombre externo sin punto final Hasta 4 (x2 con AAAA) Nunca, si puedes evitarlo
Nombre externo con punto final 1 Siempre para dominios externos
Nombre interno corto (redis-cache) 1 (acierta al primer sufijo) Dentro del mismo namespace
Nombre interno completo con punto final 1 Entre namespaces, y en rutas críticas
dnsConfig con ndots: 2 1 para externos Cuando no puedes tocar las URLs

Aviso: si bajas ndots a 1, los nombres cortos como redis-cache (cero puntos) siguen funcionando, pero postgres-reservas.rutas-norte-pro (un punto) dejaría de resolverse por búsqueda y necesitaría el FQDN. Bajar a 2 es un compromiso habitual y seguro.

  1. dnsPolicy y dnsConfig

dnsPolicy es un campo del spec del pod que decide qué resolv.conf recibe.

Valor Qué hace Cuándo usarlo
ClusterFirst Por defecto: apunta a CoreDNS, que reenvía lo externo Prácticamente siempre
ClusterFirstWithHostNet Igual, pero para pods con hostNetwork: true Pods de red de host que necesiten DNS del clúster
Default Hereda el resolv.conf del nodo. No resuelve nombres del clúster Pods de infraestructura que no hablan con Services
None Ignora todo y usa exclusivamente el dnsConfig que escribas Configuraciones DNS muy específicas

El caso de ClusterFirstWithHostNet es una trampa clásica: un pod con hostNetwork: true y dnsPolicy: ClusterFirst (el valor por defecto) recibe el resolv.conf del nodo y por tanto no puede resolver ningún Service. Si pones red de host, tienes que cambiar también la política.

dnsConfig permite ajustar el fichero resultante:

# k8s/base/api-reservas-deployment.yaml (fragmento)
spec:
  template:
    spec:
      dnsPolicy: ClusterFirst
      dnsConfig:
        options:
          - name: ndots
            value: "2"          # reduce las consultas inutiles a la pasarela
          - name: single-request-open-tcp
        searches:
          - interno.rutasnorte.example   # sufijo adicional para el DNS de oficina

Con dnsPolicy: None es obligatorio dar al menos un nameserver en dnsConfig, o el pod no arranca.

  1. Las variables de entorno heredadas de Docker

Kubernetes inyecta en cada contenedor variables de entorno con la dirección y el puerto de todos los Services que existían en su namespace en el momento de crearse el pod. Es un vestigio del enlazado de contenedores de Docker.

kubectl exec -n rutas-norte-pro deploy/api-reservas -- env | grep SERVICE | sort
POSTGRES_RESERVAS_PORT_5432_TCP_ADDR=10.96.140.22
POSTGRES_RESERVAS_PORT_5432_TCP_PORT=5432
POSTGRES_RESERVAS_SERVICE_HOST=10.96.140.22
POSTGRES_RESERVAS_SERVICE_PORT=5432
REDIS_CACHE_SERVICE_HOST=10.96.77.31
REDIS_CACHE_SERVICE_PORT=6379
TIENDA_WEB_SERVICE_HOST=10.96.88.7
TIENDA_WEB_SERVICE_PORT=80

Parecen cómodas y no deben usarse nunca. Cuatro razones:

  1. Solo existen los Services creados ANTES que el pod. Si despliegas api-reservas y luego redis-cache, la variable no existe. Y el orden de creación no es algo que controles de forma fiable.
  2. Se congelan al arrancar el pod. Si el ClusterIP cambia (Service recreado), la variable sigue con el valor viejo y el pod apunta a una IP muerta hasta que se reinicie.
  3. Contaminan el entorno. Con 50 Services en el namespace son cientos de variables por contenedor. Hay casos documentados de fallos al arrancar por exceder el tamaño del entorno.
  4. Rompen la portabilidad. Ese POSTGRES_RESERVAS_SERVICE_HOST no significa nada fuera de Kubernetes.

Usa siempre el nombre DNS, que no depende del orden, se resuelve fresco y funciona igual en cualquier sitio. El ruido de variables se elimina con enableServiceLinks: false en el spec del pod, recomendable en todos los Deployments de Rutas Norte.

  1. Diagnóstico: los tres fallos típicos

El pod de trabajo, como en 04-01:

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

Comprobaciones básicas dentro:

cat /etc/resolv.conf                     # nameserver, search, ndots
nslookup postgres-reservas               # forma corta, mismo namespace
dig +search +short postgres-reservas     # +search imita el comportamiento real
dig @10.96.0.10 postgres-reservas.rutas-norte-pro.svc.cluster.local

Fallo 1: nslookup no resuelve NADA, ni interno ni externo

;; connection timed out; no servers could be reached

Causa: CoreDNS está caído, saturado o no es alcanzable. Es un fallo global, y su firma es que todos los componentes fallan a la vez.

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
kubectl get endpointslices -n kube-system -l kubernetes.io/service-name=kube-dns

Sospechosos habituales: pods de CoreDNS en CrashLoopBackOff por un Corefile mal editado; el Service kube-dns sin endpoints; CoreDNS OOMKilled por falta de memoria bajo carga; o —y esto será muy relevante en 04-06una NetworkPolicy de egress que bloquea el puerto 53. Este último caso es tan frecuente que le dedicaremos un apartado entero.

Fallo 2: resuelve los nombres externos pero no los del clúster

$ nslookup pagos.proveedorexterno.example
Address: 198.51.100.44          # OK

$ nslookup postgres-reservas
** server can't find postgres-reservas: NXDOMAIN

Causa: el resolv.conf del pod no es el del clúster. Casi siempre es dnsPolicy: Default puesto sin querer, o hostNetwork: true sin ClusterFirstWithHostNet. Compruébalo:

kubectl get pod <pod> -n rutas-norte-pro \
  -o jsonpath='{.spec.dnsPolicy}{"  hostNetwork="}{.spec.hostNetwork}{"\n"}'

Segunda causa posible: el nombre está mal. Un error tipográfico, un Service en otro namespace, o el Service simplemente no existe.

Fallo 3: resuelve pero la conexión falla o va lentísima

$ nslookup postgres-reservas
Address: 10.96.140.22           # resuelve bien

$ nc -zv postgres-reservas 5432
... se queda colgado

El DNS no es el problema. Ya has demostrado que la resolución funciona; el fallo está en el Service (endpoints vacíos, 02-05), en el CNI o en una política de red. Este es el caso más malinterpretado: se culpa al DNS porque "no conecta por el nombre", cuando nslookup ya ha respondido correctamente.

La variante de lentitud sí puede ser DNS: si la resolución tarda segundos, mira ndots y los dominios externos sin punto final (apartado 7), o busca saturación en las métricas de CoreDNS.

Síntoma Resolución interna Resolución externa Causa probable
Nada resuelve Falla Falla CoreDNS caído / puerto 53 bloqueado
Solo falla lo del clúster Falla Funciona dnsPolicy o hostNetwork mal
Resuelve pero no conecta Funciona Funciona No es DNS: Service, CNI o NetworkPolicy
Todo va lento Lento Muy lento ndots:5 sin punto final; CoreDNS saturado

  1. Escala: NodeLocal DNSCache y réplicas de CoreDNS

En clústeres con carga alta el DNS se convierte en cuello de botella. Dos medidas estándar:

Réplicas de CoreDNS. Por defecto son 2 (1 en minikube). Se escala con el tamaño del clúster, vigilando sus métricas antes de que duela: kubectl scale deployment coredns -n kube-system --replicas=3. Existe cluster-proportional-autoscaler, que ajusta las réplicas según el número de nodos y núcleos, y es lo que usan las distribuciones gestionadas.

NodeLocal DNSCache. Un DaemonSet que pone una caché DNS en cada nodo. Los pods consultan a esa caché local (por una IP de enlace, típicamente 169.254.20.10) en lugar de cruzar la red hasta CoreDNS.

Beneficio Detalle
Menos latencia La consulta no sale del nodo si está en caché
Menos carga en CoreDNS Solo llegan los fallos de caché
Menos problemas de conntrack Usa TCP hacia CoreDNS, evitando el conocido fallo de carreras con UDP
Aísla incidentes Un CoreDNS que se reinicia no interrumpe las resoluciones cacheadas

Para Rutas Norte, con sus picos de puentes, es la medida que se plantearía junto con el autoescalado de 09-01.

Errores Comunes y Consejos

  • Culpar al DNS cuando nslookup responde bien. Si resuelve, el DNS ha hecho su trabajo. Sigue por el Service y sus endpoints.
  • Editar el Corefile sin revisar el log. Un error de sintaxis tumba CoreDNS y con él todo el clúster. Tras cada kubectl edit configmap coredns, mira kubectl logs -n kube-system -l k8s-app=kube-dns.
  • Dominios externos sin punto final. Multiplican por cuatro (u ocho) las consultas. En Rutas Norte, la URL de la pasarela de pagos debe llevar el punto.
  • Usar hostNetwork: true sin dnsPolicy: ClusterFirstWithHostNet. El pod pierde la capacidad de resolver Services y el error es desconcertante.
  • Usar las variables *_SERVICE_HOST. Dependen del orden de creación y se congelan. Usa nombres DNS y desactívalas con enableServiceLinks: false.
  • Cachear resoluciones para siempre en la aplicación. Algunos entornos (la JVM con la configuración por defecto histórica) cachean sin caducidad y siguen usando una IP muerta. Ajusta el TTL del resolutor de tu lenguaje.
  • Suponer que el namespace aísla. Un pod de dev resuelve y conecta a postgres-reservas.rutas-norte-pro. Eso solo lo corta 04-06.
  • Consejo: ante cualquier duda, ejecuta dig +search y no dig a secas. Sin +search no se aplican los sufijos y estarás probando algo distinto de lo que hace tu aplicación.
  • Consejo: vigila coredns_dns_request_duration_seconds y la tasa de NXDOMAIN. Un pico de NXDOMAIN suele significar que alguien ha desplegado una aplicación con nombres externos sin punto final.

Ejercicios

Ejercicio 1: Cartografiar los nombres de la plataforma

Desde un pod efímero en rutas-norte-pro, resuelve postgres-reservas en sus cuatro formas (corta, con namespace, con .svc, completa) y comprueba que todas dan la misma IP. Después resuelve postgres-reservas.rutas-norte-dev y explica qué demuestra el resultado.

Ejercicio 2: Medir el coste de ndots: 5

Desde un pod efímero, cuenta cuántas consultas provoca resolver pagos.proveedorexterno.example sin punto final y con punto final. Calcula el ahorro para 2.000 compras por minuto y propón dos formas de arreglarlo en el manifiesto de api-reservas.

Ejercicio 3: Diagnosticar tres pods con DNS roto

Se despliegan tres pods de prueba: A con hostNetwork: true y dnsPolicy por defecto; B normal pero consultando un Service inexistente; C normal, con el Service correcto pero cuyo Deployment está escalado a cero. Predice el síntoma de cada uno y verifícalo.

Soluciones

Ejercicio 1

kubectl run t --rm -it --restart=Never -n rutas-norte-pro \
  --image=nicolaka/netshoot -- sh -c '
    for n in postgres-reservas \
             postgres-reservas.rutas-norte-pro \
             postgres-reservas.rutas-norte-pro.svc \
             postgres-reservas.rutas-norte-pro.svc.cluster.local \
             postgres-reservas.rutas-norte-dev; do
      printf "%-56s %s\n" "$n" "$(dig +search +short $n | head -1)"
    done'
postgres-reservas                                        10.96.140.22
postgres-reservas.rutas-norte-pro                        10.96.140.22
postgres-reservas.rutas-norte-pro.svc                    10.96.140.22
postgres-reservas.rutas-norte-pro.svc.cluster.local      10.96.140.22
postgres-reservas.rutas-norte-dev                        10.96.19.88

Las cuatro primeras son la misma IP: las formas cortas se completan con la lista search. La quinta demuestra que desde producción se resuelve el Service de desarrollo, con una IP distinta: los namespaces no aíslan la red. Y si además pruebas nc -zv postgres-reservas.rutas-norte-dev 5432, conectará.

Ejercicio 2

kubectl run t --rm -it --restart=Never -n rutas-norte-pro --image=nicolaka/netshoot -- sh -c '
    dig +search pagos.proveedorexterno.example  | grep -c "^;.*IN.*A"   # sin punto
    dig         pagos.proveedorexterno.example. | grep -c "^;.*IN.*A"'  # con punto

Sin punto: 4 intentos (3 NXDOMAIN + 1 acierto), por 2 al contar AAAA = 8 consultas. Con punto: 2. Ahorro del 75 %.

A 2.000 compras por minuto: 16.000 consultas/min frente a 4.000. Doce mil consultas inútiles cada minuto contra CoreDNS.

Dos arreglos en el manifiesto:

# Opcion A (preferida): punto final en la URL del ConfigMap
PASARELA_PAGOS_URL: "https://pagos.proveedorexterno.example./cobros"

# Opcion B: bajar ndots para todo el pod
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

La A es quirúrgica y no afecta a nada más; la B sirve cuando no puedes tocar las URLs, pero recuerda que rompe las formas cortas con un punto.

Ejercicio 3

Pod Síntoma Causa
A (hostNetwork) Resuelve pagos.proveedorexterno.example pero da NXDOMAIN en postgres-reservas Usa el resolv.conf del nodo; necesita dnsPolicy: ClusterFirstWithHostNet
B (Service inexistente) NXDOMAIN solo en ese nombre; el resto resuelve El nombre no existe: error tipográfico o namespace equivocado
C (Deployment a cero) Resuelve correctamente al ClusterIP, pero la conexión agota el tiempo de espera El DNS funciona; el Service no tiene endpoints. No es un problema de DNS
# Verificacion del C: la clave esta en los endpoints, no en el DNS
kubectl get endpointslices -n rutas-norte-pro \
  -l kubernetes.io/service-name=api-reservas
# ENDPOINTS: <none>

El caso C es la lección más valiosa de las tres: resolver no es conectar.

Conclusión

Ahora sabes quién traduce postgres-reservas a una dirección IP. CoreDNS es un Deployment normal de kube-system, expuesto por el Service kube-dns en 10.96.0.10, configurado por un Corefile cuyo plugin kubernetes observa Services y EndpointSlices para responder el dominio cluster.local, y cuyo plugin forward envía todo lo demás al resolutor del nodo. Que sea una carga de trabajo corriente tiene una consecuencia operativa: puede caerse, saturarse o quedarse sin memoria, y cuando eso ocurre toda la plataforma parece rota a la vez.

Dominas el esquema de nombres <servicio>.<namespace>.svc.cluster.local y sabes que las formas cortas funcionan gracias a la lista search del resolv.conf, no por magia. Conoces los registros que genera cada objeto: una A con el ClusterIP para un Service normal, una A por pod listo para uno headless, nombres de pod derivados de la IP, y registros SRV que publican el puerto de los puertos con nombre. Y entiendes lo que la resolución no hace: no reparte carga en un Service normal (eso es kube-proxy) y no garantiza conectividad.

El apartado que más te va a ahorrar es ndots: 5. Un dominio externo con menos de cinco puntos provoca cuatro intentos de resolución, ocho consultas contando AAAA, y en los picos de puentes de Rutas Norte eso son miles de consultas inútiles por minuto contra CoreDNS. El punto final en pagos.proveedorexterno.example. lo reduce a una. Sabes ajustar dnsPolicy y dnsConfig, has visto por qué hostNetwork: true sin ClusterFirstWithHostNet deja al pod sin acceso al DNS del clúster, y por qué las variables *_SERVICE_HOST heredadas de Docker no deben usarse jamás —dependen del orden de creación y se congelan— hasta el punto de desactivarlas con enableServiceLinks: false. Tienes también los tres fallos típicos con su firma inconfundible, y en el horizonte NodeLocal DNSCache y el escalado de CoreDNS para cuando la carga apriete.

Con la red entendida (04-01), los tipos de Service dominados (04-02) y el descubrimiento resuelto, ya no queda ninguna excusa: es hora de abrir la plataforma al mundo. En 04-04 desplegaremos un controlador de Ingress y publicaremos por fin www.rutasnorte.example contra tienda-web y api.rutasnorte.example contra api-reservas, con un único punto de entrada, enrutado por dominio y por ruta. Los clientes de Rutas Norte podrán, por primera vez en el curso, comprar un billete.

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