Aquí va un remate familiar: dig devuelve la IP correcta, tus paneles de DNS muestran todo en verde, y sin embargo tu aplicación se comporta como si hubiera sido cortada de la civilización. Las solicitudes agotan el tiempo. Los handshakes TLS fallan. Las reintentos se acumulan hasta que el balanceador de carga empieza a sudar. Todo el mundo mira a DNS porque es el villano más fácil de nombrar.
A veces DNS es culpable. Más a menudo, DNS solo es el testigo — y la escena real del crimen es una capa de caché que olvidaste que existía, situada entre tu app y la respuesta autorizada, sirviendo con confianza la verdad de ayer.
El modelo mental: «DNS funciona» no es algo definitivo
Cuando alguien dice “DNS funciona”, lo que normalmente quiere decir es: desde mi portátil, ahora mismo, usando mi herramienta preferida, puedo resolver un nombre. Ese es un buen momento. No es un cierre de incidente.
En producción, DNS es una tubería. Tu aplicación llama a una API de resolutor (a menudo a través de libc), que puede consultar cachés locales, un resolutor stub, una caché a nivel de nodo, un servicio DNS del clúster, un resolutor recursivo upstream y, finalmente, los servidores autorizados. En cada salto, alguien puede decidir cachear. En cada salto, los timeouts y reintentos pueden multiplicarse. En cada salto, la configuración puede divergir entre entornos.
El resultado es la clásica falla de pantalla dividida:
- Humanos: “Mira,
digfunciona.” - Apps: “No puedo conectar, y ahora mantendré en memoria una IP obsoleta hasta la muerte térmica del universo.”
Ese desajuste ocurre porque DNS no es una sola caché. Son varias cachés, cada una con sus propias reglas de TTL, política de expulsión y semántica de fallos. No depuras DNS discutiendo registros A. Lo depuras encontrando la capa de caché que está mintiendo.
Las capas de caché con las que realmente estás lidiando
1) Cachés a nivel de aplicación (la caché de “mi código es inocente”)
Muchos runtimes y librerías cachean resultados DNS, a veces de forma agresiva, a veces para siempre, y a veces de maneras que no coinciden con el TTL del registro que configuraste con tanto cuidado.
- JVM: la caché DNS es famosamente “útil”. Dependiendo de las propiedades de seguridad y la presencia de un SecurityManager (menos común ahora), puede cachear respuestas positivas por mucho tiempo, y respuestas negativas también. Si ejecutas Java y no conoces tus ajustes efectivos de TTL de DNS, estás apostando.
- Go: la ruta del resolutor depende de flags de compilación y del entorno. En muchas configuraciones Linux, Go puede usar el resolutor del sistema vía cgo (e heredar el comportamiento de libc) o usar un resolutor puro en Go con su propio enfoque de caché y reintentos. Los contenedores lo complican todo.
- Node.js / Python / Ruby: el comportamiento varía según la librería. El runtime puede no cachear mucho, pero los clientes HTTP de alto nivel, los pools de conexiones o los SDKs de descubrimiento de servicios a menudo lo hacen.
- Clientes HTTP y pools: Incluso si DNS se actualiza correctamente, un pool puede seguir conectando a una IP antigua hasta que el socket falle. DNS “arreglado” no significa que el tráfico se haya movido.
Los problemas de caché a nivel de app son brutales porque son invisibles a tus herramientas normales de DNS. Una JVM reteniendo una IP en memoria seguirá fallando aunque dig tenga éxito. Eso no es DNS siendo errático. Es tu proceso siendo terco.
2) libc y NSS (la caché de “depende de /etc/nsswitch.conf”)
En Linux, la resolución de nombres pasa por NSS (Name Service Switch). Eso significa que tu app puede consultar files (archivo hosts), luego dns, luego mDNS, LDAP o lo que sea que alguien configuró hace tres años durante un “arreglo rápido”. El orden importa. El comportamiento ante fallos importa. Los timeouts importan.
glibc en sí no mantiene una gran caché persistente de DNS como algunos suponen. Pero aún puede darte problemas a través de:
/etc/hostsque anula (obsoleto, olvidado o incorporado en imágenes).- Módulos NSS que cachean (p. ej., SSSD, nscd) o introducen retrasos.
- Opciones en
resolv.confcomotimeout,attemptsyrotateque pueden cambiar drásticamente los modos de fallo.
3) Demonios de caché local: nscd, dnsmasq, systemd-resolved
Si tienes systemd-resolved, probablemente ejecute un resolutor stub en 127.0.0.53 y cachee respuestas. Si tienes dnsmasq en laptops o nodos, cachea. Si aún tienes nscd (no está muerto en todas partes), puede cachear búsquedas de hosts.
Estas cachés suelen ser bienintencionadas: reducir latencia, reducir tasa de consultas al upstream, sobrevivir a fallos transitorios del resolutor. Pero pueden convertirse en el lugar donde persisten respuestas erróneas.
4) Cachés locales al nodo (especialmente en Kubernetes)
Los clústeres Kubernetes suelen ejecutar CoreDNS como servicio. Muchos equipos añaden NodeLocal DNSCache para reducir la carga y mejorar la latencia en la cola. Eso está bien—hasta que no lo está. Ahora tienes:
- Pods → caché local del nodo (iptables hacia una IP local)
- Caché local del nodo → servicio CoreDNS
- CoreDNS → resolutores recursivos upstream
Más capas, más oportunidades para entradas obsoletas, configuración inconsistente y telemetría confusa. Si la caché de un nodo está bloqueada, un grupo de pods fallará mientras otros parecen normales. Ese es el tipo de incidente que genera hilos largos en Slack y temperamentos cortos.
5) Resolutores recursivos (la caché de “pagamos por esto”)
Tus resolutores recursivos upstream (DNS corporativo, resolutores del proveedor cloud o servicios gestionados) cachean agresivamente. También aplican límites de TTL en algunos entornos: TTL mínimos/máximos, comportamientos de precarga y servir datos obsoletos durante fallos upstream.
Algunos resolutores devolverán respuestas caducadas pero cacheadas (“serve stale”) para preservar la disponibilidad. Eso es genial cuando los servidores autorizados están caídos. Es terrible cuando intentas evacuar tráfico desde una IP muerta.
6) DNS autoritativo (el lugar donde sigues editando mientras nada cambia)
El DNS autoritativo es donde viven tus ajustes de TTL. También es donde esperas tener control. Pero tu autoridad solo importa si cada capa de caché la respeta. Muchas no lo harán. Muchas la “respetarán” de formas técnicamente permitidas pero operacionalmente inconvenientes.
7) Las cachés no relacionadas con DNS que parecen fallos de DNS
No todo problema que parece “DNS” es DNS. Algunos problemas son causados por:
- Pool de conexiones: clientes siguen usando sockets existentes hacia backends antiguos.
- Happy Eyeballs / preferencia IPv6: tu app prueba AAAA primero, falla lentamente y luego hace fallback.
- Reutilización de sesión TLS y desajustes SNI: llegas a la IP equivocada y obtienes un error de certificado; parece “DNS incorrecto”, pero es enrutamiento más TLS.
- Retrasos en la propagación del estado del balanceador: DNS apunta a un LB que aún envía a targets rotos.
Chiste #1: DNS es como un molino de rumores—cuando llega a todos, es técnicamente correcto y operativamente inútil.
Hechos y contexto histórico que explican el desorden actual
- DNS se diseñó a principios de los años 80 para reemplazar la distribución de HOSTS.TXT; el cacheo fue una característica desde el inicio, no un añadido posterior.
- El concepto de TTL se pensó para controlar la vida de la caché, pero muchos resolutores recursivos aplican políticas: suelos/techos de TTL que anulan tu intención.
- El cacheo negativo se estandarizó más tarde: respuestas NXDOMAIN y “sin datos” pueden ser cacheadas, lo que significa que “no existía” puede persistir después de que la crees.
- Las guías operacionales tempranas asumían registros relativamente estables. Los microservicios modernos y el autoscaling producen más variación DNS de la que el sistema estaba socialmente preparado para manejar.
- Algunos resolutores implementan comportamientos de “serve stale” para mejorar la disponibilidad durante fallos upstream, a costa de cortes más lentos.
- systemd-resolved (mediados de 2010s) normalizó resolutores stub en localhost en muchas distribuciones, cambiando patrones de depuración y sorprendiendo a quienes esperan que
/etc/resolv.confliste resolutores reales. - Kubernetes convirtió DNS en una dependencia crítica del plano de control para el descubrimiento de servicios; CoreDNS se volvió uno de los pods más importantes del clúster, te guste o no.
- CDNs y gestores de tráfico global convirtieron a DNS en “parte del enrutamiento”. Eso es poderoso, pero significa que las respuestas DNS a veces dependen de la ubicación y de políticas.
- El DNS con vista dividida (split-horizon) es común en empresas (respuestas internas y externas difieren), lo que implica que “funciona en mi portátil” puede reflejar una vista distinta a la que obtiene tu workload.
Tres mini-historias corporativas (anonimizadas, plausibles, técnicamente exactas)
Mini-historia #1: El incidente provocado por una suposición equivocada
El equipo ejecutaba una API multirregión detrás de un nombre DNS que devolvía distintos registros A por región. Durante una migración, redujeron los TTL con antelación y planificaron un corte. El plan oficial era: actualizar registros, esperar un minuto, verificar la nueva distribución de tráfico.
Llegó el día del corte. dig desde un bastión mostró los nuevos targets inmediatamente. Sin embargo, el monitoreo mostraba un flujo persistente de tráfico que seguía alcanzando la región antigua. La latencia subió. Algunas solicitudes agotaron el tiempo. El on-call pensó que el cambio no se había propagado y comenzó a “arreglar DNS” repetidamente, lo que no logró más que ansiedad.
El verdadero culpable no fue DNS autoritativo ni los resolutores recursivos. Fue una capa de aplicación: un servicio Java que realizaba la resolución DNS una sola vez al inicio para una dependencia upstream y cacheaba el resultado en proceso. No era malicioso. Era “una optimización”, escrita años atrás cuando los upstreams no se movían. El servicio había sido tan estable que nadie recordaba que existía.
Reiniciaron el deployment para forzar una resolución fresca. El tráfico se movió al instante. El postmortem fue incómodo porque el equipo de DNS había hecho todo bien, y el equipo de la app había hecho algo entendible. La solución duradera fue eliminar la caché en proceso, respetar el TTL y añadir una comprobación en tiempo de despliegue para demostrar que el runtime refresca DNS sin reinicio.
Mini-historia #2: La optimización que salió mal
Un grupo de plataforma introdujo NodeLocal DNSCache para reducir la carga de CoreDNS y arreglar latencia intermitente en tráfico con picos. El despliegue pareció ir bien. La CPU de CoreDNS bajó. La latencia p99 de resolución mejoró. El equipo declaró la victoria.
Dos meses después, un subconjunto de nodos empezó a mostrar fallos localizados y misteriosos: pods en ciertos nodos no podían resolver nombres de servicios recién creados durante varios minutos. Los ingenieros vieron NXDOMAIN en los logs de la aplicación. Mientras tanto, pods en otros nodos resolvían bien. Esa asimetría hizo sospechar de Kubernetes mismo, o de “networking”, o de la fase lunar.
La caché local del nodo estaba cacheando negativamente NXDOMAIN para un nombre de servicio que apareció poco después de un despliegue. Una carrera en la secuencia del despliegue provocó solicitudes tempranas antes de que el Service existiera, produciendo NXDOMAIN. Una vez cacheado, esos nodos siguieron creyendo que el servicio no existía. La verdad autoritativa había cambiado, pero la caché negativa no se había actualizado aún.
Lo arreglaron apretando el orden del despliegue (Service antes de workloads), reduciendo el TTL de cacheo negativo en la configuración de la caché DNS y añadiendo una alerta por tasa de NXDOMAIN por nodo. NodeLocal DNSCache se quedó—seguía siendo una buena idea—pero el equipo aprendió que mejorar la latencia p99 también puede acelerar el ritmo al que cacheas algo incorrecto.
Mini-historia #3: La práctica aburrida pero correcta que salvó el día
Una plataforma de pagos operaba con una práctica conservadora de DNS: cada dependencia crítica tenía una entrada en el runbook que listaba toda la ruta de resolución. App → libc → stub del nodo → DNS del clúster → resolutor upstream → autoritativo. Para cada salto documentaron qué logs existían, qué métricas vigilar y cómo evitarlo.
Era trabajo aburrido. Nadie recibió un ascenso por “documentar la cadena de resolutores”. Pero significó que durante una caída donde un resolutor recursivo upstream empezó a agotar tiempo de forma intermitente, no perdieron horas discutiendo con los portátiles de cada uno.
Inmediatamente reprodujeron la falla desde dentro de un pod afectado, luego desde el nodo y luego directamente contra la IP del resolutor upstream. Eso aisló el problema: el resolutor upstream era el cuello de botella, no CoreDNS ni la app. Reconfiguraron temporalmente los nodos para usar un pool secundario de resolutores y restauraron el servicio.
Más tarde, usaron sus dashboards para mostrar latencia de consulta y tasas de timeout por resolutor. Escalaron al dueño del resolutor recursivo con pruebas en lugar de sentimientos. El incidente fue corto. El postmortem fue tranquilo. La práctica aburrida pagó la renta.
Guía de diagnóstico rápido: qué verificar primero/segundo/tercero
Este es el flujo de trabajo que ahorra horas. El objetivo es identificar qué capa de caché está mintiendo, y si la falla es resolución, enrutamiento o reutilización de conexiones.
Primero: reproducir desde el mismo namespace de red que la app fallida
- Si es Kubernetes: hacer exec en el pod (o un pod de depuración en el mismo nodo).
- Si es una VM: ejecutar desde el host, no desde tu portátil.
- Si es un contenedor: ejecutar desde dentro del contenedor.
Si no puedes reproducir desde el mismo lugar, estás depurando sensaciones.
Segundo: confirmar qué resolutor está usando realmente la carga de trabajo
- Revisa
/etc/resolv.confdentro de la carga de trabajo. - Comprueba si apunta a
127.0.0.53(stub de systemd-resolved), a una IP local del nodo o al servicio DNS del clúster. - Revisa dominios de búsqueda y
ndots. Estos pueden convertir una búsqueda en cinco.
Tercero: aislar si es “respuesta equivocada” o “respuesta lenta”
- Respuesta equivocada: la IP devuelta está obsoleta, apunta a un target muerto o difiere según el cliente.
- Respuesta lenta: timeouts, reintentos largos, SERVFAIL ocasionales. Las apps suelen tratar esto como una caída de dependencia.
Cuarto: evita capas a propósito
- Consulta al resolutor configurado.
- Consulta directamente al resolutor recursivo upstream.
- Consulta directamente al autoritativo (o al menos a un recursivo conocido bueno).
Cada bypass es una prueba que elimina una capa de caché.
Quinto: verifica ajustes de caché DNS en la app/runtime
- TTL y TTL negativo en JVM.
- Sidecars proxy (Envoy) — tasa de refresco DNS y comportamiento de circuitos.
- Clientes de descubrimiento de servicio (Consul, basados en etcd, SDKs personalizados) que cachean endpoints.
Sexto: verifica que las conexiones realmente se movieron
- Si DNS se actualizó pero el tráfico no, suele ser pooling de conexiones o comportamiento del LB.
- Revisa sockets existentes, keep-alives y lógica de reintento.
Idea parafraseada (atribuida): Werner Vogels ha enfatizado que todo falla, así que los sistemas deben diseñarse para esperar y manejar fallos.
Tareas prácticas: comandos, qué significa la salida y qué decisión tomar
Estos son los movimientos prácticos que quiero que los ingenieros realmente ejecuten. Cada tarea incluye un comando, salida representativa, qué significa y la decisión que tomas.
Tarea 1: Ver cómo luce resolv.conf dentro del entorno afectado
cr0x@server:~$ cat /etc/resolv.conf
nameserver 127.0.0.53
options edns0 trust-ad
search svc.cluster.local cluster.local
Significado: Estás usando un resolutor stub local (127.0.0.53), y se añadirán dominios de búsqueda. El comportamiento de resolución depende de systemd-resolved, no del “nameserver que creías”.
Decisión: No pierdas tiempo consultando upstreams al azar aún. Primero interroga systemd-resolved y confirma a dónde reenvía.
Tarea 2: Comprobar orden NSS y si fuentes no DNS pueden anular
cr0x@server:~$ grep -E '^\s*hosts:' /etc/nsswitch.conf
hosts: files mdns4_minimal [NOTFOUND=return] dns myhostname
Significado: /etc/hosts se consulta primero. mDNS puede cortar el flujo. DNS no es la primera parada.
Decisión: Si el síntoma es “funciona en una máquina pero no en otra”, compara /etc/hosts y las configuraciones NSS. Tu “problema DNS” podría ser una anulación local.
Tarea 3: Confirmar el estado de systemd-resolved, servidores upstream y estadísticas de caché
cr0x@server:~$ resolvectl status
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.0.0.2
DNS Servers: 10.0.0.2 10.0.0.3
Significado: El stub reenvía a 10.0.0.2 y 10.0.0.3. Si esos son lentos o están envenenados, todo lo que está encima sufre.
Decisión: Prueba esas IPs de resolutor upstream directamente a continuación. Si las consultas directas son lentas, escala a los propietarios del resolutor.
Tarea 4: Vaciar la caché de systemd-resolved (para una prueba controlada)
cr0x@server:~$ sudo resolvectl flush-caches
Significado: Eliminaste entradas cacheadas locales. Si el comportamiento cambia de inmediato, has encontrado una capa que mentía.
Decisión: Si el flush lo arregla, céntrate en por qué se sirvieron entradas obsoletas (política de TTL, serve-stale, cacheo negativo o inconsistencia upstream).
Tarea 5: Consultar a través del resolutor configurado y medirlo
cr0x@server:~$ dig +tries=1 +time=2 api.internal.example A
;; ANSWER SECTION:
api.internal.example. 30 IN A 10.40.12.19
;; Query time: 3 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
Significado: El stub local respondió rápido con TTL 30. Eso está bien, pero sigue siendo solo una capa.
Decisión: Si la app aún falla, sospecha de caché a nivel de app o reutilización de conexiones. También compara consultando upstream directamente.
Tarea 6: Consultar al resolutor recursivo upstream directamente (evitar el stub)
cr0x@server:~$ dig @10.0.0.2 api.internal.example A +tries=1 +time=2
;; ANSWER SECTION:
api.internal.example. 30 IN A 10.40.12.19
;; Query time: 210 msec
;; SERVER: 10.0.0.2#53(10.0.0.2)
Significado: El upstream es más lento que el stub. Eso puede ser normal (hit de caché local) o una señal de advertencia (upstream sobrecargado).
Decisión: Si las consultas upstream son consistentemente lentas o agotan tiempo, arregla la capacidad upstream, la ruta de red o la salud del resolutor. No ajustes primero los reintentos de la app; eso solo oculta el dolor.
Tarea 7: Comprobar cacheo negativo consultando un nombre que acabas de crear
cr0x@server:~$ dig newservice.svc.cluster.local A +tries=1 +time=2
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 1122
;; Query time: 4 msec
Significado: NXDOMAIN puede ser cacheado. Si el servicio ahora existe pero algunos clientes siguen viendo NXDOMAIN, estás ante un comportamiento de cacheo negativo.
Decisión: Valida el orden de creación en despliegues y ajusta el TTL de cacheo negativo donde controles (CoreDNS, caches locales del nodo). Si no puedes ajustar, modifica los rollouts para evitar consultas antes de que los registros existan.
Tarea 8: Observar la expansión de dominios de búsqueda y el comportamiento ndots (trampa común en Kubernetes)
cr0x@server:~$ cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5 timeout:1 attempts:2
Significado: Con ndots:5, un nombre como api.internal.example (dos puntos) puede ser tratado como “relativo” y probado con dominios de búsqueda primero, generando múltiples consultas y retrasos.
Decisión: Para nombres externos, usa el FQDN con un punto final (api.internal.example.) en configuraciones donde sea soportado, o ajusta ndots con cuidado (entendiendo los valores por defecto del clúster).
Tarea 9: Usar getent para ver lo que probablemente ve la aplicación vía libc/NSS
cr0x@server:~$ getent hosts api.internal.example
10.40.12.19 api.internal.example
Significado: getent usa NSS, que coincide mejor con muchas rutas de resolución de aplicaciones que dig.
Decisión: Si dig funciona pero getent falla o devuelve resultados distintos, centrate en la configuración NSS, el archivo hosts y los demonios de caché locales—no en el DNS autoritativo.
Tarea 10: Comprobar si /etc/hosts está anulando tu nombre
cr0x@server:~$ grep -n "api.internal.example" /etc/hosts
12:10.20.1.99 api.internal.example
Significado: Alguien lo hardcodeó. Esto anulará DNS si NSS comprueba files primero (algo común).
Decisión: Elimina la anulación, reconstruye imágenes sin ella y añade checks en CI para prevenir pinneado en el hostfile para nombres de servicios en producción.
Tarea 11: En Kubernetes, consulta CoreDNS directamente y compara respuestas
cr0x@server:~$ kubectl -n kube-system get svc kube-dns -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 2y k8s-app=kube-dns
Significado: La IP del servicio CoreDNS es 10.96.0.10.
Decisión: Consúltala directamente desde un pod para evitar caches locales del nodo o stubs al aislar responsabilidades.
Tarea 12: Inspeccionar la configuración de CoreDNS para caché y dominios stub
cr0x@server:~$ kubectl -n kube-system get configmap coredns -o yaml | sed -n '1,120p'
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
data:
Corefile: |
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . 10.0.0.2 10.0.0.3
cache 30
loop
reload
loadbalance
}
Significado: CoreDNS reenvía a resolutores upstream y cachea por 30 segundos. Eso no es “malo”, pero explica tiempos de propagación y comportamientos obsoletos durante cortes.
Decisión: Si necesitas un corte más rápido para ciertos registros, considera TTLs más bajos de extremo a extremo y verifica políticas de resolutor. No pongas el cache en 0 a lo loco; te auto-DOSearás con consultas.
Tarea 13: Buscar errores y timeouts en CoreDNS
cr0x@server:~$ kubectl -n kube-system logs -l k8s-app=kube-dns --tail=20
[ERROR] plugin/errors: 2 api.internal.example. A: read udp 10.244.1.10:45712->10.0.0.2:53: i/o timeout
[ERROR] plugin/errors: 2 api.internal.example. AAAA: read udp 10.244.1.10:46891->10.0.0.2:53: i/o timeout
Significado: CoreDNS está agotando tiempo hacia el resolutor upstream 10.0.0.2. Las apps verán fallos de resolución intermitentes o demoras.
Decisión: Arregla la salud/capacidad o el enrutamiento del resolutor upstream. Como mitigación, añade/quita upstreams y reduce timeout/attempts para fallar rápido, pero no digas que es “solo Kubernetes”.
Tarea 14: Confirmar a qué IP se conecta realmente tu app (DNS vs pooling de conexiones)
cr0x@server:~$ sudo ss -tnp | grep -E ':(443|8443)\s' | head
ESTAB 0 0 10.244.3.21:48712 10.40.12.10:443 users:(("java",pid=2314,fd=214))
Significado: El proceso está conectado a 10.40.12.10, que puede ser una IP antigua incluso si DNS ahora devuelve 10.40.12.19.
Decisión: Si DNS es correcto pero los sockets apuntan a targets antiguos, tu problema es reutilización de conexiones. Reduce la vida de keep-alive, implementa re-resolución periódica o rota pods/clients durante cortes.
Tarea 15: Medir rápidamente latencia de resolución y tasa de fallos
cr0x@server:~$ for i in {1..10}; do dig +tries=1 +time=1 api.internal.example A | awk '/Query time/ {print $4}'; done
3
4
1002
2
1001
3
4
1001
2
3
Significado: Algunas consultas tardan ~1s (timeout) y luego tienen éxito en rutas de reintento, lo cual envenena la latencia en la cola.
Decisión: Trata esto como una caída de dependencia: arregla el salto de resolutor lento, reduce attempts y evita multiplicar reintentos en múltiples capas.
Chiste #2: El cacheo DNS es el único lugar donde ser “eventualmente consistente” significa “eventualmente te despiertan con un llamado”.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: dig funciona, la app aún alcanza IP antigua
Causa raíz: Pooling de conexiones o cacheo DNS en proceso (JVM, SDK, caché personalizado). DNS cambió, pero la app no re-resolvió o no volvió a conectar.
Solución: Asegurar que el runtime respete TTL y re-resuelva periódicamente; limitar la vida de keep-alive; durante cortes, reciclar clientes de forma segura. Validar con ss y ajustes de runtime.
2) Síntoma: Algunos pods fallan, otros bien (mismo deployment)
Causa raíz: Caché local del nodo atascada, ruta de red por nodo al resolutor, o issues con el stub por nodo.
Solución: Compara /etc/resolv.conf y la capa de caché por nodo; vacía cachés; reinicia pods DNS locales del nodo; arregla la alcanzabilidad upstream desde el nodo.
3) Síntoma: Nombre de servicio recién creado devuelve NXDOMAIN por minutos
Causa raíz: Cacheo negativo en caches locales del nodo o resolutores recursivos; orden de despliegue que provoca consultas antes de que el registro exista.
Solución: Crear objetos DNS antes de que los clientes los consulten; reducir TTL negativo donde sea posible; añadir gates de readiness en rollouts que verifiquen resolución.
4) Síntoma: Primera solicitud lenta, luego rápida
Causa raíz: Timeout/attempts del resolutor demasiado altos, expansión de dominios de búsqueda, o intentos AAAA que fallan antes de que A tenga éxito.
Solución: Ajustar timeouts y attempts del resolutor; arreglar alcanzabilidad IPv6 o ajustar estrategia de familia de direcciones; reducir expansiones de búsqueda innecesarias usando FQDNs absolutos.
5) Síntoma: Funciona en el portátil, falla en producción
Causa raíz: DNS split-horizon, cadena de resolutores diferente, VPN/resolutores corporativos, o zonas internas no visibles desde redes de producción.
Solución: Probar desde el mismo namespace de red y resolutores que usa la carga de trabajo; documentar qué vista (interna/externa) usa cada entorno; evitar confiar en el DNS del portátil como prueba.
6) Síntoma: Picos aleatorios de SERVFAIL
Causa raíz: Problemas en resolutores recursivos upstream, fallos de validación DNSSEC en algunas rutas, pérdida de paquetes (fragmentación UDP) o CoreDNS sobrecargado.
Solución: Revisar logs de CoreDNS y latencia de consultas upstream; probar fallback TCP; reducir tamaño EDNS si es necesario; escalar capacidad de resolutor y añadir diversidad.
7) Síntoma: La tasa de consultas DNS explota tras un pequeño cambio de configuración
Causa raíz: Deshabilitar cacheo, fijar TTL demasiado bajo globalmente o crear muchos nombres únicos (p. ej., hostnames por petición).
Solución: Rehabilitar cacheo con TTLs sensatos; evitar hostnames por petición; usar patrones de descubrimiento de servicios que no conviertan DNS en un bucle caliente.
8) Síntoma: Canary tiene éxito; tráfico masivo falla
Causa raíz: Stack runtime diferente: el canary usa un cliente distinto, configuración de sidecar distinta o flags JVM diferentes; la masa usa pooling de conexiones o resolución obsoleta.
Solución: Asegurar que canary y producción coincidan en cadena de resolutores y ajustes runtime; comparar flags de procesos y configs de sidecar; ejecutar getent e inspección de sockets en ambos.
Listas de comprobación / plan paso a paso
Checklist A: Durante un incidente (15–30 minutos)
- Reproducir desde la carga de trabajo (pod/contenedor/host). Capturar timestamp y nombre del nodo.
- Registrar evidencias de la cadena de resolutores:
/etc/resolv.conf,resolvectl status(si aplica) y/etc/nsswitch.conf. - Ejecutar tanto
digcomogetentpara el nombre afectado. Si difieren, prioriza depuración de NSS/caché local. - Medir la distribución de tiempos de consulta (loop de dig 10–20 veces). Busca picos intermitentes de 1s/2s.
- Evitar capas: consulta el resolutor configurado y luego el IP del resolutor upstream directamente.
- Comprobar caché negativo: confirmar si NXDOMAIN está implicado; identificar si el registro/servicio fue creado recientemente.
- Confirmar conexiones reales: usar
sspara ver a dónde se conectan los procesos. DNS podría estar correcto mientras los sockets están atascados. - Mitigar con seguridad: reinicia la capa de caché que miente (stub/local/CoreDNS) solo si entiendes el blast radius; de lo contrario, reroutea a resolutores saludables.
Checklist B: Después del incidente (hacer que no vuelva a ocurrir)
- Documentar la cadena de resolutores para cada entorno. Incluir quién posee cada salto.
- Establecer política explícita de cacheo DNS en runtimes para JVM y sidecars. No confiar en defaults.
- Estandarizar herramientas:
getentpara “lo que ve la app”,digpara “lo que dice DNS”. Enseñar la diferencia. - Alinear TTLs con la realidad operativa: si necesitas cortes de 30 segundos, asegúrate de que las cachés no impongan mínimos de 5 minutos.
- Añadir observabilidad: latencia de consulta, tasas SERVFAIL/NXDOMAIN y salud de caché por nodo en Kubernetes.
- Practicar cutovers: hacer un cambio de registro planificado en un entorno no productivo y verificar que los clientes se mueven sin reinicios.
Checklist C: Antes de añadir una nueva capa de caché (sé honesto)
- Definir por qué la necesitas: latencia tail, carga del resolutor, resiliencia. Elige un objetivo primario.
- Decidir quién la posee y cómo se monitoriza. “Es solo DNS” no es un modelo de ownership.
- Decidir la política de cacheo negativo explícitamente.
- Planear bypass y controles de emergencia: cómo apuntar clientes a otro resolutor rápidamente.
- Probar modos de fallo: timeout upstream, SERVFAIL, NXDOMAIN, pérdida de paquetes y cambios de autoridad.
Preguntas frecuentes (FAQ)
1) ¿Por qué dig funciona pero la app falla al resolver?
dig consulta DNS directamente y evita partes de la ruta del resolutor del sistema. Tu app probablemente usa NSS/libc (o el comportamiento del resolutor del runtime) además de cachés. Usa getent hosts name para reproducir más fielmente el comportamiento de la app.
2) Si pongo TTL a 30 segundos, ¿por qué los clientes siguen usando respuestas antiguas durante minutos?
Porque algunas capas de caché aplican suelos/techos de TTL, políticas de serve-stale o la app cachea independientemente. Además, incluso con la actualización DNS correcta, las conexiones TCP existentes no migrarán mágicamente a nuevas IPs.
3) ¿Vaciar la caché DNS es una solución real?
Es una herramienta de diagnóstico y a veces una mitigación de emergencia. Si vaciar la caché lo arregla, has encontrado una capa que sirve datos obsoletos/incorrectos. La solución real es alinear la política de TTL, la salud del resolutor y el comportamiento de cacheo en runtimes.
4) ¿Por qué solo algunos pods en Kubernetes fallan en DNS?
A menudo porque la resolución depende del nodo (caché local del nodo, ruta de red por nodo, redirección iptables o alcanzabilidad del resolutor por nodo). Confirma el nodo y prueba desde otro pod programado en el mismo nodo.
5) ¿Qué es el cacheo negativo y por qué debería importarme?
El cacheo negativo significa cachear respuestas “no existe” (NXDOMAIN/sin datos). Si tu despliegue consulta un nombre antes de que exista, las cachés pueden recordar el NXDOMAIN y seguir fallando después de que el nombre se cree.
6) ¿glibc cachea resultados DNS?
glibc sigue principalmente NSS y la configuración del resolutor; el cacheo persistente suele hacerlo demonios externos (systemd-resolved, nscd, dnsmasq) o capas superiores (app/runtime). No asumas “cache de glibc” sin evidencia.
7) ¿Cómo sé si esto es DNS o pooling de conexiones?
Si las respuestas DNS son correctas pero el proceso todavía se conecta a una IP antigua, es pooling/reutilización. Revisa conexiones activas con ss -tnp y compáralas con las respuestas DNS actuales.
8) ¿Deberíamos desactivar el cacheo DNS para evitar obsolescencia?
No, no como política general. Desactivar cacheo desplaza la carga upstream, aumenta latencia y puede crear fallos en cascada durante problemas del resolutor. En su lugar, ajusta TTLs, cacheo negativo y asegura que los clientes se comporten correctamente.
9) ¿Por qué vemos demoras resolviendo nombres cortos dentro de clusters?
Los dominios de búsqueda y ndots pueden causar múltiples consultas por lookup. Un nombre puede probarse como name.namespace.svc.cluster.local antes del FQDN previsto, y los timeouts se acumulan.
10) ¿Cuál es la métrica más útil para la fiabilidad de DNS?
La latencia de consulta del resolutor y las tasas de timeout/SERVFAIL en cada salto (local al nodo, CoreDNS, resolutor upstream). “Tasa de consultas” sola es engañosa; quieres saber cuándo la resolución se vuelve lenta o errática.
Conclusión: pasos prácticos siguientes
Si tu DNS “funciona” pero las aplicaciones aún fallan, deja de tratar a DNS como un único componente. Trátalo como una cadena de cachés y políticas. Tu trabajo es encontrar el salto que está mintiendo o lento, y luego volver a hacerlo aburrido.
- Estandariza tu línea base de depuración: siempre recopila
/etc/resolv.conf,/etc/nsswitch.confy un resultado degetentdesde el entorno de la carga de trabajo. - Instrumenta la cadena de resolutores: latencia y tasas de fallo de CoreDNS/caché node-local/resolutor upstream. Si no lo puedes ver, no lo puedes operar.
- Haz explícito el cacheo DNS en runtimes: JVM, proxies y SDKs. Los valores por defecto no son una estrategia.
- Planifica cutovers que contemplen conexiones: los cambios de DNS no drenan sockets existentes. Reserva tiempo o fuerza reconexiones de forma segura.
- Escribe el runbook que hubieras querido tener: ruta de resolutores, comandos de bypass y ownership. Es aburrido. También es cómo evitas el show improvisado a las 3 a.m.