Глубокое погружение в DNS Linux: от resolv.conf до CoreDNS в Kubernetes

Исходная ситуация: DNS-хаос в облачной инфраструктуре

В конце марта 2026 года к нам обратился облачный провайдер КлаудВан — российский IaaS с 2 000 виртуальных машин на OpenStack. Проблема: клиенты массово жаловались на «случайные» DNS-сбои. Сайты открывались через раз, API-запросы падали с ошибкой «Could not resolve host», а кеширование DNS вело себя непредсказуемо.

При первичной диагностике мы обнаружили хаос:

  • На разных VM разные механизмы резолвинга: где-то systemd-resolved, где-то NetworkManager, где-то ручной resolv.conf
  • Кеширование DNS отсутствовало — каждый запрос шёл к upstream-серверам
  • В Kubernetes-кластере CoreDNS падал под нагрузкой, ломая service discovery
  • Часть VM использовала DNS over TLS, часть — plain DNS, конфликтуя с фаерволом

Задача: стандартизировать DNS-инфраструктуру на всех уровнях — от VM до Kubernetes — и устранить сбои.

Основы: resolv.conf и nsswitch.conf — кто главный

Прежде чем чинить, нужно понять, как DNS работает в Linux. Это не один файл и не один сервис — это цепочка из нескольких компонентов:

# /etc/nsswitch.conf — определяет порядок поиска для glibc
# Строка hosts определяет, откуда брать DNS-ответы
hosts: files dns mymachines

# files   → /etc/hosts (первый приоритет)
# dns     → /etc/resolv.conf (обращение к DNS-серверу)
# mymachines → systemd-machined (контейнеры systemd)

# Важно: dig, nslookup, host — НЕ используют nsswitch!
# Они идут напрямую в DNS, минуя /etc/hosts
# Только ping, curl, wget, getent используют nsswitch
# /etc/resolv.conf — конфигурация DNS-клиента
# Этот файл может быть:
# 1. Статическим (ручное редактирование)
# 2. Симлинком на /run/systemd/resolve/stub-resolv.conf (systemd-resolved)
# 3. Генерируемым NetworkManager
# 4. Генерируемым dhclient

# Проверяем, кто управляет resolv.conf
ls -la /etc/resolv.conf
# lrwxrwxrwx 1 root root 39 /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf
# ^ это systemd-resolved

# Типичный resolv.conf
nameserver 127.0.0.53          # systemd-resolved stub
options edns0 trust-ad
search cloudvan.local

# Или прямые DNS-серверы (без resolved)
nameserver 8.8.8.8
nameserver 1.1.1.1
options timeout:2 attempts:3 rotate
# Ключевые опции resolv.conf:
# timeout:N     — таймаут ожидания ответа (секунды, по умолчанию 5)
# attempts:N    — количество попыток (по умолчанию 2)
# rotate        — round-robin между nameservers
# ndots:N       — количество точек в имени для абсолютного запроса
#                 (по умолчанию 1; в Kubernetes обычно ndots:5)
# edns0         — включает Extension DNS (поддержка ответов > 512 байт)
# trust-ad      — доверять флагу Authenticated Data от DNS-сервера

На VM КлаудВана мы нашли 4 разных конфигурации resolv.conf. Первый шаг — стандартизация: все VM переведены на systemd-resolved с единообразной конфигурацией.

systemd-resolved: правильная настройка

systemd-resolved — современный DNS-резолвер Linux, встроенный в systemd. Он обеспечивает кеширование, split DNS, DNSSEC валидацию и DNS over TLS. Но его нужно правильно настроить:

# /etc/systemd/resolved.conf — основная конфигурация
[Resolve]
# Основные DNS-серверы (CloudFlare + Google)
DNS=1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google

# Fallback при недоступности основных
FallbackDNS=77.88.8.8 77.88.8.1

# Домены для search — добавляются к коротким именам
Domains=cloudvan.local

# DNS over TLS — шифрование DNS-запросов
DNSOverTLS=opportunistic
# opportunistic — используем TLS если доступен, fallback на plain
# yes — только TLS, plain запрещён
# no — отключено

# DNSSEC — проверка подлинности ответов
DNSSEC=allow-downgrade

# Кеширование
Cache=yes
CacheFromLocalhost=no

# Multicast DNS (для .local домена)
MulticastDNS=resolve
# Проверяем статус resolved
resolvectl status

# Global
#   Protocols: +LLMNR +mDNS +DNSOverTLS DNSSEC=allow-downgrade
#   resolv.conf mode: stub
#   Current DNS Server: 1.1.1.1#cloudflare-dns.com
#   DNS Servers: 1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google
#   Fallback DNS Servers: 77.88.8.8 77.88.8.1

# Статистика кеша
resolvectl statistics

# Current Cache Size: 247
#   Cache Hits: 18432
#   Cache Misses: 3891
#   Cache Hit Rate: 82.6%   ← хороший показатель

# Очистка кеша
resolvectl flush-caches
# Важно: resolv.conf должен быть симлинком на stub
ls -la /etc/resolv.conf
# Если нет — создаём правильный симлинк
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

# stub-resolv.conf указывает на 127.0.0.53 — локальный resolved
# Альтернатива: /run/systemd/resolve/resolv.conf — прямые серверы
# (для приложений, которые не работают через 127.0.0.53)

systemctl restart systemd-resolved

После стандартизации на systemd-resolved cache hit rate вырос с 0% (не было кеширования) до 82% — нагрузка на upstream DNS упала в 5 раз.

DNS-кеширование: dnsmasq и unbound для продакшена

systemd-resolved подходит для отдельных VM, но для DNS-серверов, обслуживающих сотни клиентов, нужны специализированные решения. Мы развернули два уровня:

# Уровень 1: dnsmasq — лёгкий кеширующий DNS для каждой подсети
apt install -y dnsmasq

# /etc/dnsmasq.conf
# Слушаем на внутреннем интерфейсе
listen-address=10.0.0.1
bind-interfaces

# Размер кеша
cache-size=10000

# Минимальный TTL — не кешируем меньше 60 секунд
min-cache-ttl=60

# Негативный кеш
neg-ttl=60

# Upstream DNS-серверы
server=1.1.1.1
server=8.8.8.8

# Не читаем /etc/resolv.conf (используем свои upstream)
no-resolv

# Логирование запросов (для отладки)
log-queries
log-facility=/var/log/dnsmasq.log

# Локальные записи
address=/internal.cloudvan.local/10.0.0.100

systemctl enable dnsmasq
systemctl start dnsmasq
# Уровень 2: unbound — рекурсивный резолвер для всей инфраструктуры
apt install -y unbound

# /etc/unbound/unbound.conf
server:
    interface: 10.0.0.2
    access-control: 10.0.0.0/8 allow
    access-control: 127.0.0.0/8 allow
    
    # Размер кешей
    msg-cache-size: 128m
    rrset-cache-size: 256m
    key-cache-size: 64m
    
    # TTL ограничения
    cache-min-ttl: 60
    cache-max-ttl: 86400
    
    # Prefetch — обновление популярных записей до истечения TTL
    prefetch: yes
    prefetch-key: yes
    
    # Количество потоков = количество ядер
    num-threads: 4
    msg-cache-slabs: 4
    rrset-cache-slabs: 4
    key-cache-slabs: 4
    
    # DNSSEC
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    val-clean-additional: yes
    
    # DNS over TLS к upstream
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

forward-zone:
    name: "."
    forward-tls-upstream: yes
    forward-addr: 1.1.1.1@853#cloudflare-dns.com
    forward-addr: 8.8.8.8@853#dns.google

unbound-control-setup
systemctl enable unbound
systemctl start unbound
# Проверяем работу unbound
unbound-control stats_noreset | grep total.num
# total.num.queries=184532
# total.num.cachehits=158291
# total.num.cachemiss=26241
# Cache hit rate: 85.8%

# Проверяем DNSSEC
dig @10.0.0.2 cloudflare.com +dnssec
# flags: qr rd ra ad  ← ad = Authenticated Data, DNSSEC валидирован

Архитектура DNS для КлаудВан: клиентские VM → dnsmasq (на каждой подсети) → unbound (2 сервера с failover) → upstream DNS over TLS. Три уровня кеширования, каждый уровень снижает нагрузку на следующий.

DNS в контейнерах: Docker и CoreDNS в Kubernetes

Контейнерная среда добавляет ещё один уровень сложности. Docker и Kubernetes используют собственные DNS-механизмы:

# Docker DNS — встроенный resolver на 127.0.0.11
# Каждый контейнер получает свой resolv.conf
docker exec container1 cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0

# Docker resolver обрабатывает:
# 1. Имена контейнеров в одной сети (container1 → 172.18.0.2)
# 2. Имена сервисов docker-compose (api → 172.18.0.3)
# 3. Всё остальное → forwarded на DNS хоста

# Кастомный DNS для Docker:
# /etc/docker/daemon.json
{
    "dns": ["10.0.0.2", "10.0.0.3"],
    "dns-search": ["cloudvan.local"],
    "dns-opts": ["timeout:2", "attempts:3"]
}
# CoreDNS в Kubernetes — центральный DNS для кластера
# ConfigMap с конфигурацией CoreDNS
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        
        # Kubernetes service discovery
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        
        # Кеширование — ключевая настройка!
        cache 300 {
            success 9984 300  # позитивный кеш: 300 секунд
            denial 9984 60    # негативный кеш: 60 секунд
            prefetch 10 60s 10%  # prefetch при 10+ запросах
        }
        
        # Forward внешних запросов на unbound
        forward . 10.0.0.2 10.0.0.3 {
            max_concurrent 1000
            policy round_robin
            health_check 5s
        }
        
        # Prometheus метрики
        prometheus :9153
        
        # Логи ошибок
        log . {
            class error
        }
        
        loop
        reload
        loadbalance
    }
# ndots:5 в Kubernetes — источник проблем
# Pod resolv.conf по умолчанию:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

# При ndots:5 запрос "api.external.com" (3 точки < 5)
# сначала пробует:
#   api.external.com.default.svc.cluster.local  → NXDOMAIN
#   api.external.com.svc.cluster.local          → NXDOMAIN
#   api.external.com.cluster.local               → NXDOMAIN
#   api.external.com.                             → успех!
# 4 DNS-запроса вместо 1!

# Решение: используем FQDN с точкой на конце
# или уменьшаем ndots в pod spec:
apiVersion: v1
kind: Pod
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

Снижение ndots с 5 до 2 и включение кеширования в CoreDNS уменьшило DNS-запросы в кластере на 70%. CoreDNS перестал падать под нагрузкой.

Диагностика: dig, drill и resolvectl

Когда DNS не работает, нужно уметь быстро найти проблему. Вот инструментарий, который мы используем на каждом инциденте:

# dig — основной инструмент диагностики DNS
# Простой запрос
dig example.com

# Запрос к конкретному серверу
dig @1.1.1.1 example.com

# Запрос конкретного типа записи
dig example.com MX
dig example.com AAAA
dig example.com TXT

# Трассировка от корневых серверов — показывает всю цепочку делегирования
dig +trace example.com

# Краткий вывод — только ответ
dig +short example.com

# Полный вывод с TTL
dig +nocmd +noall +answer +ttlid example.com
# example.com.  247  IN  A  93.184.216.34
# ^ TTL 247 секунд — столько запись будет в кеше
# resolvectl — диагностика systemd-resolved
# Запрос через resolved (учитывает все настройки системы)
resolvectl query example.com

# Статус всех интерфейсов
resolvectl status

# Статистика кеша
resolvectl statistics

# Мониторинг DNS-запросов в реальном времени
resolvectl monitor
# Покажет каждый DNS-запрос от приложений — очень полезно для отладки
# Сравнение: что использует приложение vs что показывает dig

# getent использует nsswitch.conf (как реальное приложение)
getent hosts example.com
# 93.184.216.34   example.com

# dig идёт напрямую в DNS (может показать другой результат!)
dig +short example.com
# 93.184.216.34

# Если результаты отличаются — проблема в /etc/hosts или nsswitch.conf

# strace — увидеть, куда реально ходит приложение
strace -e trace=network curl -s example.com 2>&1 | grep connect
# connect(3, {sa_family=AF_INET, sin_addr=inet_addr("127.0.0.53"), sin_port=htons(53)}, ...)
# ^ curl ходит на 127.0.0.53 (systemd-resolved)

# tcpdump — перехват DNS-трафика
sudo tcpdump -i any port 53 -n -l
# 10:23:45 IP 10.0.0.5.43210 > 1.1.1.1.53: 12345+ A? example.com. (28)
# Типичные проблемы и диагностика

# Проблема 1: DNS-запросы идут мимо кеша
tcpdump -i any port 53 -c 20 -n
# Если видим запросы к upstream на каждый запрос → кеш не работает
resolvectl statistics  # Cache Hits должны расти

# Проблема 2: resolv.conf перезаписывается
# Узнаём, кто управляет файлом
ls -la /etc/resolv.conf
# Если не симлинк → кто-то перезаписывает напрямую
journalctl -u systemd-resolved | grep -i resolv
journalctl -u NetworkManager | grep -i dns

# Проблема 3: негативный кеш (NXDOMAIN) мешает после создания домена
# Очистка всех уровней кеша
resolvectl flush-caches           # systemd-resolved
sudo systemctl restart dnsmasq     # dnsmasq
unbound-control flush example.com  # unbound (точечно)
sudo nscd -i hosts                 # nscd (если используется)

DNS over TLS/HTTPS и EDNS0

Классический DNS передаёт запросы открытым текстом по UDP:53. Это позволяет провайдеру и man-in-the-middle видеть все DNS-запросы пользователя. Для КлаудВан, предоставляющего облачные сервисы, это неприемлемо.

# DNS over TLS (DoT) — порт 853
# Уже настроен в systemd-resolved:
# DNSOverTLS=opportunistic

# Проверяем, что DoT работает
resolvectl status | grep -i tls
# Protocols: +DNSOverTLS

# Мониторим трафик — на порту 53 не должно быть запросов
tcpdump -i eth0 port 53 -c 5 -n
# Если пусто → всё идёт через TLS на порт 853
tcpdump -i eth0 port 853 -c 5 -n
# Должны видеть TLS-соединения
# DNS over HTTPS (DoH) — через HTTPS порт 443
# Для клиентов, у которых порт 853 заблокирован фаерволом

# Установка dnscrypt-proxy для DoH
apt install -y dnscrypt-proxy

# /etc/dnscrypt-proxy/dnscrypt-proxy.toml
listen_addresses = ['127.0.0.53:53']
server_names = ['cloudflare', 'google']

[sources]
  [sources.public-resolvers]
    urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md']
    cache_file = 'public-resolvers.md'
    minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'

[anonymized_dns]
  routes = [
    { server_name='cloudflare', via=['sdns://...'] }
  ]
# EDNS0 — Extension DNS
# Позволяет DNS-ответы больше 512 байт (лимит классического DNS)
# Необходим для DNSSEC, больших TXT-записей, IPv6

# Проверяем поддержку EDNS0
dig +edns=0 +bufsize=4096 example.com
# ;; OPT PSEUDOSECTION:
# ; EDNS: version: 0, flags:; udp: 4096

# TCP fallback — если ответ не помещается в UDP даже с EDNS
# DNS автоматически переключается на TCP
dig +tcp example.com
# ;; ->>HEADER<<- ... , QUERY: 1, ANSWER: 1
# ;; Query time: 15 msec  ← TCP чуть медленнее, но надёжнее

# Фаервол должен разрешать DNS по TCP!
# iptables -A INPUT -p tcp --dport 53 -j ACCEPT
# iptables -A INPUT -p udp --dport 53 -j ACCEPT

Мы стандартизировали DNS over TLS для всех VM КлаудВан в режиме opportunistic — если upstream поддерживает TLS, используем его, иначе fallback на plain DNS. Для параноидных клиентов предложили strict режим с dnscrypt-proxy.

Итоги и архитектура DNS для КлаудВан

После двух недель работы DNS-инфраструктура КлаудВан стала стабильной и предсказуемой:

ПараметрДоПосле
DNS-сбои в неделю15-20 инцидентов0
Среднее время DNS-резолвинга45 мс2 мс (из кеша)
Cache hit rate0% (нет кеша)85%
CoreDNS перезапуски3-5 в день0
DNS-трафик к upstream50K qps7.5K qps
Шифрование DNS0%100% (DoT)

Финальная архитектура DNS КлаудВан:

  1. VM: systemd-resolved с кешем → dnsmasq на подсети
  2. Подсеть: dnsmasq → unbound (2 сервера)
  3. Unbound: рекурсивный резолвер + DNS over TLS → upstream (CloudFlare + Google)
  4. Kubernetes: CoreDNS с кешем и prefetch → unbound

Рекомендации от itfresh.ru для любой Linux-инфраструктуры: стандартизируйте DNS-стек на всех серверах, включайте кеширование на каждом уровне, мониторьте cache hit rate и количество NXDOMAIN через Prometheus, и всегда тестируйте DNS через getent (как приложение), а не через dig (прямой DNS).

Часто задаваемые вопросы

dig обращается напрямую к DNS-серверу, минуя nsswitch.conf и /etc/hosts. Приложение (curl, ping, wget) сначала проверяет /etc/hosts, потом идёт в DNS. Используйте getent hosts domain.com — эта команда работает так же, как приложение. Если getent не резолвит, а dig резолвит — проблема в nsswitch.conf или /etc/hosts.
dnsmasq — лёгкий форвардер с кешем, идеален для отдельных серверов и подсетей. Потребляет минимум ресурсов. unbound — полноценный рекурсивный резолвер с DNSSEC и продвинутым кешем, подходит для центральных DNS-серверов инфраструктуры. Для малых проектов хватит dnsmasq, для облачных провайдеров и крупных кластеров — unbound.
Три шага: включите кеширование в CoreDNS (cache 300), уменьшите ndots с 5 до 2 в dnsConfig подов, используйте FQDN с точкой на конце (api.external.com.) в конфигурации приложений. Это снижает DNS-трафик на 60-80%.
Первый запрос — да, на 10-20 мс из-за TLS handshake. Но resolved и unbound держат постоянное TLS-соединение, поэтому последующие запросы идут без дополнительных задержек. С учётом кеширования реальное влияние на пользователей — менее 1 мс в среднем.
Это негативный кеш (NXDOMAIN). DNS-серверы кешируют ответ «домен не существует» на время neg-ttl (обычно 60-3600 секунд). Очистите кеш на всех уровнях: resolvectl flush-caches, sudo systemctl restart dnsmasq, unbound-control flush domain.com. Для предотвращения — уменьшите neg-ttl в dnsmasq и cache-min-ttl в unbound.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#dns linux#resolv.conf настройка#systemd-resolved#dns over tls#dnsmasq unbound#nsswitch.conf#coredns kubernetes#dig drill resolvectl
Комментарии 0

Оставить комментарий

загрузка...