Подробные статьи о технологиях, определяющих будущее.

Настройка DNS по-прежнему ломает продакшн

DNS кажется простым, пока не сломается. Как обновления ОС, resolv.conf, mDNS и TLD .internal вызывают простои в продакшне, которых вы не ждёте.

Письма, ошибочно направленные в неправильные пневмотрубки в сортировочном помещении

Последний релиз macOS от Apple сломал пользовательские настройки DNS у тысяч разработчиков. Конкретно: система стала перехватывать запросы к TLD .internal, которым многие компании пользуются для ресурсов приватной сети, и отправлять их в mDNS (multicast DNS) вместо настроенного DNS-резолвера. Сервисы, которые годами работали нормально, внезапно стали недоступны. Решение было неочевидным. Сообщения об ошибках ничего не объясняли. А как обычно бывает с проблемами DNS, симптомы выглядели как что угодно, кроме DNS.

Это история о конфигурации DNS. С ней сталкивается каждый разработчик, мало кто понимает её по-настоящему, и все её недооценивают, пока она не сломает что-нибудь в продакшне в два часа ночи.

Инцидент с TLD .internal

Многие организации используют .internal как приватный TLD для внутренних сервисов: api.internal, db.staging.internal, grafana.internal. Выбор казался безопасным: это не публичный TLD, он ясно говорит «это внутреннее», и так его используют уже десятилетия.

Потом новая версия ОС от Apple решила, что .internal должен обрабатываться через mDNS, тот же протокол, который резолвит myprinter.local в домашней сети. Когда ваш Mac пытается зарезолвить api.internal, вместо запроса к настроенному DNS-серверу он рассылает mDNS-запрос в локальную сеть. Ваш DNS-сервер этот запрос не видит. Ответ либо отсутствует (таймаут), либо приходит неверный адрес, если какое-то устройство в локальной сети случайно откликается на это имя.

Симптомы: curl api.internal висит 5 секунд и падает. SSH-подключения к внутренним хостам отваливаются по таймауту. Пользователи VPN не могут достучаться до внутренних ресурсов. Docker-контейнеры не могут резолвить имена сервисов. А поскольку сбой выглядит как общий таймаут соединения, большинство разработчиков начинают копать VPN, файрвол, приложение, что угодно, кроме DNS.

Как на самом деле работает разрешение DNS в современных системах

Представление «DNS это просто»: ваш компьютер спрашивает у DNS-сервера IP, сервер отвечает. Оно уже много лет не соответствует действительности. В современных системах есть конвейер разрешения из нескольких этапов, и каждый из них может перехватить, изменить или перенаправить запрос.

DNS resolution pipeline on a typical macOS/Linux system:
Application calls getaddrinfo("api.internal")
↓
1. /etc/hosts — checked first. Static overrides.
↓
2. NSSwitch / resolver configuration
- Linux: /etc/nsswitch.conf controls lookup order
- macOS: /etc/resolver/* for per-domain DNS servers
↓
3. System resolver (systemd-resolved, mDNSResponder)
- May intercept .local, .internal, or other special TLDs
- May apply DNSSEC validation
- May cache responses
↓
4. DNS server (from DHCP, VPN, or manual config)
- Queries sent to configured upstream resolver
- May go through a corporate DNS proxy
↓
5. Recursive resolver (8.8.8.8, 1.1.1.1, corporate)
- Walks the DNS hierarchy: root → TLD → authoritative
↓
Response flows back up the chain

На каждом этапе что-то может пойти не так. Системный резолвер может перехватить запрос до того, как он дойдёт до настроенного DNS-сервера. VPN может переопределить настройки DNS. DHCP может передать DNS-серверы, которые окажутся приоритетнее ваших ручных настроек. А кэширование на каждом уровне означает, что исправление может подействовать не сразу.

Ложь /etc/resolv.conf

В Linux /etc/resolv.conf исторически был единственным источником правды о настройках DNS. Прописали nameserver, система его использует. Просто.

Современные дистрибутивы Linux сильно всё усложнили. Если вы используете systemd-resolved (его по умолчанию ставят Ubuntu, Fedora и большинство дистрибутивов на systemd), то /etc/resolv.conf это симлинк на заглушку-резолвер по адресу 127.0.0.53. Реальная конфигурация DNS хранится в состоянии systemd-resolved и управляется через resolvectl. Если редактировать /etc/resolv.conf напрямую, изменения либо перезапишутся при следующей смене сети, либо сломают заглушку.

# What you THINK your DNS config is:
$ cat /etc/resolv.conf
nameserver 127.0.0.53  # This is systemd-resolved's stub
# What your DNS config ACTUALLY is:
$ resolvectl status
Global:
DNS Servers: 8.8.8.8 8.8.4.4
DNS Domain: ~.
Link 2 (eth0):
DNS Servers: 10.0.0.1  # From DHCP — this takes precedence
DNS Domain: corp.internal
Link 5 (wg0):  # WireGuard VPN
DNS Servers: 10.100.0.1
DNS Domain: ~internal  # VPN claims the .internal domain
# Three different DNS servers, each handling different domains.
# /etc/resolv.conf shows none of this.

Docker добавляет ещё один уровень. Контейнеры Docker получают собственный /etc/resolv.conf, обычно скопированный из конфигурации хоста при запуске. Если DNS хоста меняется (подключился VPN, переключилась сеть), работающие контейнеры продолжают использовать старую конфигурацию, пока их не перезапустят. Это частая причина багов вида «после перезапуска работает, а через какое-то время ломается».

Split DNS и конфликты с VPN

Split DNS, когда для разных доменов используются разные DNS-серверы, это стандартная практика в корпоративных сетях. VPN направляет запросы к *.corp.internal на корпоративный DNS-сервер, а публичные запросы идут через обычный резолвер. Это отлично работает при правильной настройке и ломается самым запутанным образом, когда настройка неверна.

Типичные сценарии сбоев:

  • VPN передаёт DNS-серверы как глобальные по умолчанию. Некоторые VPN-клиенты назначают DNS-сервер VPN резолвером по умолчанию для всех запросов, а не только для внутренних. Теперь все ваши DNS-запросы, включая обычный серфинг, идут через корпоративный DNS. Это медленно (корпоративный DNS находится в другом дата-центре) и создаёт проблемы с приватностью.
  • Конфликты порядка DNS-серверов. Когда настроено несколько DNS-серверов, система опрашивает их по очереди. Если первый сервер медленный или недоступен (VPN отключён, а конфигурация DNS осталась), каждый DNS-запрос ждёт таймаута, прежде чем попробовать второй. Из-за этого каждое соединение получает задержку в 5 секунд.
  • Search-домены тихо дописываются. Search-домен corp.internal означает, что запрос к api также попробует api.corp.internal. Это удобно (пишешь ssh api вместо ssh api.corp.internal), пока не перестаёт быть удобным, когда короткое имя хоста совпадает с именем внутреннего сервиса и запросы уходят на неверный IP.
  • mDNS перехватывает внутренние домены. Как показал инцидент с .internal, системный резолвер может перехватить запросы для определённых TLD раньше, чем они дойдут до любого настроенного DNS-сервера. Это особенно коварно, потому что перехват происходит незаметно: ни сообщения об ошибке, только таймаут или неверный ответ.

Отладка DNS: инструменты

Когда DNS ломается, нужны инструменты, которые покажут, что именно происходит на каждом этапе конвейера разрешения.

# 1. Check what your system actually resolves:
$ dig api.internal
# Shows: query sent to which server, response received
# The 'SERVER' line tells you which DNS server answered
# 2. Query a specific DNS server directly:
$ dig @10.100.0.1 api.internal
# Bypasses the system resolver — goes directly to the specified server
# If this works but 'dig api.internal' doesn't, the problem is
# in the system resolver, not the DNS server
# 3. Check systemd-resolved state (Linux):
$ resolvectl query api.internal
# Shows which interface/server handled the query
# 4. Check macOS DNS routing:
$ scutil --dns
# Shows per-domain DNS configuration on macOS
# Look for your .internal domain — is it being routed correctly?
# 5. Monitor DNS queries in real time:
$ sudo tcpdump -i any port 53
# See actual DNS packets. If no packets go out for your query,
# the system resolver is intercepting it locally.
# 6. Flush DNS cache (when fixing config):
$ sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder  # macOS
$ resolvectl flush-caches  # Linux with systemd-resolved

Самый важный диагностический шаг: запросить DNS-сервер напрямую командой dig @server hostname. Если так работает, а обычное разрешение нет, проблема между приложением и DNS-сервером: перехват системным резолвером, кэширование или маршрутизация. Если и прямые запросы падают, проблема в самом DNS-сервере или в сетевой доступности до него.

Выбор приватного TLD

Инцидент с .internal поднимает практический вопрос: какой TLD использовать для внутренних сервисов? Универсально безопасного ответа нет, но одни варианты лучше других.

  • .internal — RFC 6762 резервирует его для внутреннего использования, но обработка mDNS в macOS от Apple делает его проблемным. На Linux, вероятно, будет работать нормально.
  • .local — зарезервирован для mDNS (RFC 6762). Использование его для сервисов, которые разрешаются через DNS, будет конфликтовать с Bonjour/Avahi. Не используйте.
  • .corp, .home, .mail — не зарезервированы. ICANN может в любой момент назначить их публичными TLD, и это сломает внутреннее разрешение имён. Не используйте.
  • Поддомен домена, которым вы владеете. internal.yourcompany.com — самый безопасный вариант. Вы контролируете родительский домен, поэтому коллизий не будет. Минус: набирать его дольше.
  • .test, .example, .invalid, .localhost — зарезервированы RFC 6761 и гарантированно никогда не будут назначены публичными TLD. .test — самый чистый вариант для внутренних сервисов, которые не являются продакшном.

Скучный, но правильный ответ: используйте поддомен домена, которым владеете. api.internal.yourcompany.com однозначен, ни с чем не конфликтует и одинаково работает на любой ОС и любом DNS-резолвере. Имя длиннее, но проблемы с DNS в два часа ночи хуже, чем лишние несколько символов при наборе.

Уроки для продакшн-систем

Каждый сбой DNS учит одним и тем же урокам, и мы раз за разом переучиваем их заново.

  1. Мониторьте разрешение DNS, а не только DNS-серверы. То, что DNS-сервер здоров, не значит, что разрешение работает. Мониторьте весь путь: может ли приложение реально резолвить нужные ему имена? Синтетическая проверка, которая каждые 30 секунд резолвит критичные имена, поймает проблемы быстрее, чем мониторинг самого сервера.
  2. Ставьте короткий TTL для внутреннего DNS. Когда нужно поменять внутреннюю DNS-запись, длинный TTL означает, что в кэшах останутся устаревшие данные. TTL в 60 секунд для внутренних сервисов даёт быстрый failover при минимальной нагрузке на резолверы.
  3. Тестируйте обновления ОС на своей DNS-конфигурации. Поломка с .internal затронула только пользователей macOS. Проверяйте среду разработки после крупных обновлений ОС, потому что изменения в разрешении DNS редко попадают в release notes.
  4. Документируйте архитектуру DNS. Какие DNS-серверы обслуживают какие домены? Где точки разделения? Что происходит, когда VPN отключается? Эта документация понадобится первой во время инцидента и последней, что кто-либо напишет.
  5. Имейте запасной вариант. Если разрешение DNS для внутреннего сервиса падает, может ли приложение использовать захардкоженный IP? Это не изящно, но рабочий захардкоженный IP лучше имени DNS, которое не резолвится.

DNS один из тех фундаментальных сервисов, который невидим, когда работает, и катастрофичен, когда нет. Поломка с .internal напоминает, что конфигурация DNS хрупче, чем кажется: одно обновление ОС может изменить поведение разрешения для миллионов пользователей. Проектируйте системы, исходя из того, что DNS однажды сломается, и вас меньше удивит, когда это случится.