Каждое обращение к имени без локального кэша уходит на внешний сервер и занимает от единиц до сотен миллисекунд, а в корпоративной сети добавляется поиск по нескольким суффиксам. Кэш на рабочей станции снимает большую часть таких обращений. Разбираем устройство systemd-resolved, настройку кэша и доменов, замену стандартного компонента и измерение эффекта.

Зачем нужен кэш на рабочей станции и как он устроен

Разрешение имени состоит из нескольких шагов, и часть их повторяется при каждом обращении: приложение запрашивает имя у системной библиотеки, та идёт к резолверу, резолвер к серверу имён. Локальный кэш убирает последние шаги для повторных обращений.

Польза заметнее в трёх случаях. Первый, частые обращения к одним внутренним ресурсам: файловые хранилища, серверы сборки. Второй, короткий срок жизни записей у внешних имён. Третий, нестабильный канал, где кэш позволяет работать с известными адресами при кратковременной потере связи.

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

В большинстве современных дистрибутивов компонентом разрешения имён служит systemd-resolved. Он слушает локальный адрес и принимает запросы от приложений, ведёт собственный кэш и хранит настройки по каждому сетевому соединению отдельно.

resolvectl status
systemctl status systemd-resolved --no-pager
ls -l /etc/resolv.conf

Последняя команда показывает, куда ведёт ссылка на файл resolver: обычно это сгенерированный файл, в котором указан локальный адрес прослушивателя. Ручная правка такого файла бессмысленна, поскольку он перезаписывается.

Настройка systemd-resolved и привязка к соединениям

Основной файл настройки задаёт поведение службы в целом, а параметры конкретного соединения применяются отдельно.

# /etc/systemd/resolved.conf
[Resolve]
DNS=192.168.10.5
FallbackDNS=
Domains=lan.example
DNSSEC=no
Cache=yes
DNSStubListener=yes
ReadEtcHosts=yes

Параметр DNS задаёт сервер по умолчанию, FallbackDNS список запасных внешних серверов, который стоит очистить в изолированной сети, чтобы запросы не уходили наружу. Параметр Domains перечисляет поисковые суффиксы, DNSSEC включает проверку подписей, Cache управляет кэшированием, а DNSStubListener включает локальный прослушиватель.

Параметр ReadEtcHosts указывает службе учитывать содержимое файла с локальными соответствиями имён и адресов, что удобно для тестовых записей. Файл перечитывается при каждом запросе, поэтому правка применяется сразу, без перезапуска службы.

Отдельно настраивают поведение при недоступности сервера. Штатный компонент возвращает ошибку после исчерпания времени ожидания, и для приложений с собственными повторами это означает заметную задержку. Сокращение времени ожидания и добавление второго сервера в список уменьшают паузу, но не отменяют необходимости следить за доступностью основного.

Настройки отдельного соединения задают через сетевой менеджер либо через файлы сети.

sudo resolvectl dns eth0 192.168.10.5
sudo resolvectl domain eth0 lan.example
sudo resolvectl default-route eth0 yes
resolvectl status eth0

Признак default-route определяет, уйдёт ли через это соединение запрос к внешнему имени. На машине с двумя сетями, одна из которых служит только для внутренних ресурсов, признак ставят только на внешнем соединении, а внутреннему назначают домен. Тогда запросы к коротким именам идут во внутреннюю сеть, а всё остальное наружу, и смешения не происходит.

# /etc/systemd/network/10-lan.network
[Network]
DNS=192.168.10.5
Domains=lan.example

После правки файлы перечитывают, а состояние проверяют по выводу статуса, который показывает серверы, домены и признак маршрута по умолчанию для каждого соединения.

sudo systemctl restart systemd-networkd
resolvectl status

Размер кэша, время жизни записей и очистка

Служба сообщает число записей в кэше по каждому протоколу и семейству адресов вместе с числом обращений и попаданий.

resolvectl statistics
resolvectl flush-caches
resolvectl reset-statistics

Число записей в кэше само по себе не показывает качество работы: важнее соотношение обращений и попаданий. При малом числе попаданий стоит проверить, не задаёт ли сервер слишком короткий срок жизни для записей внутренней зоны, поскольку в таком случае кэш не успевает отработать даже между соседними запросами.

Доля попаданий главный показатель эффективности. Значение выше семидесяти процентов говорит о том, что кэш работает и экономит обращения, значение около нуля означает, что имена почти не повторяются либо записи живут слишком короткое время.

Размер кэша в стандартной поставке ограничен несколькими тысячами записей, чего достаточно для рабочей станции. Увеличение лимита оправдано на машине, которая обслуживает много клиентов, но там уместнее поставить отдельный кэширующий сервер, а не расширять кэш резолвера.

Отрицательные ответы также сохраняются. Время их хранения определяется значением SOA в ответе сервера и обычно составляет несколько десятков секунд. Отсюда практическое следствие: после добавления записи в зону ждать распространения приходится недолго, но принудительная очистка кэша экономит это время.

Полезно учитывать, что кэш сбрасывается при перезапуске службы и при смене сетевого соединения. Если замеры доли попаданий ведутся после каждой перезагрузки, картина оказывается необъективной, и статистику стоит смотреть за рабочий день.

Unbound и dnsmasq как локальный кэширующий сервер

Когда нужен больший кэш, гибкая настройка и проверка подписей, на машину ставят отдельную службу. Unbound проектировался именно как кэширующий резолвер и умеет опрашивать корневые серверы напрямую, не завися от внешнего пересылающего сервера.

# /etc/unbound/unbound.conf.d/local.conf
server:
    interface: 127.0.0.1
    interface: 192.168.10.20
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.10.0/24 allow
    cache-max-ttl: 86400
    cache-min-ttl: 30
    msg-cache-size: 64m
    rrset-cache-size: 128m
    do-not-query-localhost: no

forward-zone:
    name: "."
    forward-addr: 192.168.10.5

Параметры cache-max-ttl и cache-min-ttl задают границы времени хранения, что полезно при работе с зонами, где сроки заданы неудачно. Размеры кэшей подбирают по числу обслуживаемых клиентов: указанных значений хватает на несколько сотен активных имён. Число рабочих потоков задают отдельным параметром, обычно по числу ядер.

Более простой вариант dnsmasq, который совмещает кэш с раздачей адресов и подходит для небольшой сети.

# /etc/dnsmasq.conf
listen-address=127.0.0.1,192.168.10.20
server=192.168.10.5
cache-size=5000
neg-ttl=30
log-queries

После запуска отдельной службы локальный прослушиватель systemd-resolved отключают, чтобы два компонента не спорили за один порт, а файл resolver переводят на новый адрес.

sudo sed -i 's/^DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
resolvectl status

При таком порядке приложения продолжают читать тот же файл, а запросы идут через новый кэш. Проверка показывает, какой сервер используется фактически.

Внутренние зоны, раздельное разрешение и поисковые домены

Корпоративная среда обычно содержит имена, которые существуют только внутри, и обращаться с ними к внешним серверам бессмысленно. Механизм раздельного разрешения направляет запросы по доменам разным серверам.

sudo resolvectl domain eth0 '~lan.example'
sudo resolvectl dns eth0 192.168.10.5

Домен с символом тильды в начале считается маршрутизирующим: запросы к именам из этого домена уходят только на указанный сервер, и поисковые суффиксы к ним не применяются. Без тильды домен работает как поисковый, и к коротким именам он просто добавляется.

В отдельной службе то же задают зоной пересылки.

forward-zone:
    name: "lan.example"
    forward-addr: 192.168.10.5

Для локальных имён, которые не должны попадать в сеть вовсе, подходит файл с соответствиями адресов.

echo "192.168.10.77 lab.example.local" | sudo tee -a /etc/hosts
getent hosts lab.example.local

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

Поисковые домены сокращают ввод, но умножают число запросов. Каждое короткое имя проверяется по очереди с каждым суффиксом, и при трёх суффиксах одно обращение превращается в три-четыре запроса. Для рабочих станций список стоит держать коротким, а в приложениях использовать полные имена с точкой на конце.

Диагностика задержек и типовые причины медленного разрешения

Измерение начинают с времени отклика сервера и с того, попадает ли запрос в кэш.

resolvectl query app.lan.example
dig @192.168.10.5 app.lan.example +stats | grep -E 'Query time|status'
getent hosts app.lan.example
systemd-analyze blame | head -n 10

Повторный запрос к тому же имени должен занимать доли миллисекунды. Если время не снижается, кэш не работает: служба перезапущена, запись имеет нулевой срок жизни либо запрос идёт мимо локального резолвера.

Диагностику начинают с разделения двух случаев: имя не разрешается вовсе либо разрешается медленно. В первом случае проверяют назначение серверов и доступность порта, во втором измеряют время и смотрят, падает ли оно при повторном запросе.

Первая типовая причина задержек, одновременные обращения по двум семействам адресов. Приложение запрашивает оба типа записей, и при недоступности одного из серверов или при фильтре на пути второе обращение ждёт времени ожидания. Вторая причина, длинный список поисковых доменов. Третья, обращение к внешнему серверу через канал с большой задержкой вместо внутреннего. Четвёртая, запись с коротким сроком жизни, из-за которой кэш почти не помогает.

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

Проверить, куда именно уходит запрос, помогает журнал службы.

sudo journalctl -u systemd-resolved -n 50 --no-pager
resolvectl log-level debug
sudo journalctl -u systemd-resolved -f

Повышенный уровень журнала показывает каждый запрос с указанием сервера и результата, после чего уровень возвращают к прежнему значению, иначе журнал быстро разрастается.

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

Сравнение подходов и выбор схемы

Три варианта покрывают большинство задач. Первый, оставить штатный компонент и настроить его: этого достаточно для отдельной рабочей станции, а преимущество в отсутствии дополнительных служб и в учёте настроек каждого соединения. Второй, поставить кэширующий сервер на той же машине: выбор оправдан, когда нужен большой кэш, проверка подписей или обслуживание нескольких клиентов. Третий, выделенный сервер имён в сегменте: он снимает нагрузку с рабочих станций и служит единой точкой настройки для всей сети.

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

Перед включением проверяют три вещи: какой адрес указан в файле resolver, какие серверы назначены каждому соединению и какой домен считается маршрутизирующим. Эти три параметра определяют, куда пойдёт запрос, и почти любая проблема с именами объясняется расхождением в одном из них.

Завершает настройку измерение. Замер времени разрешения десяти типичных имён до включения кэша и после даёт число, которое можно показать коллегам, и оно убеждает лучше любых описаний преимущества.