Модуль: Сети и протоколы · Уровень: Middle+/Senior

TL;DR#

  • DNS — распределённая иерархическая система имён; резолвер рекурсивно идёт от root → TLD → authoritative, отдавая записи с TTL.
  • Типы записей: A/AAAA (IP), CNAME (алиас), MX (почта), TXT (метаданные/SPF), NS, SOA, SRV (сервис+порт), PTR (обратный), CAA.
  • Кэширование на каждом уровне по TTL — отсюда главные продовые боли: устаревшие записи, “залипший” резолв при деплое/failover.
  • В Go два резолвера: cgo (системный) и чистый Go; выбор влияет на поведение /etc/hosts, /etc/resolv.conf, кэширование (которого в Go-нет по умолчанию).
  • Прод-грабли: отсутствие кэша в Go → DNS на каждый запрос, негативное кэширование, TTL vs балансировка, DNS-таймауты как причина “медленных” сервисов.

Теория#

Иерархия и резолвинг#

                root (.)
                 |
       TLD (.com, .org, .ru)
                 |
   authoritative (example.com NS)
  • Recursive resolver (обычно у провайдера / 8.8.8.8 / внутренний) делает работу за клиента: спрашивает root → получает NS для TLD → спрашивает TLD → получает authoritative NS → получает финальный ответ.
  • Stub resolver — это сам клиент (getaddrinfo/Go), который шлёт запрос рекурсивному резолверу.
  • Транспорт: UDP/53 (дефолт), TCP/53 при truncation (флаг TC) или zone transfer. Современное: DoT (DNS over TLS, 853), DoH (DNS over HTTPS, 443).

Типы записей#

ТипНазначение
Ahostname → IPv4
AAAAhostname → IPv6
CNAMEалиас на другое имя (нельзя на apex-домене вместе с другими записями)
MXпочтовые серверы (+приоритет)
TXTпроизвольный текст: SPF, DKIM, верификации
NSделегирование зоны на name servers
SOAпараметры зоны (serial, refresh, TTL негатива)
SRV_service._proto → host+port (service discovery)
PTRобратный резолв IP → имя
CAAкакие CA могут выдавать сертификаты для домена

TTL и кэширование#

  • Каждая запись несёт TTL — сколько секунд её можно кэшировать.
  • Кэш на всех уровнях: stub, рекурсивный резолвер, ОС, иногда приложение.
  • Negative caching: NXDOMAIN тоже кэшируется (TTL берётся из SOA minimum) — поэтому только что созданная запись может “не находиться” какое-то время.
  • Trade-off TTL: низкий TTL → быстрый failover/смена IP, но больше нагрузки и латентности; высокий TTL → меньше запросов, но медленная пропагация изменений. Перед миграцией TTL заранее снижают.

CNAME-нюансы#

  • CNAME — алиас, требует доп. резолва на целевое имя.
  • На apex (корне зоны, example.com) CNAME запрещён стандартом (конфликт с SOA/NS) → провайдеры предлагают ALIAS/ANAME/CNAME flattening.
  • Цепочки CNAME увеличивают латентность резолва.

DNS в Go#

Go имеет два резолвера:

  1. cgo-based (системный) — вызывает getaddrinfo, уважает nsswitch, mDNS, сложные конфиги. Используется по умолчанию на некоторых платформах.
  2. pure Go resolver — сам парсит /etc/resolv.conf и шлёт DNS-запросы; кроссплатформенный, без cgo.

Выбор:

// Принудительно чистый Go
net.DefaultResolver.PreferGo = true
// или через env: GODEBUG=netdns=go (или netdns=cgo)
  • Go не кэширует DNS в приложении (полагается на ОС/резолвер). Без локального кэширующего резолвера каждый net.Dial по имени = DNS-запрос.
  • Кастомный резолвер:
r := &net.Resolver{
    PreferGo: true,
    Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        d := net.Dialer{Timeout: 2 * time.Second}
        return d.DialContext(ctx, network, "1.1.1.1:53") // свой DNS
    },
}
ips, err := r.LookupHost(ctx, "example.com")
  • net.LookupHost/LookupIP/LookupSRV/LookupTXT/LookupCNAME/LookupMX — типизированные запросы.
  • Контроль через Dialer.Resolver и Resolver.LookupIPAddr (контекст-aware, с таймаутом).

Подводные камни / gotchas#

  • Нет DNS-кэша в Go: высоконагруженный сервис, дёргающий хост по имени, генерит DNS-запрос на каждое соединение. Решение: локальный кэширующий резолвер (systemd-resolved, dnsmasq, CoreDNS/NodeLocal в k8s) или кэш в приложении.
  • Залипший резолв при failover: приложение/пул держит старый IP (закэшированный или удерживаемый keep-alive соединением) после смены DNS. HTTP-пул переиспользует соединение со старым IP, игнорируя новый DNS. Нужны ограничение времени жизни соединений и/или повторный резолв.
  • TTL=0 / низкий TTL не помогает, если приложение само кэширует или держит соединения. DNS-балансировка слаба именно из-за кэшей.
  • NXDOMAIN кэшируется (negative caching) — новая запись “не видна” до истечения negative TTL.
  • CNAME на apex не работает — типичная ошибка конфигурации.
  • search-домены в /etc/resolv.conf (особенно в Kubernetes ndots:5) → каждый внешний запрос порождает несколько лишних lookup’ов (перебор search-суффиксов) → задержки и нагрузка. Классическая k8s-проблема, лечится ndots или FQDN с точкой на конце.
  • cgo vs pure-Go расхождения: разный парсинг /etc/hosts, поведение при нескольких A-записях, поддержка mDNS — поведение может отличаться между средами.
  • DNS-таймауты маскируются под “медленный сервис”: блокирующий резолв с дефолтным таймауту добавляет секунды к запросу. Всегда контекст с таймаутом на резолв.
  • IPv4/IPv6 (AAAA): лишние AAAA-запросы и неудачные IPv6-подключения добавляют латентность; Happy Eyeballs (Go умеет) смягчает.

Вопросы на собеседовании#

В: Опиши путь рекурсивного резолва example.com. О: Stub-резолвер шлёт запрос рекурсивному резолверу. Тот (если нет в кэше) спрашивает root-сервер → получает NS для .com → спрашивает TLD-сервер → получает authoritative NS example.com → спрашивает его → получает A-запись. Результат кэшируется по TTL на каждом уровне.

В: Разница между A, CNAME и ALIAS? О: A — прямое отображение имя→IP. CNAME — алиас на другое имя (требует доп. резолва), запрещён на apex. ALIAS/ANAME — провайдерское решение для apex: ведёт себя как CNAME, но резолвится на стороне DNS-провайдера и отдаётся как A.

В: Почему изменение DNS “не применяется сразу”? О: Кэширование по TTL на всех уровнях (резолверы, ОС, приложение) + negative caching для несуществовавших записей. Старые значения живут до истечения TTL. Перед миграцией TTL снижают заранее.

В: Кэширует ли Go DNS? Чем это грозит? О: Нет, стандартная библиотека не кэширует — полагается на системный/рекурсивный резолвер. Под нагрузкой без локального кэша это DNS-запрос на каждое новое соединение → нагрузка и латентность. Решение: кэширующий резолвер на хосте или кэш в приложении.

В: Сервис не переключился на новый IP после failover, хотя DNS обновлён. Почему? О: Либо закэшированный старый IP, либо (чаще) HTTP-пул держит keep-alive соединение со старым IP и не делает новый резолв. Лечится IdleConnTimeout, ограничением жизни соединений, форсированным реконнектом или резолвом перед каждым соединением.

В: Что такое проблема ndots в Kubernetes? О: ndots:5 в /etc/resolv.conf пода заставляет резолвер сначала перебирать search-домены для имён с <5 точками → много лишних DNS-запросов на каждый внешний хост. Лечится снижением ndots, использованием FQDN с завершающей точкой, NodeLocal DNS-кэшем.

В: Когда DNS использует TCP вместо UDP? О: Когда ответ не влезает в лимит UDP (флаг TC — truncated), при zone transfer (AXFR/IXFR), при DNSSEC с большими ответами. Клиент видит TC и переотправляет запрос по TCP/53.

В: Чем отличаются cgo и pure-Go резолверы и как выбрать? О: cgo (getaddrinfo) уважает nsswitch/mDNS/сложные конфиги, но требует cgo и блокирует поток в системном вызове. Pure-Go сам парсит resolv.conf, кроссплатформенный, контекст-aware. Выбор: GODEBUG=netdns=go|cgo или Resolver.PreferGo. Для контейнеров/статической линковки обычно pure-Go.

На что копают на senior+#

  • Взаимодействие DNS-TTL и connection pooling: почему DNS-балансировка ненадёжна и как делать клиентскую балансировку/повторный резолв.
  • NodeLocal DNSCache, CoreDNS в k8s, autopath, кэш negative-ответов и их влияние на latency tail.
  • DoT/DoH: приватность, обход цензуры, как это меняет операционную видимость и debugging.
  • DNSSEC: цепочка подписей (RRSIG/DNSKEY/DS), что он защищает (целостность) и не защищает (конфиденциальность).
  • Happy Eyeballs (RFC 8305) в Go: параллельные IPv4/IPv6 попытки, тюнинг fallback delay.
  • Service discovery через SRV-записи vs Consul/etcd; почему DNS-based discovery ограничен (нет весов/health в стандартном DNS).
  • Диагностика: dig +trace, проверка авторитетных серверов, TTL, расхождения между резолверами; почему nslookup врёт про кэш приложения.