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
- Por qué el descubrimiento se hace por DNS
- CoreDNS: qué es y dónde vive
- El
Corefile: la configuración del DNS del clúster - El esquema de nombres y las formas cortas
- Registros de Services, Services headless y pods
- Registros SRV y puertos con nombre
- El
/etc/resolv.confdel pod y el efecto dendots: 5 dnsPolicyydnsConfig- Las variables de entorno heredadas de Docker
- Diagnóstico: los tres fallos típicos
- Escala: NodeLocal DNSCache y réplicas de CoreDNS
- 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.
- 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).
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 3dTres 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.10es la décima dirección delserviceCIDRpor convención, y es la que el kubelet inyecta en cada pod. - El puerto
9153expone 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.
- El
Corefile: la configuración del DNS del clúster
Corefile: la configuración del DNS del clústerCoreDNS se configura con un fichero llamado Corefile, que vive en un ConfigMap y se monta en el pod. Puedes leerlo tal cual:
.: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:
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.
- 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 ServiceLas 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 |
Sí | El del mismo namespace |
postgres-reservas.rutas-norte-pro |
Sí | Explícito; sirve entre namespaces |
postgres-reservas.rutas-norte-pro.svc |
Sí | Explícito |
postgres-reservas.rutas-norte-pro.svc.cluster.local |
Sí | Canónico y sin ambigüedad |
postgres-reservas.rutas-norte-dev |
Sí | 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 paradev,preyprosin cambios. - Para cruzar namespaces, usa siempre la forma completa con
.svc.cluster.local. Es explícita y, como veremos, más rápida.
- 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.localUna 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.23Tres 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.localEsa 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 |
- 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):
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.
- El
/etc/resolv.conf del pod y el efecto de ndots: 5
/etc/resolv.conf del pod y el efecto de ndots: 5El kubelet escribe este fichero dentro de cada contenedor:
nameserver 10.96.0.10
search rutas-norte-pro.svc.cluster.local svc.cluster.local cluster.local
options ndots:5Tres líneas, tres decisiones:
nameserver 10.96.0.10: el ClusterIP dekube-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 esopostgres-reservasa secas funciona dentro derutas-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 OKCuatro 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"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.
dnsPolicy y dnsConfig
dnsPolicy y dnsConfigdnsPolicy 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 oficinaCon dnsPolicy: None es obligatorio dar al menos un nameserver en dnsConfig, o el pod no arranca.
- 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.
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=80Parecen cómodas y no deben usarse nunca. Cuatro razones:
- Solo existen los Services creados ANTES que el pod. Si despliegas
api-reservasy luegoredis-cache, la variable no existe. Y el orden de creación no es algo que controles de forma fiable. - 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.
- 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.
- Rompen la portabilidad. Ese
POSTGRES_RESERVAS_SERVICE_HOSTno 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.
- 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 -- bashComprobaciones 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.localFallo 1: nslookup no resuelve NADA, ni interno ni externo
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-dnsSospechosos 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-06— una 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: NXDOMAINCausa: 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 colgadoEl 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 |
- 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
nslookupresponde bien. Si resuelve, el DNS ha hecho su trabajo. Sigue por el Service y sus endpoints. - Editar el
Corefilesin revisar el log. Un error de sintaxis tumba CoreDNS y con él todo el clúster. Tras cadakubectl edit configmap coredns, mirakubectl 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: truesindnsPolicy: 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 conenableServiceLinks: 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
devresuelve y conecta apostgres-reservas.rutas-norte-pro. Eso solo lo corta 04-06. - Consejo: ante cualquier duda, ejecuta
dig +searchy nodiga secas. Sin+searchno se aplican los sufijos y estarás probando algo distinto de lo que hace tu aplicación. - Consejo: vigila
coredns_dns_request_duration_secondsy la tasa deNXDOMAIN. Un pico deNXDOMAINsuele 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.88Las 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 puntoSin 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
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
