Остановите две службы Linux, борющиеся за /etc/resolv.conf
Выясните, кому принадлежит файл, прежде чем его править. Флаг неизменяемости скрывает конфликт, а переподключения или продления DHCP снова и снова отменяют ручное исправление DNS.
DNS постоянно возвращается в прежнее состояние, переподключение VPN отменяет вашу правку, и в конце концов кто-то в команде предлагает заблокировать файл. Эта последовательность — симптом, а не решение: два компонента оба считают, что владеют /etc/resolv.conf, и побеждает тот, кто пишет последним. Блокировка файла не решает конфликт владения, она лишь скрывает его и при этом ломает тот компонент, который вам был на самом деле нужен. Выполните четыре проверки ниже по порядку и исправьте устройство на том уровне, который им владеет, а не в самом файле.
Проверка 1: это символическая ссылка, и куда
Начните с самого файла, потому что путь, который вы правите, может вообще не быть файлом. Символическая ссылка в каталог времени выполнения резолвера ведёт себя совершенно иначе, чем обычный файл, который перезаписывает клиентский скрипт:
ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf
cat /etc/resolv.conf
Ссылка на stub-путь systemd-resolved означает, что содержимое файла генерируется, и ручная правка будет перезаписана при следующем событии. Обычный файл со строками nameserver обычно пишет тот клиент или хук DHCP, который запускается при подключении. И то и другое законно; важно знать, что именно у вас, прежде чем что-то менять.
Проверка 2: какой демон запущен
Выясните, что действительно активно, а не что установлено. В современном дистрибутиве может присутствовать несколько резолверов, и отвечает тот, который держит порт 53 на интерфейсе loopback:
systemctl status systemd-resolved --no-pager
resolvectl status
ss -lunp | grep ':53'
Вывод состояния называет DNS-серверы для каждой ссылки и домены маршрутизации — это и есть карта, нужная для split DNS. Если внутренние имена должны уходить на внутренний сервер, а всё остальное на публичный, эта политика живёт в доменах для каждой ссылки, и сведение её к статическому списку nameserver в файле молча теряет это различие.
Собственная конфигурация NetworkManager решает, какой из этих бэкендов он использует. Его параметр dns= в главном файле конфигурации выбирает между его внутренним резолвером, dnsmasq, systemd-resolved и none, и дистрибутив мог выбрать за вас. Прочитайте этот параметр, прежде чем решать, что значит «починить DNS» на этом хосте.
Проверка 3: что клиенту VPN разрешено писать
Именно здесь обычно и живёт конфликт. Клиент VPN, который напрямую перезаписывает файл резолвера, будет воевать с любой службой, которая тоже его поддерживает, и проигрывает тот, кто запускается первым. Новые выпуски OpenVPN умеют передавать настройки DNS в systemd-resolved вместо перезаписи файла — это и есть нужное вам кооперативное поведение; старые скрипты и написанные вручную хуки часто вызывают устаревший вспомогательный инструмент или просто перезаписывают файл.
Ситуация с WireGuard похожа по форме: сама конфигурация интерфейса не управляет DNS, это делают инструменты вокруг неё — через помощник резолвера или интеграцию с resolved. Если установлены два таких помощника, оба попытаются применить своё видение мира при подключении.
Читайте хуки поднятия и опускания клиента, а не гадайте. Несколько строк shell в /etc/openvpn/up.sh или аналогичном каталоге объясняют поведение, которое иначе выглядит случайным, и именно там можно заставить клиента сотрудничать, а не соревноваться.
Проверка 4: чините у владельца, а не в файле
Когда владелец известен, настройте DNS через поддерживаемый им интерфейс. Для systemd-resolved это настройки для каждой ссылки и домены маршрутизации вместо статического файла; для NetworkManager — настройки DNS профиля подключения и выбранный бэкенд; для дистрибутива с интеграцией resolvconf — запись через этого помощника.
Не делайте файл неизменяемым. Атрибут неизменяемости останавливает законные продления DHCP и обновления VPN вместе с теми, с которыми вы боролись, и оставляет систему в состоянии, когда следующее обновление пакетов или переподключение даёт сбой, который никто не может объяснить из конфигурации. Он скрывает конфликт владения — именно то, что вы пытались решить.
Там, где два компонента действительно должны сосуществовать, дайте им разные задачи. Пусть сетевой менеджер владеет конфигурацией на уровне интерфейса, пусть клиент VPN добавляет DNS и домены для каждой ссылки через ту же интеграцию, а третий скрипт, который напрямую пишет файл, удалите. Документация systemd-resolved описывает поддерживаемые режимы и то, что каждый из них показывает в /etc/resolv.conf. Выберите режим, соответствующий объёму политики, который нужно сохранить. Если split DNS важен, stub-устройство с доменами для каждой ссылки сохраняет его, а режим, копирующий вышестоящие серверы в файл, молча его уплощает.
Проверяйте после переподключения, а не после правки
Файл, который выглядит правильным сразу после вашего изменения, ничего не доказывает, потому что следующее событие ещё не произошло. Запустите ту последовательность, которая раньше его ломала: опустите и поднимите туннель, продлите аренду DHCP или переключите интерфейс, а затем снова прочитайте те же значения.
resolvectl query build.corp.example
resolvectl query example.com
resolvectl status | head -40
Затем подтвердите различие, которое важно: внутреннее имя разрешается через внутренний сервер, а публичное имя по-прежнему работает. Конфигурация, отправляющая всё на внутренний сервер, выглядит хорошо, пока кто-нибудь не попробует выйти в интернет через туннель, а конфигурация, отправляющая всё на публичный, утекает внутренние имена.
Запишите владельца, бэкенд и режим символической ссылки в заметки о развёртывании. Этот один абзац превращает будущее обновление пакетов из инцидента в пункт проверки и не даёт следующему человеку заново обнаружить тот же конфликт командой chattr.