El fenómeno de los 5 segundos en la resolución DNS
Diversos administradores de clústeres de Kubernetes han reportado un comportamiento anómalo: algunas solicitudes DNS desde un Pod presentan una latencia exacta de 5 segundos, mientras que el resto se resuelvan en milisegundos. Al realizar capturas de tráfico mediante tcpdump, se observa que ciertos paquetes UDP simplemente no reciben respuesta. Tras expirar el tiempo de espera predeterminado del sistema (definido en man resolv.conf como 5 segundos para el resolver de glibc), el cliente reintenta la consulta y esta suele tener éxito de inmediato.
Este problema no suele originarse en el servidor DNS (CoreDNS o kube-dns), sino que los paquetes se pierden en el trayecto, antes de llegar al Pod encargado del servicio de nombres.
Origen técnico: El conflicto en conntrack
La causa raíz reside en una condición de carrera dentro del módulo conntrack del kernel de Linux. Este componente es responsable del seguimiento de las conexiones de red. El problema se manifiesta bajo las siguientes condiciones:
- Múltiples hilos o procesos envían consultas UDP de forma concurrente utilizando el mismo socket o la misma tupla de cinco elementos (IP/puerto origen y destino, protocolo).
- Librerías como
glibcomuslrealizan "consultas paralelas" para registros A (IPv4) y AAAA (IPv6). - Debido a que
kube-proxyen modo IPVS también depende de conntrack, esta modalidad no elimina el riesgo de colisión.
Aunque existen parches en versiones recientes del kernel (posteriores a la 4.19) para mitigar las colisiones en las tablas de seguimiento, el problema persiste en entornos con múltiples servidores DNS o versiones de kernel más antiguas.
Métodos de mtiigación y soluciones
1. Forzar el uso de TCP
Dado que el protocolo TCP mantiene un estado de conexión más robusto y no sufre de las mismas colisiones de conntrack que UDP, una opción teórica es activar use-vc en la configuración del resolver. Esto obliga a utilizar circuitos virtuales (TCP).
options use-vc
Sin embargo, se ha observado que en ciertas distribuciones ligeras o versiones específicas de glibc, esta opción es ignorada, manteniendo el tráfico sobre UDP.
2. Modificar el comportamiento de las consultas paralelas
La forma más efectiva de evitar la colisión es impedir que las consultas A y AAAA compartan la misma entrada en la tabla de conntrack. Para ello, se pueden emplear dos opciones en el archivo /etc/resolv.conf:
- single-request-reopen: Envía las solicitudes A y AAAA utilizando puertos de origen distintos, evitando que ocupen el mismo slot en conntrack.
- single-request: Desactiva la ejecución en paralelo, enviando las consultas de forma secuencial.
Para implementar estas opciones en Kubernetes, existen varios enfoques:
A. Uso de dnsConfig en el Spec del Pod:
template:
spec:
dnsConfig:
options:
- name: single-request-reopen
B. Implementación vía Ciclo de Vida (PostStart):
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- "echo 'options single-request-reopen' >> /etc/resolv.conf"
**C. Uso de un ConfigMap para sobrescribir la configuración:**Se puede montar un archivo personalizado mediante volumeMounts, aunque esto requiere mayor mantenimiento si los nameservers cambian.
apiVersion: v1
kind: ConfigMap
metadata:
name: dns-performance-patch
data:
resolv.conf: |
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local
options ndots:5 single-request-reopen timeout:2
---
# En el Deployment
volumeMounts:
- name: config-dns
mountPath: /etc/resolv.conf
subPath: resolv.conf
volumes:
- name: config-dns
configMap:
name: dns-performance-patch
3. Implementar NodeLocal DNSCache
La solución definitiva recomendada para clústeres de producción es el uso de NodeLocal DNSCache. Este sistema ejecuta un agente de caché DNS en cada nodo del clúster (DaemonSet). Los beneficios principales son:
- Las consultas se dirigen a una IP local (en el mismo nodo), evitando el DNAT y las colisiones en conntrack.
- Reduce la carga sobre los pods centrales de CoreDNS.
- Minimiza la latencia al evitar saltos de red adicionales.
Para habilitarlo, se suele configurar el parámetro --cluster-dns en el kubelet de cada nodo para que apunte a la IP del agente local, o bien utilizar políticas de DNS personalizadas en los Pods para que resuelvan contra la interfaz local del nodo.