Резолвер, блокирующий рекламу, авторитетный сервер для внутренних имен, сервер DHCP и компонент, поддерживающий DNS-over-HTTPS. В большинстве сетей это четыре пакета, четыре формата конфигурации и четыре вещи, о которых можно забыть, пока одна из них не выйдет из строя. DNS-сервер Technitium — это единый процесс, который выполняет все эти функции, а также предоставляет веб-консоль и полнофункциональный HTTP-API.
В этом руководстве описаны четыре способа установки Technitium DNS Server: установщик для «голого железа» на Ubuntu, на Debian, на Rocky Linux (где установщик намеренно не затрагивает firewalld и SELinux) и развёртывание с помощью Docker Compose, полностью настраиваемое через переменные среды. Затем сервер проходит процесс укрепления безопасности, настраивается с использованием зашифрованных исходных серверов и списков блокировки, тестируется на реальных запросах, переносится с Pi-hole или BIND 9, автоматизируется через API и отслеживается с помощью Prometheus.
Тестирование проводилось в августе 2026 года на Technitium 15.4 в средах Ubuntu 26.04, Debian 13, Rocky Linux 10 и Docker. Все приведённые ниже цифры получены непосредственно с этих систем, а не из технических спецификаций.
Что заменяет Technitium и когда его можно не использовать
Основной аргумент — консолидация. Один бинарный файл отвечает на рекурсивные запросы, авторитетно обслуживает ваши собственные зоны, подписывает их с помощью DNSSEC, блокирует домены из списков в формате hosts, завершает соединения DoH/DoT/DoQ, и выдает адреса по DHCP. Ни одно другое популярное решение не выполняет весь этот набор задач в рамках одного процесса, а приведенная ниже таблица намеренно даёт альтернативам большую свободу: BIND 9 поддерживает native DoT и DoH начиная с версии 9.18 и может осуществлять блокировку с помощью Response Policy Zones, а Unbound также обслуживает оба зашифрованных протокола. Разница заключается в том, что вы настраиваете эти возможности самостоятельно, для каждого демона, на отдельных языках конфигурации.
| Инструмент | Рекурсивный | Авторитетный | Блокировка | Сервер DoH/DoT | DHCP | Графический интерфейс |
|---|---|---|---|---|---|---|
| Technitium | да | да | да | да | да | да |
| Pi-hole / AdGuard Home | перенаправляет | нет | да | частично | да | да |
| BIND 9 | да | да | через RPZ | да, начиная с версии 9.18 | нет | нет |
| PowerDNS | отдельный демон | да | через RPZ (Recursor) | через dnsdist | нет | дополнение |
| Unbound | да | минимальный | через RPZ | да | нет | нет |
| dnsmasq | перенаправления | минимальный | файл hosts | нет | да | нет |
Пропустите этот вариант, если вы обслуживаете авторитетный DNS для сотен публичных зон, где BIND или PowerDNS с базой данных в качестве бэкэнда по-прежнему остаются лучшим решением. Пропустите его, если вам нужен максимально компактный резолвер на аппаратном обеспечении с ограниченными ресурсами, поскольку среда выполнения .NET потребляет реальную память (количество измерено ниже). Также пропустите этот вариант, если ваша команда уже обладает опытом работы с BIND и хранит файлы зон в Git, поскольку переход на сервер с приоритетом графического интерфейса и API — это изменение рабочего процесса, а не просто смена пакета. Наше сравнение BIND, dnsmasq, PowerDNS и Unbound даст вам более полное представление, если вы все еще выбираете.
Необходимые условия и расчёт ресурсов
Три фактора определяют технические характеристики устройства Technitium, и ни один из них не связан с процессором. Первый — списки блокировки: каждый список при загрузке анализируется и помещается в память, поэтому один список хостов с 99 000 записей занимает реальный объем ОЗУ, а конфигурация с пятью списками — в несколько раз больше. Второй — размер кэша, настраиваемый по максимальному количеству записей, а не по объему в байтах. Третьим фактором является срок хранения журналов и статистики, которые размещаются на диске, а не в оперативной памяти.
Для резолвера в локальной сети, обслуживающего несколько сотен клиентов, 2 виртуальных процессора (vCPU) и 2 ГБ оперативной памяти вполне достаточно, и причина в том, что DNS не требует больших ресурсов: измеренный объем памяти, занимаемый всеми четырьмя установками, приведенными ниже, составлял от 163 до 176 МБ при загруженном списке блокировки с 99 000 зон. Если же речь идёт о тысячах клиентов, нескольких списках блокировок и подписи ваших собственных зон с помощью DNSSEC, то 4 ГБ будет оптимальным объёмом. Любой сервер, обрабатывающий DoH для общедоступного Интернета, должен рассчитываться исходя из количества TLS-рукопожатий, а не запросов.
Серверы, описанные в этом руководстве, работали с 2 виртуальными процессорами, от 3 до 4 ГБ оперативной памяти и 20 ГБ дискового пространства. Рассматривайте эти параметры как минимальные требования для повторения инструкций, а не как рекомендации для производственной среды.
Вам также понадобятся некоторые компоненты, которых нет в облачных образах. Облачные образы Debian и Rocky Linux поставляются без dig, а в облачном образе Rocky вообще не установлено firewalld , что имеет значение, поскольку в обычной установке Rocky это присутствует. Установите клиентские инструменты на ту машину, с которой вы будете проводить тестирование:
sudo apt-get install -y bind9-dnsutils # Ubuntu, Debian
sudo dnf install -y bind-utils firewalld # Rocky, AlmaLinux, RHEL
Порт 53 должен быть свободен, что в Ubuntu и Debian означает необходимость работы с systemd-resolved. Установщик сам решает эту задачу для путей «bare-metal», а для Docker это единственный шаг, требующий ручного вмешательства; оба случая описаны ниже.
Задайте переменные, используемые на каждом шаге
Некоторые значения повторяются в правилах брандмауэра, записях зон, вызовах API и файле Compose. Экспортируйте их один раз, чтобы вы могли вставлять остальную часть руководства, не тратя время на поиск значений для подстановки:
export DNS_HOST="10.0.1.53"
export DNS_FQDN="ns1.lab.example.com"
export ZONE="lab.example.com"
export LAN_CIDR="10.0.1.0/24"
export TDNS_PASS="ChangeMe-Strong-Passphrase"
Подставьте адрес вашего сервера, имя хоста, внутреннюю зону и диапазон LAN, выберите реальный пароль, а затем убедитесь, что все поля заполнены, прежде чем запускать что-либо, что зависит от этих значений:
echo "server : ${DNS_HOST} (${DNS_FQDN})"
echo "zone : ${ZONE}"
echo "lan : ${LAN_CIDR}"
Эти переменные сохраняются только в текущей оболочке. Если вы переподключитесь или перейдете в sudo -i , вам нужно будет экспортировать их заново.
Установка на Ubuntu и Debian
Установщик из исходного репозитория представляет собой одну команду, и она одинакова для обоих дистрибутивов:
curl -sSL https://download.technitium.com/dns/install.sh | sudo bash
Пока не запускайте её. Существует ловушка с зависимостями, предотвратить которую займёт всего две минуты, и это единственная веская причина прочитать этот раздел вместо однострочной инструкции из исходного репозитория.
Резервная версия ICU устанавливает 137 МБ, тогда как требуется всего 36 МБ
Technitium работает на .NET, которому для глобализации требуется ICU. Установщик ищет libicu74, libicu72 и libicu70 по имени. В Debian 13 поставляется libicu76, а в Ubuntu 26.04 — libicu78, поэтому в текущих выпусках ни одно из этих имён не совпадает, а на компьютере, где ICU вообще отсутствует, скрипт переходит к использованию подстановочного знака:
No specific libicu package was found, trying generic installation...
Эта общая установка — apt-get install -y libicu*, и шаблон glob работает именно так, как и положено. В Debian 13 было загружено одиннадцать пакетов общим объёмом 137 МБ, из которых один пакет и 36 МБ были действительно необходимы:
| Пакет | Размер после установки | Требуется? |
|---|---|---|
| libicu76 | 37 371 КБ | да |
| libicu-dev | 50 061 КБ | нет |
| libicu4j-java | 17 429 КБ | нет, это Java |
| libicu4j-4.4-java | 6 635 КБ | нет, это Java |
| libc6-dev, linux-libc-dev, manpages-dev, libc-dev-bin, libcrypt-dev, rpcsvc-proto, icu-devtools | 28 935 КБ | нет, частичный набор инструментов для C |
| Всего установлено | 140 431 КБ |
Наличие двух несвязанных библиотек Java и набора инструментов сборки на языке C на DNS-сервере — это не катастрофа, но этого можно избежать, и это увеличивает площадь уязвимостей на машине, единственная задача которой — отвечать на запросы по порту 53. Сначала установите правильный пакет ICU, и скрипт glob никогда не запустится. Вместо того чтобы жестко прописывать число, которое устареет в следующем выпуске, пусть apt сам определит название пакета:
sudo apt-get update
ICU_PKG=$(apt-cache search --names-only '^libicu[0-9]+$' | awk '{print $1}' | sort -V | tail -1)
echo "installing ${ICU_PKG}"
sudo apt-get install -y "${ICU_PKG}"
В Ubuntu 26.04 это соответствует libicu78, а в Debian 13 — libicu76. Разветвление с подстановочным символом срабатывает только тогда, когда пакет libicu вообще не установлен, и именно так разделились эти два варианта: образ Ubuntu 26.04 уже содержал libicu78, поэтому установщик пропустил всю цепочку, тогда как образ Debian 13 не содержал libicu, поэтому сработал шаблон glob и загрузил остальные десять пакетов. Установите ICU в первую очередь, и установщик сообщит об этом одной строкой:
ICU package is already installed.
Теперь запустим установщик. Он загружает среду выполнения ASP.NET Core с сайта Microsoft в /opt/dotnet, создаёт символьные ссылки /usr/bin/dotnet, размещает приложение в /opt/technitium/dnsи завершает работу значительно быстрее, чем за полминуты: 11,65 секунд на Ubuntu против 17,18 секунд на Debian, где дополнительное время ушло на 137 МБ пакетов ICU, которые загружал glob до появления этого исправления.
Что изменил установщик и название модуля, которое никто не угадает
Прежде чем приступить к поиску, стоит знать о двух неожиданностях. Служба называется dns.service, а не technitium, и запускается от имени системного пользователя dns-server:
systemctl status dns.service
sudo ss -lntupH | awk '/:53 |:5380/{print $1, $5, $7}'
Набор прослушивателей показывает, что доступно при свежей установке: это только резолвер и консоль с простым HTTP. DoT на 853 и DoH на 443 остаются отключенными, пока вы их не включите:
udp 0.0.0.0:53 users:(("dotnet",pid=1139,fd=221))
tcp 0.0.0.0:53 users:(("dotnet",pid=1139,fd=222))
tcp *:5380 users:(("dotnet",pid=1139,fd=220))
Второй неожиданностью является то, что установщик останавливает и отключает systemd-resolved, принудительно заменяет dns=none на /etc/NetworkManager/NetworkManager.conf , если этот файл существует, и заменяет /etc/resolv.conf на nameserver 127.0.0.1. Сначала создаётся резервная копия. В Ubuntu и Debian эта резервная копия бесполезна, и это ставит пользователей в тупик в самый неподходящий момент.
Резервная копия файла resolv.conf в Ubuntu и Debian представляет собой «висящую» символьную ссылку
Программа установки копирует /etc/resolv.conf с cp -a. В Ubuntu и Debian этот путь представляет собой символьную ссылку на ../run/systemd/resolve/stub-resolv.conf, поэтому cp -a сохраняет не содержимое, а саму символьную ссылку. Затем программа установки отключает systemd-resolved, что приводит к удалению целевого файла. При последующем чтении резервной копии ничего не получается:
sudo cat /opt/technitium/dns/resolv.conf.bak
Файл существует в виде ссылки, ссылка указывает на файл, которого больше нет, и описанный в документации способ восстановления («восстановить резервную копию») не может сработать:
cat: /opt/technitium/dns/resolv.conf.bak: No such file or directory
Если dns.service не запускается, то на этом компьютере нет работающего резолвера и нет резервной копии, к которой можно было бы обратиться. Способ восстановления, который действительно работает, заключается в том, чтобы вернуть systemd-resolved и вручную воссоздать символическую ссылку:
sudo systemctl enable --now systemd-resolved
sudo ln -sf ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
resolvectl status | head -5
В Rocky Linux эта же резервная копия представляет собой реальный файл с реальным содержимым, поскольку systemd-resolved там никогда не использовался. Стоит узнать, к какому семейству вы относитесь, прежде чем это понадобится.
Установите систему на Rocky Linux, а затем исправьте то, что пропустил установщик
Сам установщик представляет собой ту же команду и завершил работу за 12,03 секунды на Rocky Linux 10, используя libicu уже имеющийся пакет. В семействе RHEL нет цепочки ICU с нумерацией версий, поэтому проблема с подстановочными знаками из предыдущего раздела здесь отсутствует.
Что установщик не делает в Rocky, так это не затрагивает брандмауэр, и это не упущение, которое можно незаметно обойти. Установка Rocky по умолчанию разрешает три службы в public зоне:
sudo firewall-cmd --list-all | grep -E '^ (services|ports):'
DNS в их число не входит, поэтому сервер отлично отвечает на запросы к самому себе, но никакие другие устройства в локальной сети не могут к нему подключиться:
services: cockpit dhcpv6-client ssh
ports:
Попробуйте отправить запрос с другого хоста в таком состоянии — вы получите сетевую ошибку, а не ошибку DNS, что заставляет пользователей копаться в настройках Technitium, хотя пакет так и не дошел:
dig @10.0.1.53 cloudflare.com
Показательным является сообщение «host unreachable» вместо SERVFAIL или REFUSED:
;; communications error to 10.0.1.53#53: host unreachable
Включите резолвер, консоль и опциональные зашифрованные протоколы. По одному флагу на каждый вызов, поскольку firewall-cmd не позволяет смешивать --add-service и --add-port в одном вызове:
sudo firewall-cmd --permanent --add-service=dns
sudo firewall-cmd --permanent --add-port=5380/tcp
sudo firewall-cmd --permanent --add-port=853/tcp
sudo firewall-cmd --permanent --add-port=853/udp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=443/udp
sudo firewall-cmd --reload
После загрузки правил тот же dig выполняет разрешение, а консоль отвечает HTTP 200 из другой части сети. Если вы запускаете только резолвер и не планируете завершать работу зашифрованного DNS, удалите строки 853 и 443 вместо того, чтобы открывать порты, которые вы не используете.
SELinux продолжает применять ограничения, поскольку ничто не ограничивает работу службы
Читатели ожидают, что SELinux создаст проблемы, но их нет, и об этом стоит четко заявить, указав причину. Поставляемое устройство не имеет политики, поэтому процесс попадает в универсальный домен:
getenforce
ps -eZ | grep dotnet
sudo ausearch -m avc -ts recent 2>&1 | tail -1
Режим «enforcing», нулевое количество отказов и метка, объясняющая причину:
Enforcing
system_u:system_r:unconfined_service_t:s0 1139 ? 00:00:12 dotnet
<no matches>
Таким образом, исправлять нечего, и защищать вас тоже нечего: unconfined_service_t Это означает, что SELinux не ограничивает доступ этого процесса к ресурсам. Никогда не отключайте SELinux, чтобы заставить его работать, потому что он и так работает. Если вам нужна изоляция, вам придётся самостоятельно написать модуль политики.
Запустите его в Docker с помощью Compose
Контейнер — это наиболее воспроизводимый способ, поскольку Technitium принимает всю конфигурацию для первого запуска через DNS_SERVER_* переменные среды. Не нужно кликать по консоли, чтобы получить работающий резолвер.
Во-первых, порт 53 на хосте. Поскольку systemd-resolved удерживает свой фиктивный прослушиватель, публикация порта завершается с ошибкой, в которой точно указано название конфликта. Это форма конфликта, связанная с опубликованными портами; в режиме хоста тот же занятый порт вместо этого отображается как сбой при привязке со стороны самого Technitium в docker compose logs:
Error response from daemon: failed to set up container networking: driver failed
programming external connectivity on endpoint dns-server (4e79e99cb18a...): failed to
bind host port 0.0.0.0:53/tcp: address already in use
В большинстве руководств рекомендуется отключить systemd-resolved. Лучше отключить только промежуточный прослушиватель, так как resolved продолжает выполнять остальные задачи, а изменение сводится к одному файлу, который можно удалить позже. Создайте файл-замену:
sudo mkdir -p /etc/systemd/resolved.conf.d
sudo vim /etc/systemd/resolved.conf.d/99-technitium.conf
Достаточно всего двух строк:
[Resolve]
DNSStubListener=no
Перезапустите resolved и убедитесь, что порт 53 свободен, пока сама служба продолжает работать:
sudo systemctl restart systemd-resolved
systemctl is-active systemd-resolved
sudo ss -lnup | grep -q '127.0.0.53:53' && echo "stub still up" || echo "stub listener gone"
Один шаг, который пропускают в большинстве руководств, но без которого вы застрянете: /etc/resolv.conf — это по-прежнему символьная ссылка на stub-resolv.conf, который при каждом запросе перенаправляет на 127.0.0.53, а там уже ничего не прослушивает. resolved намеренно не перезаписывает этот файл, пока заглушка отключена, поэтому у хоста теперь нет работающего резолвера, и docker compose up ниже даже не может загрузить образ. Направьте символьную ссылку на файл, не являющийся заглушкой, в котором перечислены реальные серверы-источники:
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
getent hosts registry-1.docker.io
Если при этом будут возвращены адреса, хост сможет снова выполнять разрешение и загрузка сработает. Как только Technitium запустится, можно направить /etc/resolv.conf на 127.0.0.1 , чтобы система использовала собственный резолвер. Стоит подробнее объяснить, как была обнаружена эта проблема: хост в лаборатории, казалось, работал с неработающей символьной ссылкой, потому что VPN-клиент уже переписал эти файлы — именно такой локальный сбой и приводит к тому, что у читателя сервер перестаёт работать.
Поскольку порт свободен, а DNS по-прежнему работает, остаётся решить вопрос о сетевом режиме контейнера, и это не просто вопрос стилистики.
Используйте сетевую инфраструктуру хоста, и вот доказательство, почему
Разработчики рекомендуют сетевую инфраструктуру хоста для DHCP. Причина использовать её даже без DHCP заключается в том, что режим моста незаметно нарушает как вашу политику безопасности, так и вашу видимость, и именно на выявление этой проблемы ушло больше всего времени.
При развертывании в режиме моста с рекурсией, ограниченной локальной сетью, каждый запрос возвращал статус REFUSED, включая запросы от самого хоста Docker. Docker выполняет source-NAT трафика к шлюзу сети Compose, поэтому Technitium никогда не видит адрес клиента. Он видит один адрес — всегда. Панель мониторинга показывает всю картину, как только вы переключаетесь между режимами с подключенным тем же объемом данных:
Top Clients (last hour)
172.18.0.1 8 hits <- every client during bridge mode, collapsed into one
10.0.1.10 5 hits <- the real client, after switching to host mode
Это влечет за собой три последствия. Список доступа, составленный с использованием вашего реального диапазона LAN, отклоняет все запросы. Расширение его до подсети моста, казалось бы, решает проблему, но на самом деле позволяет доступ любому, кто может достичь опубликованного порта — именно так резолвер случайно становится открытым. А статистика по клиентам и журналы запросов бесполезны, поскольку всегда присутствует только один клиент.
Сначала создайте файл паролей, чтобы учетные данные никогда не хранились в файле Compose:
mkdir -p ~/technitium/secrets
printf '%s' "${TDNS_PASS}" > ~/technitium/secrets/dns_admin_password
chmod 600 ~/technitium/secrets/dns_admin_password
Затем создайте файл Compose:
vim ~/technitium/compose.yaml
Было подтверждено, что все приведённые ниже значения впоследствии попадают в рабочую конфигурацию, а тег образа закреплён намеренно, чтобы при повторном развёртывании не была загружена версия, отличная от той, которую вы тестировали. Проверьте список релизов исходного проекта перед тем, как повысить версию:
services:
dns-server:
container_name: dns-server
image: docker.io/technitium/dns-server:15.4.0
network_mode: "host"
environment:
- DNS_SERVER_DOMAIN=ns1.lab.example.com
- DNS_SERVER_ADMIN_PASSWORD_FILE=/run/secrets/dns_admin_password
- DNS_SERVER_WEB_SERVICE_LOCAL_ADDRESSES=0.0.0.0
- DNS_SERVER_FORWARDERS=https://cloudflare-dns.com/dns-query (1.1.1.1), https://dns.quad9.net/dns-query (9.9.9.9)
- DNS_SERVER_FORWARDER_PROTOCOL=Https
- DNS_SERVER_RECURSION=UseSpecifiedNetworkACL
- DNS_SERVER_RECURSION_NETWORK_ACL=10.0.1.0/24, 127.0.0.1
- DNS_SERVER_ENABLE_BLOCKING=true
- DNS_SERVER_BLOCK_LIST_URLS=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
- DNS_SERVER_LOG_USING_LOCAL_TIME=true
volumes:
- config:/etc/dns
- ./secrets/dns_admin_password:/run/secrets/dns_admin_password:ro
restart: unless-stopped
volumes:
config:
Запустите систему и понаблюдайте, как при первом запуске применяется настройка среды:
cd ~/technitium
sudo docker compose up -d
sudo docker compose logs --tail 20 dns-server
Если у вас есть причина оставаться на мостовой сети (например, хост, на котором вы не можете освободить порты), то опубликуйте 53/udp, 53/tcp и 5380/tcp, внимательно настройте рекурсивный ACL на подсеть моста и добавьте sysctl, который устанавливает исходный проект по определённой причине:
sysctls:
- net.ipv4.ip_local_port_range=1024 65535
Без этого диапазона у загруженного резолвера закончатся исходные порты для исходящих запросов внутри пространства имён контейнера. Ещё одна причина избегать такой конфигурации: Docker вставляет свои правила для открытых портов в nat , перед цепочками, по которым фильтрует ufw, поэтому опубликованный 5380 будет доступен независимо от правила ufw, добавленного в разделе по усилению безопасности ниже. Может показаться, что консоль защищена брандмауэром, но на самом деле это не так.
Первый вход в систему: замените учетные данные по умолчанию
Откройте http://10.0.1.53:5380. То, с какими учетными данными вы войдете в систему, зависит от выбранного вами пути установки, и это единственное место, где эти четыре варианта существенно различаются. Три пути установки создают учетную запись администратора с паролем admin, на сервисе, прослушивающем порт 53, поэтому это первое, что нужно изменить. Развертывание Compose уже установило его: Technitium считывает первую строку файла, указанного DNS_SERVER_ADMIN_PASSWORD_FILE при инициализации учётной записи, поэтому читатель Docker уже находится на ${TDNS_PASS} и может полностью пропустить смену пароля.
Следует отдать должное остальным настройкам по умолчанию. Настройка рекурсии по умолчанию не является неосторожной: свежая установка поставляется с AllowOnlyForPrivateNetworks, поэтому сразу после загрузки система не является открытым резолвером. Блокировка включена без привязки списков в путях установщика, поэтому на самом деле ничего не блокируется, пока вы не добавите список (файл Compose выше привязывает его при первом запуске), а регистрация запросов везде отключена, что объясняет пустую вкладку «Логи» на сервере, который явно работает.
Запишите текущий пароль в переменную, чтобы остальная часть руководства работала независимо от выбранного вами пути, а затем получите токен. Каждый последующий вызов API будет использовать его повторно:
CURRENT_PASS="admin" # installer paths
# CURRENT_PASS="${TDNS_PASS}" # Compose path: the password file already set this
TOKEN=$(curl -s "http://${DNS_HOST}:5380/api/user/login?user=admin&pass=${CURRENT_PASS}&includeInfo=false" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
echo "${TOKEN:0:8}..."
Если вместо восьми символов выводится трассировка Python, это означает, что вход был отклонен и вы выбрали не ту строку: сервер ответил объектом ошибки, в котором отсутствует token . Оставьте этот терминал открытым, поскольку ${TOKEN} является токеном сеанса, и повторный вход — единственный способ вернуться. В разделе об автоматизации он позже будет заменен на токен, срок действия которого не истекает.
В путях установщика сразу же измените пароль. Вызов API требует указания как текущего, так и нового пароля, а при передаче только нового возвращается простой error без каких-либо пояснений. В консоли эта же функция находится в пункте «Изменить пароль» в меню учетной записи в правом верхнем углу, рядом с «Мой профиль», а не в разделе «Администрирование» (вкладки которого: «Сессии», «Пользователи», «Группы», «Права доступа», «SSO» и «Кластер»):
curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://${DNS_HOST}:5380/api/user/changePassword?pass=${CURRENT_PASS}&newPass=${TDNS_PASS}"
Оба этих вызова передают пароль в URL-адресе, поэтому он попадает в историю командной строки и в собственный журнал запросов сервера. Конечные точки также принимают тело запроса POST, которое и должен использовать скрипт. Убедитесь, что изменение вступило в силу, попробовав войти со старыми учетными данными — теперь они должны быть отклонены.
Затем настройте политику рекурсии, в которой явно укажите название вашей сети, вместо того чтобы полагаться на эвристику «private-network». В разделе «Настройки» → «Рекурсия» выберите «Использовать указанный список контроля доступа к сети» и укажите диапазон, который вы экспортировали, в виде ${LAN_CIDR}. Пользователи Docker могут просто подтвердить это, а не вносить изменения, поскольку файл Compose устанавливает и режим, и список при первом запуске:
ACL оценивается в указанном порядке, и первый ! отклоняет запрос, поэтому !10.0.1.99 выше 10.0.1.0/24 исключает один хост из диапазона, который в остальном разрешён. Всё, что ни с чем не совпадает, попадает в настройку по умолчанию, которая отклоняет всё, кроме loopback. Именно такого поведения вы и хотите добиться.
Не оставляйте консоль доступной из любого места пока, и это нужно сделать на всех четырёх путях, а не только на том, перед которым стоит брандмауэр. Ранее в этом руководстве Rocky открыл 5380/tcp доступ ко всей зоне ранее в этом руководстве; файл Compose привязывает консоль ко всем адресам; а в установках Ubuntu и Debian на стандартном облачном образе брандмауэр вообще не запущен, что делает их наиболее уязвимыми из всех четырёх. На Rocky замените общее правило для порта на правило, ограниченное вашей подсетью управления:
sudo firewall-cmd --permanent --remove-port=5380/tcp
sudo firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=${LAN_CIDR} port port=5380 protocol=tcp accept"
sudo firewall-cmd --reload
Если вы ранее пропустили шаг с брандмауэром, первая команда вернёт Error: NOT_ENABLED: 5380:tcp и завершится с ненулевым кодом, что безвредно. Пропустите её и запустите две остальные.
Оставаясь в Rocky, убедитесь, что замена произошла, поскольку оставшееся общее правило будет держать консоль открытой, хотя на первый взгляд будет казаться, что проблема устранена. Для портов и расширенных правил требуются отдельные вызовы, так как firewall-cmd не выводит их оба в одном вызове:
sudo firewall-cmd --list-ports
sudo firewall-cmd --list-rich-rules
Порт 5380 исчез из списка портов (firewalld переупорядочивает оставшиеся элементы), а расширенное правило указывает вашу подсеть:
443/tcp 853/tcp 443/udp 853/udp
rule family="ipv4" source address="10.0.1.0/24" port port="5380" protocol="tcp" accept
В Ubuntu и Debian нет firewalld, поэтому используйте ufw. В Ubuntu он поставляется установленным, но неактивным; в облачном образе Debian его нет вовсе, поэтому сначала установите его там. Добавьте правило SSH, прежде чем что-либо включать, иначе вы заблокируете себе доступ к удалённому компьютеру:
sudo apt-get install -y ufw # Debian only, Ubuntu already has it
sudo ufw allow OpenSSH
sudo ufw allow 53/tcp
sudo ufw allow 53/udp
sudo ufw allow from ${LAN_CIDR} to any port 5380 proto tcp
sudo ufw --force enable
sudo ufw status verbose
Status: active — это строка, подтверждающая, что включение прошло успешно. Резолвер остаётся открытым для всего, что и требуется для DNS-сервера в локальной сети, в то время как консоль ограничена одной подсетью:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
53/tcp ALLOW IN Anywhere
53/udp ALLOW IN Anywhere
5380/tcp ALLOW IN 10.0.1.0/24
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
53/tcp (v6) ALLOW IN Anywhere (v6)
53/udp (v6) ALLOW IN Anywhere (v6)
Семь строк для четырёх команд, поскольку ufw добавляет двойник IPv6 для каждого правила, в котором не указан конкретный адрес. Правило 5380/tcp не имеет двойника: в нём указан диапазон IPv4, как и в расширенном правиле для Rocky. Оно по умолчанию закрыто, что является безопасным вариантом, но это может вас удивить, если вы администрируете систему через IPv6, поскольку консоль просто перестанет отвечать. Добавьте эквивалентное правило v6 для вашего префикса управления, если вы подключаетесь именно таким образом.
В случае Docker эквивалентом является сужение DNS_SERVER_WEB_SERVICE_LOCAL_ADDRESSES от 0.0.0.0 до адреса интерфейса управления. При использовании сетевых настроек хоста брандмауэр хоста применяется как обычно, поэтому приведенные выше правила ufw или firewalld также охватывают этот случай.
Находясь в разделе «Настройки», на вкладке «Веб-сервис» можно привязать консоль к одному интерфейсу, а не ко всем адресам, а также настроить TLS для консоли. Technitium требует файл PKCS#12 .pfx с включённым закрытым ключом, а certbot такой файл не генерирует. Именно на этапе преобразования терпит неудачу большинство попыток, поэтому вот инструкция:
sudo openssl pkcs12 -export \
-out "/etc/dns/${DNS_FQDN}.pfx" \
-inkey "/etc/letsencrypt/live/${DNS_FQDN}/privkey.pem" \
-in "/etc/letsencrypt/live/${DNS_FQDN}/fullchain.pem" \
-passout pass:CHANGE_THIS_PFX_PASSWORD
Это sudo важно для обеих команд: certbot создаёт /etc/letsencrypt/live в 07:00 и privkey.pem в 0600, оба принадлежат пользователю root. Затем чаще всего возникают две проблемы. Пакет должен действительно содержать закрытый ключ, и файл должен быть доступен для чтения пользователю dns-server :
sudo openssl pkcs12 -in "/etc/dns/${DNS_FQDN}.pfx" -nodes -passin pass:CHANGE_THIS_PFX_PASSWORD \
| grep -c 'BEGIN PRIVATE KEY'
sudo chown dns-server:dns-server "/etc/dns/${DNS_FQDN}.pfx"
sudo chmod 600 "/etc/dns/${DNS_FQDN}.pfx"
Вам нужен именно 1 . Подсчитывайте количество ключей, а не всех объектов PEM, поскольку fullchain.pem содержит конечный сертификат плюс как минимум один промежуточный, поэтому настоящий пакет Let’s Encrypt содержит три объекта, а только один самоподписанный сертификат будет содержать два.
Затем укажите его веб-сервису в разделе «Настройки», «Веб-сервис»: установите флажок «Включить HTTPS», укажите путь к файлу сертификата TLS и пароль сертификата TLS; порт HTTPS по умолчанию — 53443. Прочитайте следующее предложение, прежде чем уходить, потому что именно эта часть заставляет многих запутаться. Включение HTTPS добавляет слушатель на порту 53443; порт 5380 не отключается и продолжает обслуживать консоль по простому HTTP. Установите галочку «Включить перенаправление с HTTP на HTTPS» , иначе незашифрованная консоль, которую вы только что с таким трудом защитили брандмауэром, по-прежнему будет работать на порту 5380. При включенном перенаправлении порт 5380 продолжает прослушивать, но вместо консоли отвечает перенаправлением.
Одно уточнение по этому шагу: преобразование, проверка ключа и проверка владения были выполнены с использованием локально сгенерированного сертификата и ключа, а не реального сертификата, выданного Let’s Encrypt, поэтому рассматривайте пути certbot как образец, а не как зафиксированный ход выполнения. Автоматизация-экспорта при каждом продлении, а также предоставление DoH и DoT клиентам, а не только защита консоли, — это более сложная задача, которой будет посвящено отдельное руководство в этой серии.
Настройка резолвера: DoH-источники, списки блокировки, локальная зона
Резолвер без политики выбора источников, без списков и без локальной зоны — это просто кэш. Три настройки превращают его в то, что вам действительно нужно.
В первую очередь настраиваются перенаправители. Technitium принимает URL-адрес и IP-подсказку в скобках, что позволяет ему достичь конечной точки DoH без необходимости сначала искать собственное имя конечной точки с помощью работающего резолвера. Эту деталь начальной настройки легко упустить, и именно поэтому адреса указаны здесь:
https://cloudflare-dns.com/dns-query (1.1.1.1)
https://dns.quad9.net/dns-query (9.9.9.9)
Установите протокол DNS-over-HTTPS в разделе «Настройки», затем «Прокси и перенаправители». Функция «Одновременная переадресация» включена по умолчанию с количеством одновременных запросов, равным 2, что означает отправку запросов к обоим исходным серверам и выбор того, который ответит первым:
Одно замечание, которое выдает консоль, но которое не упоминается в большинстве руководств: https поддерживает только DoH через HTTP/1.1 и HTTP/2. Для HTTP/3 нужно указывать h3:// , причём в случае сбоя соединения резервный протокол не используется. Выбирайте этот вариант сознательно, а не случайно.
Списки блокировки находятся в разделе «Настройки», затем «Блокировка». Добавьте URL-адреса в список, оставьте тип блокировки «NX Domain», и сервер будет возвращать NXDOMAIN для совпадающих имен, а не перенаправлять их на адрес «черной дыры»:
Списки обновляются по таймеру, но вы можете принудительно запустить обновление, не дожидаясь его. На лабораторном сервере один список StevenBlack загрузил 99 275 зон:
curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://${DNS_HOST}:5380/api/settings/forceUpdateBlockLists"
Последней идет ваша собственная зона, и именно здесь Technitium начинает доказывать своё преимущество перед Pi-hole. Зоны, «Добавить зону», «Основная», затем добавьте записи. Шесть кликов дают вам то же, что и файл зоны плюс перезагрузка в BIND:
Меню DNSSEC на той же странице зоны позволяет подписать её, а настройки передачи зоны поддерживают протоколы TCP, TLS и QUIC с ключами TSIG. BIND 9.18 также осуществляет передачу зон по TLS в обоих направлениях, поэтому справедливое утверждение звучит более узко, чем это обычно описывается в большинстве статей: QUIC — это транспортный протокол, который BIND не поддерживает. Первичные и вторичные зоны с подписью достаточно сложны, чтобы заслуживать отдельного руководства, поэтому данное руководство ограничивается первичной зоной с поддержкой подписи.
Проведите надлежащее тестирование
Стоит проверить четыре варианта поведения, и каждый из них даёт сбой по-своему, поэтому запускайте их по отдельности, а не предполагайте, что всё работает dig google.com доказывает что-либо:
dig +short @${DNS_HOST} www.${ZONE}
dig @${DNS_HOST} doubleclick.net | grep -oE 'status: [A-Z]+'
dig +dnssec @${DNS_HOST} cloudflare.com | grep -m1 -oE 'flags: [a-z ]+'
dig +short @${DNS_HOST} github.com
Авторитетный ответ из вашей собственной зоны, NXDOMAIN для заблокированного имени, флаг ad , подтверждающий, что вышестоящая цепочка проверила DNSSEC, и обычный рекурсивный ответ:
10.0.1.50
status: NXDOMAIN
flags: qr rd ra ad
140.82.121.4
Затем убедитесь, что вы не создали открытый резолвер. Этот флаг означает лишь то, что запрос поступил с адреса источника, не охваченного списком ACL, поэтому повторный запуск с того же хоста, на котором вы проводили тестирование, ничего не доказывает. Либо используйте второй компьютер в подсети, которую вы не указали, либо добавьте запасной адрес за пределами диапазона и отправьте запрос с этого источника. dig -b может привязываться только к адресу, которым хост фактически располагает, поэтому сначала добавьте его:
IFACE=$(ip route show default | awk '{print $5; exit}')
sudo ip addr add 10.0.2.99/24 dev "${IFACE}"
dig -b 10.0.2.99 @${DNS_HOST} example.com | grep -oE 'status: [A-Z]+'
sudo ip addr del 10.0.2.99/24 dev "${IFACE}"
REFUSED — это условие успешного прохождения теста. Получение ответа означает, что ACL шире, чем вы думаете, и если у сервера также есть публичный адрес, то вы только что обнародовали источник амплификации. Пустой вывод не является успешным прохождением: это означает, что ответ не смог пройти обратно к этому вымышленному источнику, поэтому повторите тест с хоста, на который сервер действительно может ответить:
status: REFUSED
Панель мониторинга — это вторая половина теста, поскольку она показывает, работает ли сервер так, как вы предполагаете. После запуска скрипта, смешавшего обычные запросы и запросы рекламной сети, счетчики распределились так, как и должно быть: в основном попадания в кэш, пятая часть запросов заблокирована, а часть получила авторитетный ответ из локальной зоны.
Прокрутите вниз — таблица по клиентам покажет, сохранилась ли идентичность клиентов при вашем развертывании. Наличие здесь одного реального LAN-адреса вместо шлюза контейнера — это разница между полезной и бесполезной статистикой:
После подтверждения поведения возникает следующий вопрос: влечёт ли выбор пути установки за собой какие-либо измеримые потери?
Влияет ли метод установки на задержку запросов?
Нет. При измерении с одного и того же клиента для всех четырёх вариантов установки медианы неотличимы, и Docker не является самым медленным:
| Путь установки | Авторитетный | Кэшированный | Некешированный | В оперативной памяти |
|---|---|---|---|---|
| Ubuntu, «bare metal» | 1,0 мс | 1,0 мс | 127 мс | 176 МБ |
| Debian, «bare metal» | 1,0 мс | 1,0 мс | 131 мс | 163 МБ |
| Rocky Linux, «bare metal» | 1,0 мс | 1,0 мс | 129 мс | 167 МБ |
| Docker, сеть хоста | 1,0 мс | 1,0 мс | 129 мс | 163 МБ |
Выбирайте тот вариант, который лучше вписывается в вашу систему управления остальной инфраструктурой, а не тот, который вам кажется более быстрым. Показатели памяти — вот настоящий компромисс: от 163 МБ до 176 МБ — это минимальный порог для среды выполнения .NET, а Unbound или dnsmasq справятся с задачей, связанной исключительно с разрешением имен, за гораздо меньший объём памяти.
Какой транспорт для передачи данных вверх по потоку обходится дешевле всего?
DoT фактически бесплатен по сравнению с обычным UDP, а DoH занимает примерно от 10 до 30 мс плюс худший хвост. Честно говоря, чтобы получить этот ответ, потребовалось две попытки, и первая из них была ошибочной — об этом стоит рассказать, поскольку это лёгкая ловушка.
Запрос случайных субдоменов с целью вынудить промахи кеша измеряет не то, что нужно: он заставляет верхний уровень к полному прохождению цепочки делегаций, из-за чего время каждого транспорта достигало примерно 125 мс, а ранжирование менялось от прогона к прогону. Решением является очистка локального кэша перед каждым запросом и запрос популярных имен, которые верхний уровень уже кэшировал, так что в итоге остаются в основном наши собственные транспортные затраты. При использовании этого метода было получено 40 выборок для каждого режима, три прогона на двух хостах:
| Транспорт | Медиана прогона 1 | Медиана прогона 2 | Медиана прогона 3 | Наихудший наблюдавшийся случай |
|---|---|---|---|---|
| DNS-over-HTTPS | 88 мс | 78 мс | 68 мс | 261 мс |
| DNS-over-TLS | 65 мс | 56 мс | 55 мс | 79 мс |
| Обычный UDP | 60 мс | 61 мс | 58 мс | 96 мс |
| Обычный TCP | 56 мс | 56 мс | 62 мс | 71 мс |
Эти медианы получены с помощью тестового набора, который управляет сервером через его собственный API и очищает кэш между запросами, поэтому абсолютные значения ниже, чем показатели без кэширования в предыдущей таблице, которые были измерены с помощью dig со стороны клиента. Сравнивайте транспортные протоколы друг с другом, а не с другой таблицей.
Два вывода выдерживают тщательную проверку. DoH оказался самым медленным во всех трёх прогонах и демонстрирует худшие результаты в конце распределения. DoT, UDP и TCP находятся в диапазоне от 55 мс до 65 мс, а разрывы между этими тремя протоколами укладываются в пределы шума между прогонами, поэтому здесь не утверждается никакой иерархии между ними. Выбирайте DoH, потому что он проходит через брандмауэры и противостоит проверкам, а не потому, что он быстр. Если вам нужны зашифрованные восходящие соединения с минимальной задержкой, выбирайте DoT; DNSCrypt — это другой подход к решению той же задачи.
Переход с Pi-hole или BIND без перерыва в разрешении имен
Оба варианта миграции проходят по одной и той же схеме: запустите Technitium параллельно с тем, что он заменяет, перенесите конфигурацию, настройте один клиент на него, а затем переключите сеть. Ничто не должно отключаться.
При переходе с Pi-hole стоит перенести два элемента: списки рекламы и локальные записи DNS. Pi-hole хранит URL-адреса списков в собственной базе данных, и они попадают прямо в поле «URL-адреса списка блокировки» Technitium. В этом запросе важны две детали: используйте pihole-FTL sqlite3 вместо отдельного sqlite3, который Pi-hole не устанавливает, и фильтруйте по type=0, поскольку строки type=1 представляют собой списки разрешенных доменов. Скопируйте их в поле списка блокировки, и вы заблокируете домены, которые намеревались разрешить.
sudo pihole-FTL sqlite3 /etc/pihole/gravity.db \
"select address from adlist where enabled=1 and type=0;"
Записи локального DNS Pi-hole становятся записями в основной зоне Technitium. Если их больше, чем несколько, не вводите их заново, так как API принимает файл зоны напрямую (об этом рассказывается в следующем разделе). В нашем руководстве «Pi-hole в Docker» указаны пути, если вы переносите систему с контейнерной установки.
Переход с BIND требует меньше усилий, поскольку Technitium импортирует файлы зон RFC 1035 без изменений. Создайте зону, а затем отправьте файл методом POST на конечную точку импорта с помощью text/plain следующим телом запроса:
curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://${DNS_HOST}:5380/api/zones/create?zone=corp.example.com&type=Primary"
curl -s -X POST -H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: text/plain" \
--data-binary @/var/named/corp.example.com.zone \
"http://${DNS_HOST}:5380/api/zones/import?zone=corp.example.com&overwrite=true&overwriteSoaSerial=true"
Файл зоны из девяти записей с записями A, CNAME, MX, NS и SOA импортирован без единого изменения, а серийный номер SOA перенесён без изменений:
corp.example.com SOA ns1.corp.example.com / [email protected] / serial 2026080301
corp.example.com MX 10 mail.corp.example.com
api.corp.example.com CNAME www.corp.example.com
www.corp.example.com A 10.0.1.20
db01.corp.example.com A 10.0.1.31
Обратите внимание, что overwriteSoaSerial имеет значение, если вторичные серверы отслеживают изменения. Установка серийного номера ниже текущего останавливает их синхронизацию, поэтому в активной зоне лучше позволить Technitium сохранить свой собственный серийный номер. Существующие пары первичных и вторичных серверов BIND могут продолжать работать во время перехода, при этом Technitium будет выступать в качестве дополнительного вторичным сервером, пока вы не будете готовы передать ему роль первичного.
Что касается самого переключения, не поддавайтесь соблазну сначала изменять опцию DHCP. Вручную настройте одну рабочую станцию на новый сервер, поработайте с ней в течение дня и только потом обновите резолвер, объявляемый через DHCP. Оставьте старый сервер работающим до тех пор, пока не истекут сроки аренды клиентов, поскольку исчезновение резолвера в середине срока аренды приводит к появлению заявок в службу поддержки, которые выглядят как сбои приложений.
Автоматизируйте этот процесс с помощью HTTP-API
Всё, что делает консоль, представляет собой вызов API, и начиная с версии 15.0 аутентификация осуществляется с помощью токена bearer в заголовке. Токен сеанса, полученный при входе в систему, имеет ограниченный срок действия; для скриптов вам понадобится именованный токен, срок действия которого не ограничен:
curl -s "http://${DNS_HOST}:5380/api/user/createToken?user=admin&pass=${TDNS_PASS}&tokenName=automation"
Ответ содержит токен длиной 64 символа. Храните его так же, как и любые другие учетные данные, поскольку он эквивалентен доступу через консоль:
{"username":"admin","tokenName":"automation","token":"REDACTED_64_CHAR_TOKEN","status":"ok"}
После этого добавление записей становится идемпотентным, если вы это допустите. Добавление уже существующей записи не вызывает ошибки, поэтому скрипт, запускаемый при каждом развертывании, сходится, а не завершается с ошибкой:
AUTH="Authorization: Bearer ${TOKEN}"
API="http://${DNS_HOST}:5380/api"
for HOST_REC in "web01:10.0.1.20" "db01:10.0.1.31" "cache01:10.0.1.35"; do
NAME="${HOST_REC%%:*}"; ADDR="${HOST_REC##*:}"
curl -s -H "${AUTH}" \
"${API}/zones/records/add?zone=${ZONE}&domain=${NAME}.${ZONE}&type=A&ttl=3600&ipAddress=${ADDR}&overwrite=true"
echo " ${NAME}.${ZONE} -> ${ADDR}"
done
Тот же паттерн применим к операционным вызовам, которые вам понадобятся в CI: /api/cache/flush когда вы изменили ответ, который уже был кэширован (совершенно новая запись не требует очистки кэша, поскольку кэш хранит рекурсивные ответы, а не ваши собственные зоны), /api/settings/forceUpdateBlockLists по расписанию, а также /api/zones/export для хранения файла зоны в Git. Также предусмотрен доступ к журналу запросов, но он относится к приложению для ведения журнала DNS, а не к основному серверу, поэтому для его использования необходимо установить это приложение и передать его путь к классам. сопутствующий репозиторий содержит все это в виде запускаемых скриптов, а также оба файла Compose, задание Prometheus, панель Grafana и тест производительности транспортного протокола.
Настройка в производственной среде: резервное копирование, обновление и метрики Prometheus
Всё состояние сервера Technitium хранится в одном каталоге, что делает резервное копирование приятно простым делом. Зоны, статистика, кэши списков блокировок, конфигурация аутентификации, области DHCP и настройки DNS находятся в каталоге /etc/dns, поэтому можно либо заархивировать сам каталог, либо воспользоваться API и получить ZIP-архив. Версия API принимает по одному флагу на каждую категорию, и по умолчанию они отключены, поэтому нужно указать всё, что действительно нужно:
curl -s -H "Authorization: Bearer ${TOKEN}" -o tdns-backup.zip \
"http://${DNS_HOST}:5380/api/settings/backup?dnsSettings=true&zones=true&authConfig=true&blockLists=true&allowedZones=true&blockedZones=true&scopes=true&stats=true&apps=true"
ls -lh tdns-backup.zip
На лабораторном сервере это дало архив размером 723 КБ, большую часть которого занимал кэшированный список блокировок. Восстановление осуществляется с помощью соответствующего /api/settings/restore вызова, и стоит протестировать его на чистой установке, прежде чем он понадобится вам в 2 часа ночи.
Обновление осуществляется либо повторным запуском того же установщика на «голом железе» (при этом конфигурация сохраняется), либо с помощью изменения тега и docker compose up -d в контейнере. Оба способа сохраняют /etc/dns. Один побочный эффект, о котором следует знать при обновлении на «голом железе»: резервное копирование и перезапись файла resolv.conf выполняются безоговорочно при каждом запуске, поэтому обновление перезаписывает resolv.conf.bak файлом, созданным самим установщиком. В Rocky это уничтожает единственную действительно полезную копию, которая у вас была, поэтому перед обновлением сделайте собственную резервную копию. Незначительная особенность, на которую можно не обращать внимания: при первой установке всегда выводится сообщение «Updating Technitium DNS Server», поскольку скрипт создаёт каталог конфигурации до того, как проверит, существует ли он уже.
Названия метрик Prometheus не соответствуют документации
Technitium предоставляет метрики Prometheus в текстовом формате по адресу /api/dashboard/metrics/text, и для этого конечного пункта требуется тот же токен bearer, что и для остальной части API. Разработчик отмечает его как экспериментальный и оставляет за собой право вносить изменения, поэтому зафиксируйте версию, на которой вы создавали дашборды. Проблема заключается в названиях метрик. В документации API перечислены total_queries, total_blocked и другие; работающий сервер выдает формы с суффиксами. Если запрашивать имена, указанные в документации, ваши панели будут возвращать NO DATA, хотя всё выглядит правильно настроенным.
Спросите у сервера, что он на самом деле публикует, вместо того чтобы доверять либо документации, либо этой статье:
curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://${DNS_HOST}:5380/api/dashboard/metrics/text" | grep -v '^#' | awk '{print $1}'
Тринадцать серий. Одиннадцать счетчиков ставят _total в конце, а не в начале, и в этом вся проблема:
queries_total
no_error_total
server_failure_total
nx_domain_total
refused_total
authoritative_total
recursive_total
cached_total
blocked_total
dropped_total
clients_total
uptime_seconds
start_time
Добавьте задание сбора данных с токеном в качестве учетных данных типа «bearer». Откройте конфигурацию Prometheus:
sudo vim /etc/prometheus/prometheus.yml
Неочевидная часть заключается в том, что metrics_path, поскольку конечная точка не находится по адресу /metrics:
scrape_configs:
- job_name: technitium
metrics_path: /api/dashboard/metrics/text
static_configs:
- targets: ["10.0.1.53:5380"]
authorization:
type: Bearer
credentials: "PASTE_THE_API_TOKEN_HERE"
Перезагрузите Prometheus и убедитесь, что цель работает, прежде чем приступать к созданию панелей на её основе:
curl -s http://localhost:9090/api/v1/targets | grep -oE '"health":"[a-z]+"'
Четыре выражения охватывают все важные панели. Частота запросов, частота блокировок, эффективность кэша и разбивка результатов — все они построены на основе счетчиков, поэтому rate() выполняют всю работу. Значение clamp_min в коэффициенте предотвращает деление панели на ноль, когда сервер находится в режиме простоя:
rate(queries_total[1m])
rate(blocked_total[1m])
rate(cached_total[5m]) / clamp_min(rate(queries_total[5m]), 0.0001)
rate(no_error_total[1m]) # repeat for nx_domain_total, refused_total, server_failure_total
При реальной нагрузке запросов вы получаете картину, которую не может отобразить консоль: показатели во времени, а не значения счетчиков с момента запуска, с 19,7 запросов в секунду, 4,37 заблокированных в секунду и коэффициентом попадания в кэш 68,7 процента в этом прогоне.
Показатель refused_total заслуживает не панели, а оповещения. Резолвер, который внезапно начинает отклонять запросы, обычно означает, что клиент перешёл в подсеть, не охваченную вашим ACL, и это гораздо проще заметить на графике, чем в заявке в службу поддержки. В нашем руководстве по мониторингу DNS-серверов с помощью Prometheus и Grafana рассматриваются правила оповещений и подход с использованием экспортера для серверов, которые не публикуют метрики сами.
Ошибки, возникшие в ходе этого практического занятия, и способы их устранения
Все приведённые ниже сообщения были получены с тестовых машин, а не из системы отслеживания проблем.
Ошибка: «Не удалось привязать порт хоста 0.0.0.0:53/tcp: адрес уже используется»
systemd-resolved удерживает заглушковый прослушиватель на порту 53. Добавьте DNSStubListener=no drop-in из раздела Docker и перезапустите resolved. Не отключайте resolved полностью, если у вас нет на то веских причин.
Каждый запрос возвращает REFUSED, в том числе с хоста Docker
Мостовая сеть плюс ACL, в котором указана ваша реальная локальная сеть. Docker переписывает исходный адрес на шлюз Compose, поэтому ни один клиент не попадает под соответствие. Переключитесь на network_mode: "host". Расширение списка ACL на подсеть моста — заманчивое, но неправильное решение, поскольку оно разрешает доступ любому, кто может связаться с портом.
Ошибка: «Ошибка связи с 10.0.1.53#53: хост недоступен»
Это сетевая ошибка, а не ошибка DNS, и в этом заключается подсказка. В Rocky Linux службе firewalld не указано, что касается DNS. Примените правила firewalld из раздела, посвящённого Rocky Linux. Если резолвер отвечает правильно на самом компьютере, но нигде больше, то вина всегда лежит на брандмауэре.
Ошибка: «аргумент –remove-port: не допускается с аргументом –remove-service»
firewall-cmd не допускает смешивания аргументов service и port в одном вызове, в любом направлении. Разделите их, используя по одному флагу на вызов.
Ошибка: «cat: /opt/technitium/dns/resolv.conf.bak: Такого файла или каталога нет»
Файл существует в виде символьной ссылки; цель этой ссылки отсутствует. Причину см. в разделе по установке Ubuntu и Debian; используйте средство восстановления systemd-resolved вместо поиска содержимого резервной копии.
Программа установки выводит сообщение «Не найден конкретный пакет libicu»
Само по себе это безобидно, но означает, что вот-вот запустится установка с использованием подстановочных знаков, в результате чего будет загружено около 137 МБ ненужных пакетов. Прервите процесс, установите пакет ICU, который предлагает apt, и запустите установку заново. Установщик является идемпотентным.
На панелях Grafana отображается NO DATA при исправной целевой величине Prometheus
Используйте задокументированные имена метрик вместо выводимых. Используйте queries_total вместо total_queries, и проверьте актуальный список с помощью приведённой выше команды metrics, а не полагайтесь на какие-либо документы, включая этот.
Какой путь установки выбрать
Поскольку задержка и объем памяти оказались одинаковыми для всех четырёх вариантов, решение зависит от того, как вы хотите управлять системой в течение следующих двух лет.
| Ситуация | Путь | Почему |
|---|---|---|
| Вам нужна воспроизводимая конфигурация в Git | Docker с сетевыми настройками хоста | Весь сервер объявлен в файле Compose; нет операций, выполняемых одним щелчком мыши, которые нужно документировать |
| Существующий парк серверов семейства RHEL с политикой | Rocky Linux, «голый» сервер | Подходит для существующего процесса установки исправлений, но необходимо предусмотреть в бюджете работу с firewalld и учитывать, что сервис работает без ограничений |
| Хост на базе Debian или Ubuntu, без среды выполнения контейнеров | Установщик на «голом железе» | Самый быстрый способ получить работающий резолвер; предварительно установите ICU и знайте, как восстановить resolv.conf |
| Вам нужен DHCP с того же хоста | Docker с сетевыми настройками хоста или «bare metal» | DHCP требует сетевого пространства имён хоста для просмотра широковещательных сообщений |
Что бы вы ни выбрали, две вещи, которые нужно настроить правильно в первый же день, — это ACL рекурсии и пароль, который не является admin. Большинство остальных настроек можно изменить позже через консоль, хотя любые изменения, затрагивающие привязку веб-сервиса или его сертификат, приведут к перезапуску этого сервиса. Именно эти два момента определяют разницу между резолвером, который вы запускаете, и резолвером, который может стать объектом атаки с усилением трафика.