Знакомая ситуация. Сервер только что отвечал на пинги, обновлялся без вопросов, а через час ни одно имя не резолвится. Браузер на десктопе говорит, что сайт не найден, apt не может достучаться до репозиториев, а команда host при этом упрямо показывает правильный адрес. Парадокс выглядит необъяснимым ровно до того момента, пока не вспоминаешь, что на современном Linux именем занимается не один механизм, а как минимум два, и они не всегда согласны друг с другом.
В центре этой истории стоит systemd-resolved. Сервис задумывался как удобная прослойка между приложениями и реальными DNS-серверами, с кешем, поддержкой разных серверов на разных интерфейсах и валидацией DNSSEC. На бумаге всё стройно. На практике он соседствует со старым добрым файлом resolv.conf, к которому привыкли поколения администраторов, и вот на этом стыке рождается большинство ночных разборов. Стоит понять механику конфликта, и загадочные сбои превращаются в предсказуемую инженерную задачу с понятным решением.
Откуда вообще взялся конфликт и почему host работает, а программа нет
Исторически резолвинг в Linux был устроен примитивно и понятно. Существовал файл со списком серверов имён, библиотека читала его сверху вниз и спрашивала первый доступный сервер. Администратор правил файл руками и шёл дальше заниматься делами. Проблема вылезла, когда среды стали динамическими. Ноутбуки переключаются между сетями, облачные машины получают адреса по DHCP, туннельные подключения поднимают свои маршруты и хотят свои серверы имён. Статический файл перестал успевать за реальностью, и появились инструменты, которые переписывали его на лету по событиям сети.
systemd-resolved пошёл дальше и добавил собственный слой. Он поднимает локальный заглушечный резолвер на адресе 127.0.0.53 и порту 53, и идея в том, чтобы все приложения спрашивали именно его, а он уже принимал умные решения, куда переслать запрос дальше. Локальный заглушечный слушатель работает на 127.0.0.53 и используется приложениями, которые обрабатывают DNS-запросы напрямую. Здесь и зарыта первая мина. Часть программ ходит через системную библиотеку glibc и слушается настроек в nsswitch, а часть лезет напрямую в resolv.conf или вообще говорит с резолвером по своему протоколу. Если эти пути настроены по-разному, одни и те же имена начинают резолвиться по-разному.
Классический симптом выглядит именно так. Команда host отвечает мгновенно и корректно, потому что она обращается к серверу напрямую в обход системного слоя. А реальное приложение, тот же менеджер ключей или apt, идёт через resolv.conf к заглушке и получает отказ. Запросы приложения уходят на 127.0.0.53 через resolv.conf, тогда как host обходит systemd-resolved полностью. Вывод из этого один. Если host работает, а программа нет, проблема не в самом DNS-сервере, а в том, каким маршрутом до него идёт конкретный софт.
Что показывает resolv.conf и почему символическая ссылка решает всё
Первое, что нужно выяснить при разборе, это куда вообще указывает главный файл конфигурации резолвинга. От его содержимого зависит, попадёт приложение в заглушку systemd-resolved или пойдёт мимо неё.
# Смотрим, что собой представляет /etc/resolv.conf
ls -la /etc/resolv.conf
# И что внутри
cat /etc/resolv.conf
Здоровый вариант при работающем systemd-resolved это символическая ссылка на динамический файл заглушки. Внутри неё единственный сервер имён, и это адрес локальной заглушки.
# Ожидаемое содержимое при режиме stub
nameserver 127.0.0.53
options edns0 trust-ad
search .
Тонкость в том, что у systemd-resolved есть два разных режима, и они дают похожие на вид, но принципиально разные файлы. Режим stub направляет клиентов на заглушку 127.0.0.53, и тогда все запросы проходят через логику сервиса с кешем и пересылкой. Файл stub-resolv.conf указывает клиентским библиотекам отправлять запросы на 127.0.0.53, где systemd-resolved слушает и затем пересылает их дальше. Второй режим отдаёт клиентам напрямую те адреса серверов, которые сервис вычислил из конфигурации и DHCP. Этот вариант обходит шаг пересылки systemd-resolved, но вместе с ним обходит и всю его логику принятия решений о том, куда именно пересылать каждый запрос. Понимание, в каком из режимов живёт система, наполовину решает задачу диагностики.
Если ссылка сбита или файл стал обычным с чужим содержимым, восстановление режима stub делается одной командой. Важно понимать, что команда перезаписывает текущий файл, поэтому перед ней стоит сохранить копию.
# Бэкап на всякий случай
sudo cp /etc/resolv.conf /etc/resolv.conf.backup
# Возвращаем правильную символическую ссылку
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
Диагностика через resolvectl, или как посмотреть в голову резолверу
Прежде чем что-то менять, стоит выяснить, что сервис вообще думает о текущей конфигурации. Основной инструмент здесь это resolvectl, который показывает картину по каждому сетевому интерфейсу отдельно.
# Общий статус резолвинга
resolvectl status
# Подробно по всем интерфейсам
resolvectl status --all
# Какие серверы и домены используются
resolvectl dns
resolvectl domain
В выводе важны несколько строк. Поле с режимом resolv.conf прямо говорит, в каком из двух состояний система. Текущий сервер показывает, куда реально уходят запросы после прохождения заглушки. Состояние DNSSEC намекает на потенциальный источник загадочных отказов, о котором речь ниже. По каждому интерфейсу видно свой набор серверов, и именно здесь обнаруживается, что туннельное подключение или второй сетевой адаптер тянет резолвинг в свою сторону.
Дальше проверяется живой путь запроса. Если резолвинг через заглушку отвечает, а напрямую через внешний сервер нет, или наоборот, это сразу сужает круг подозреваемых.
# Запрос через системный механизм
resolvectl query example.com
# Запрос напрямую к заглушке
dig @127.0.0.53 example.com
# Запрос мимо заглушки, к внешнему серверу
dig @1.1.1.1 example.com
Расхождение в ответах этих трёх команд это и есть диагноз. Когда внешний сервер отвечает, а заглушка молчит или возвращает ошибку, проблема внутри systemd-resolved. Когда заглушка отвечает, а приложение всё равно не видит имён, копать нужно в сторону того, каким механизмом резолвинга пользуется конкретная программа.
Битва за порт 53, главный источник отказов на серверах
Самая частая причина, по которой systemd-resolved ломает резолвинг на сервере, это конфликт за порт. Заглушка хочет занять адрес 127.0.0.53 и порт 53, но если на машине стоит свой кеширующий резолвер вроде dnsmasq или bind, он тоже метит в порт 53. Два процесса не могут слушать один порт, один из них падает или не стартует, и резолвинг рассыпается. Поскольку обе службы пытаются занять порт 53, они конфликтуют, из-за чего разрешение имён перестаёт работать.
Проверяется это одной командой, которая показывает, кто именно держит порт.
# Кто слушает порт 53
sudo ss -ltnup | grep ':53'
# Альтернатива через netstat
sudo netstat -tulpn | grep ':53'
Типичная картина конфликта выглядит так. Один процесс сидит на 127.0.0.1 порт 53, другой на 127.0.0.53 порт 53, и приложения, идущие через resolv.conf, попадают не туда, куда ожидают.
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 1234/dnsmasq
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 5678/systemd-resolv
Решений тут два, и выбор зависит от того, кто на машине главный по DNS. Если основным резолвером должен быть dnsmasq или bind, заглушку systemd-resolved нужно отключить, освободив порт. Делается это правкой конфигурации сервиса.
sudo nano /etc/systemd/resolved.conf
В секции Resolve выставляется отключение заглушки. Полезно сразу задать и серверы по умолчанию, чтобы сам сервис продолжал работать как клиент.
[Resolve]
DNS=1.1.1.1 8.8.8.8
DNSStubListener=no
После правки сервис перезапускается и проверяется, что порт освободился.
sudo systemctl restart systemd-resolved
sudo ss -ltnup | grep ':53'
Отключение заглушечного слушателя освобождает порт 53 для dnsmasq. Стоит учесть версию systemd, потому что опция отключения заглушки появилась не сразу. Версия 247 поддерживает опцию DNSStubListener no, а на версиях старше 232 приходится полагаться на другие методы вроде правки зависимостей служб.
Коварная история с DNSSEC, когда ломается ровно одна программа
Отдельного разговора заслуживает сценарий, который ставит в тупик даже опытных администраторов. Резолвинг работает почти везде, сайты открываются, apt обновляется, но одна конкретная программа упорно не может разрешить имя и выдаёт странную ошибку про недопустимый аргумент. Чаще всего жертвой становятся инструменты, которые запрашивают не обычные адресные записи, а служебные записи с цифровыми подписями DNSSEC.
Корень в том, что при определённых условиях systemd-resolved не умеет корректно отдавать некоторые типы записей и возвращает ошибку формата вместо нормального ответа. Записи RRSIG содержат цифровые подписи DNSSEC, и при определённых условиях systemd-resolved не поддерживает запросы этих записей. Программа, которая ждёт такую запись, получает формат-эррор, трактует его как фатальный сбой и отказывается работать, хотя само имя прекрасно резолвится через любой внешний сервер.
Проверить гипотезу можно прямым сравнением. Запрос проблемного типа записи к заглушке и к внешнему серверу даёт разные ответы, и это финальное доказательство.
# Запрос служебной записи через заглушку
host -t RRSIG example.com 127.0.0.53
# Тот же запрос напрямую к внешнему серверу
host -t RRSIG example.com 1.1.1.1
Если заглушка возвращает ошибку, а внешний сервер отвечает нормально, диагноз подтверждён. Временное лечение это отключение строгой проверки DNSSEC в конфигурации сервиса, после чего проблемная программа оживает.
[Resolve]
DNSSEC=no
Когда проще уйти на классическую схему, чем чинить надстройку
Иногда вывод по итогам диагностики неутешительный. На конкретной машине systemd-resolved приносит больше боли, чем пользы. Это типично для серверов под контейнерные платформы, для нод оркестраторов, для систем с несколькими туннельными подключениями и нестандартной маршрутизацией, где умная логика сервиса воюет с не менее умной логикой других компонентов. В таких случаях разумнее не латать надстройку, а вернуться к прямой и предсказуемой классической схеме, когда resolv.conf это обычный файл со списком серверов, и точка.
Переход делается аккуратно и в определённом порядке, иначе можно остаться вообще без резолвинга. Сначала сервис останавливается и снимается с автозапуска.
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo systemctl mask systemd-resolved
Затем убирается символическая ссылка и на её месте создаётся честный статический файл с реальными серверами имён, а не с адресом заглушки.
# Удаляем симлинк на динамический файл заглушки
sudo rm /etc/resolv.conf
# Создаём обычный статический файл
sudo tee /etc/resolv.conf > /dev/null << 'EOF'
nameserver 1.1.1.1
nameserver 8.8.8.8
options timeout:2 attempts:3
EOF
Остаётся одна засада, о которой забывают чаще всего. Менеджер сети при следующем событии может снова переписать файл по-своему. Чтобы этого не происходило, нужно либо настроить менеджер сети так, чтобы он сам управлял файлом по классической схеме, либо защитить файл от перезаписи. На системах с NetworkManager управление резолвингом переключается в его конфигурации.
# /etc/NetworkManager/conf.d/dns.conf
[main]
dns=default
После правки менеджер сети перезапускается, и он сам начинает писать в файл список серверов, полученных по DHCP или заданных вручную.
sudo systemctl restart NetworkManager
Есть и более жёсткий, но железобетонный способ зафиксировать содержимое файла, когда никакой менеджер не должен его трогать. Атрибут неизменяемости запрещает перезапись даже от имени суперпользователя до тех пор, пока атрибут не снят.
# Запрещаем любые изменения файла
sudo chattr +i /etc/resolv.conf
# Когда понадобится снова его править, атрибут снимается так
sudo chattr -i /etc/resolv.conf
Важная деталь для тех, кто использует resolvconf или openresolv для динамического управления файлом. После отказа от systemd-resolved нельзя оставлять в системе пакет-прослойку, который притворяется рабочим резолвером. Если systemd-resolved не используется, пакет systemd-resolvconf должен быть удалён, иначе он вызовет проблемы у сетевого софта, который ожидает рабочий бинарник resolvconf. Логика простая. Один менеджер файла, одна точка истины, никаких посредников, которые молча перехватывают управление.
Как закрепить результат и не вернуться к тому же сбою через месяц
Любая из описанных правок бесполезна, если после перезагрузки система откатится в исходное состояние. Поэтому финальный шаг любого сценария это проверка устойчивости. После перезагрузки нужно убедиться, что файл резолвинга выглядит так, как задумано, что нужный процесс держит порт 53, а лишний не запускается, и что реальные приложения, а не только тестовые утилиты, видят имена.
# После reboot прогоняем контрольный набор
ls -la /etc/resolv.conf
sudo ss -ltnup | grep ':53'
resolvectl status 2>/dev/null || echo "resolved отключён, это ожидаемо"
# Финальная проверка реального резолвинга
getent hosts example.com
curl -sI https://example.com | head -n 1
Команда getent здесь принципиальна. В отличие от dig и host, она ходит ровно тем же путём через системную библиотеку, что и большинство реальных приложений, поэтому её ответ это честная имитация того, что увидит apt, curl или менеджер пакетов. Если getent резолвит имя, значит, проблема действительно закрыта, а не замаскирована.
Главный вывод из всей этой истории не про конкретные команды. Он про подход. Сбои резолвинга на современном Linux почти никогда не означают, что сломан сам DNS. В подавляющем большинстве случаев сломан маршрут, которым конкретный софт идёт до сервера имён, и виновата рассогласованность между умной надстройкой и привычным файлом. Стоит держать в голове карту этих маршрутов, кто слушает порт, куда указывает resolv.conf, в каком режиме живёт сервис, каким механизмом пользуется приложение, и непонятные ночные сбои превращаются в короткую цепочку проверок с предсказуемым финалом. А там, где надстройка стабильно мешает, классическая схема с обычным файлом по-прежнему остаётся честным и работающим выбором, не уступающим современным решениям ни в чём, кроме модности.