Настройка основного и резервного DNS-серверов с автоинкрементом серийного номера зоны на базе PowerDNS


Администрирование операционных систем на базе Linux (Debian/Ubuntu и Centos/RedHat) Сложные сетевые решения (VPN/Routing и т.п.) Администрирование серверов баз данных Postgresql Мое портфолио, сертификаты и разработки
content cv debian dns domain gita gita-dev.ru powerdns-admin обновление dns
 
 

* В этом блоге я описываю свою повседневную рабочую практику, поэтому все статьи в блоге написаны лично мной и при копировании их на свой сайт пожалуйста указывайте ссылку на страницу откуда вы скопировали.
* Если какая-то статья вам помогла, то вы можете дать мне немного денег вместо простого спасибо (ссылка на форму поддержки проекта внизу страницы), если вы что-то не поняли или у вас что-то не получается, то вы можете нанять меня и я вам все подробно расскажу (расценки и ссылки в конце статьи).


(последние правки 3 недели, 4 дня)

Как вы наверное знаете, вам совсем не обязательно использовать DNS-сервера провайдера для управления вашим доменом и вы можете осуществлять хостинг DNS-записей на своих собственных DNS-серверах. Такой подход дает большую гибкость в управлении DNS-зоной, но и настройка DNS-серверов работающих в режиме MASTER-SLAVE это не самая тривиальная задача. Если вы все же решили изучить этот вопрос, то вы наверное обратили внимание, что 90% статей сводятся к настройке двух DNS-серверов Bind в режиме ведущий-ведомый и может показаться, что bind это единственный Opensource DNS-сервер.

Около трех лет назад, я решал довольно интересную задачу по реализации отказоустойчивого кластера web-серверов и одним из компонентов этой системы был DNS-сервер на базе PowerDNS с хранением данных в базе СУБД Postgresql. Этот элемент отвечал за оперативное переключение DNS-записей Front-узлов Nginx на случай DDos-атаки и выдачу гео-зависимых адресов Front-узлов исходя из IP-адреса запрашивающего.

Сейчас назревает похожий проект и я подумал, а почему бы мне не использовать старые наработки и не освежить в памяти как это вообще было. Тестировать можно на собственных серверах, я все равно хотел настроить Wildcard SSL для Let's Encrypt, а для этого мне в любом случае понадобится подконтрольный мне DNS-сервер, а не сервер провайдера.

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

Установка и базовая настройка PowerDNS

Установка необходимых пакетов:

# aptitude install pdns-backend-pgsql pdns-server

Разворачиваем схему данных в базу:

# cat /usr/share/doc/pdns-backend-pgsql/schema.pgsql.sql | psql -U web_portal -h 127.0.0.1 web_portal_db

Настраиваем конфигурационный файл /etc/powerdns/pdns.d/pdns.local.gpgsql.conf, он содержит параметры подключения к базе данных (настройте его на подключение к базе куда мы загрузили схему данных):

# PostgreSQL Configuration 
# 
# Launch gpgsql backend 
launch+=gpgsql 
 
# gpgsql parameters 
gpgsql-host=localhost 
gpgsql-port=5432 
gpgsql-dbname=pdns_db 
gpgsql-user=pdns_user 
gpgsql-password=xxxPasswordxxx 
gpgsql-dnssec=yes

Так как наш PowerDNS сервер будет выступать в качестве мастер-сервера в центральную конфигурацию (/etc/powerdns/pdns.conf) обязательно добавьте параметр:

master=yes

Вносим в базу данных записи о обслуживаемых доменах и DNS-записях, это можно делать вручную или использовать несколько WEB-интерфейсов для управления DNS-сервером.

  • PowerAdmin - web-интерфейс написанный на PHP предназначенный для администрирования PowerDNS при помощи WEB-интерфейса, официальная страница на GITHUB - https://github.com/poweradmin/poweradmin
  • PowerDNS-Admin - еще один web-интерфейс теперь уже написанный на Python и упакованный в Docker-контейнер. Официальная страница на том же GITHUB - https://github.com/ngoduykhanh/PowerDNS-Admin
  • DjangoPowerDNS - как вы наверное поняли из названия, еще одна web-мордочка, но теперь уже написанная на Django, но она использует Python 2.7 и это уже легаси и должно быть переписано на Python 3

Проще всего конечно запустить PowerDNS-Admin так как он изначально задумывался как Docker-контейнер (хотя конечно можно и без контейнера его запустить), но я предпочитаю не использовать Docker там где можно обойтись.

Запуск PowerDNS-Admin без использования Docker-контейнера

Официальную инструкцию вы можете найти по адресу: https://github.com/ngoduykhanh/PowerDNS-Admin/wiki/Running-PowerDNS-Admin-on-Ubuntu-16.04---Ubuntu-18.04, я в свою очередь опишу последовательность установки которую я применял когда запускал этот проект.

Устанавливаем необходимые пакеты:

# apt-get install python3-dev libsasl2-dev libldap2-dev libssl-dev libxml2-dev \
  libxslt1-dev libxmlsec1-dev libffi-dev pkg-config python3-virtualenv

Устанавливаем Yarn:

# curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
# echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
# apt-get update -y
# apt-get install -y yarn

Клонируем проект из репозитария и создаем виртуальное окружение:

# git clone https://github.com/ngoduykhanh/PowerDNS-Admin.git /opt/web/powerdns-admin
# cd /opt/web/powerdns-admin
# virtualenv -p python3 flask
# source ./flask/bin/activate
# pip install -r requirements.txt
# cp ./config_template.py ./config.py

Отредактируйте файл config.py (подключение к базе и т.п.), после чего инициализируйте базу данных:

# export FLASK_APP=app/__init__.py
# flask db upgrade
# flask db migrate -m "Init DB"
# yarn install --pure-lockfile
# flask assets build
# ./run.py

Большой минус, это то что web-интерфейс использует базу данных исключительно mysql. После тестового запуска web-интерфейс будет доступен по адресу http://localhost:9191.

Заполнение базы данных вручную и тестирование работы PDNS

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

INSERT INTO domains (name, type) values ('gita-dev.ru', 'MASTER');
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'gita-dev.ru','ns1.gita-dev.ru. anton.gita-dev.ru. 100080 10380 3600 604800 3600','SOA',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'gita-dev.ru','ns1.gita-dev.ru','NS',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'gita-dev.ru','ns2.gita-dev.ru','NS',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'ns1.gita-dev.ru','80.211.102.101','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'ns2.gita-dev.ru','94.177.204.179','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'gita-dev.ru','80.211.102.101','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'www.gita-dev.ru','80.211.102.101','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'mail.gita-dev.ru','94.177.204.179','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'gita-dev.ru','mail.gita-dev.ru','MX',3600,10);

Проверяем, что pdns-сервер работает корректно при помощи двух команд dig и nslookup. Классический DNS-запрос A-записи у конкретного DNS-сервера выглядит следующим образом:

# nslookup www.gita-dev.ru 127.0.0.1

В современных дистрибутивах по умолчанию уже нет nslookup, а используется более мощная команда dig и запрос MX-записей у локального DNS-сервера будет выполнен вот такой командой:

# dig MX gita-dev.ru @127.0.0.1

В результате выполнения команды вы получите более развернутый ответ от DNS-сервера:

; <<>> DiG 9.10.3-P4-Ubuntu <<>> MX gita-dev.ru @127.0.0.1 
;; global options: +cmd 
;; Got answer: 
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12244 
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 2 
;; WARNING: recursion requested but not available 
 
;; OPT PSEUDOSECTION: 
; EDNS: version: 0, flags:; udp: 1680 
;; QUESTION SECTION: 
;gita-dev.ru.                  IN     MX 
 
;; ANSWER SECTION: 
gita-dev.ru.           3600   IN     MX     10 mail.gita-dev.ru. 
 
;; ADDITIONAL SECTION: 
mail.gita-dev.ru.      3600   IN     A      94.177.204.179 
 
;; Query time: 4 msec 
;; SERVER: 127.0.0.1#53(127.0.0.1) 
;; WHEN: Sun Aug 05 05:31:56 CEST 2018 
;; MSG SIZE rcvd: 77

Настройка MASTER-SLAVE связки DNS серверов PDNS и Bind

В качестве резервного DNS-сервера я буду использовать уже упомянутый в начале статьи Bind, хотя вы можете пойти более интересным путем и настроить потоковую или логическую репликацию хранилища данных Postgresql и использовать его как источник данных для аналогичного PowerDNS-сервера (про потоковую репликацию я уже писал в статье: https://gita-dev.ru/blog/nastrojka-potokovoj-replikatsii-postgresql-servera-wal-replikatsija).

Итак, мы выбрали классический путь и начнем с настройки SLAVE-сервера на базе Bind, для чего естественно его надо установить:

# aptitude install bind9

Не забудьте открыть в Firewall 53-ий UDP-порт (и TCP-порт если используется).

Итак, что нам сейчас требуется сделать:

  • Описать DNS-зону
  • Разрешить передачу зоны с мастер-сервера
  • На мастере настроить оповещения о том, что зона обновлена

Для того чтобы описать SLAVE-зону вам потребуется создать следующую запись в файле /etc/bind/named.conf:

zone "gita-dev.ru" { 
   type slave; 
   file "gita-dev.ru.db"; 
   masters { 93.170.131.222; }; 
};

Если вы сейчас выполните перезапуск DNS сервера BIND, то в логах вы увидите ошибку авторизации на MASTER-сервере, что совершенно логично, так как совершенно незачем передавать кому попало информацию о DNS-записях:

Aug 6 09:38:13 mail named[6329]: zone gita-dev.ru/IN: Transfer started. 
Aug 6 09:38:13 mail named[6329]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: connected using 94.177.204.179#43437 
Aug 6 09:38:13 mail named[6329]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: failed while receiving responses: NOTAUTH 
Aug 6 09:38:13 mail named[6329]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: Transfer status: NOTAUTH 
Aug 6 09:38:13 mail named[6329]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: Transfer completed: 0 messages, 0 records, 0 bytes, 0.006 secs (0 bytes/sec)

Как вы понимаете, зону мы описали и теперь надо настроить трансфер зоны с MASTER на SLAVE и для этого вернемся на MASTER-сервер и изменим один параметр конфигурационного файла /etc/powerdns/pdns.conf:

allow-axfr-ips=127.0.0.0/8,::1,94.177.204.179/32

Потребуется перезапуск PDNS-сервера:

# /etc/init.d/pdns restart

Перезапускаем Bind на SLAVE-сервере и проверяем лог-файл:

Aug 6 09:45:15 mail named[6373]: zone gita-dev.ru/IN: Transfer started. 
Aug 6 09:45:15 mail named[6373]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: connected using 94.177.204.179#34511 
Aug 6 09:45:15 mail named[6373]: zone gita-dev.ru/IN: transferred serial 100080 
Aug 6 09:45:15 mail named[6373]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: Transfer status: success 
Aug 6 09:45:15 mail named[6373]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: Transfer completed: 3 messages, 10 records, 320 bytes, 0.083 secs (3855 bytes/sec)

Вот теперь зона передана успешно и мы можем попробовать запросить сведения о DNS-записях уже у SLAVE-сервера:

$ dig www.gita-dev.ru @94.177.204.179 
 
; <<>> DiG 9.10.3-P4-Ubuntu <<>> www.gita-dev.ru @94.177.204.179 
;; global options: +cmd 
;; Got answer: 
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7613 
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3 
;; WARNING: recursion requested but not available 
 
;; OPT PSEUDOSECTION: 
; EDNS: version: 0, flags:; udp: 4096 
;; QUESTION SECTION: 
;www.gita-dev.ru.              IN     A 
 
;; ANSWER SECTION: 
www.gita-dev.ru.       3600   IN     A      80.211.102.101 
 
;; AUTHORITY SECTION: 
gita-dev.ru.           86400  IN     NS     ns2.gita-dev.ru. 
gita-dev.ru.           86400  IN     NS     ns1.gita-dev.ru. 
 
;; ADDITIONAL SECTION: 
ns1.gita-dev.ru.       3600   IN     A      80.211.102.101 
ns2.gita-dev.ru.       3600   IN     A      94.177.204.179 
 
;; Query time: 100 msec 
;; SERVER: 94.177.204.179#53(94.177.204.179) 
;; WHEN: Mon Aug 06 09:48:25 +07 2018 
;; MSG SIZE rcvd: 128

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

Кстати, на SLAVE-сервере мы не указывали полный путь к файлу где будет храниться база данных зоны у bind и по умолчанию в Ubuntu и Debian она размещается в файле:

/var/cache/bind/gita-dev.ru.db

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

После первичной синхронизации в таблице domains (базы данных Postgresql мастер-сервера) появится запись notified_serial с серийным номером зоны. Следующим этапом добавим DNS-запись в базу данных и инкрементируем серийный номер DNS-зоны:

INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'chat.gita-dev.ru','94.177.216.44','A',3600,NULL);
UPDATE public.records SET content='ns1.gita-dev.ru. anton.gita-dev.ru. 100181 10380 3600 604800 3600' WHERE id = 1;

Проверяем логи на SLAVE-сервере и видим, что синхронизация до номера серийного номера зоны 181 прошла в автоматическом режиме:

Aug 7 09:29:50 mail named[8342]: client 80.211.102.101#15741: received notify for zone 'gita-dev.ru' 
Aug 7 09:29:50 mail named[8342]: zone gita-dev.ru/IN: notify from 80.211.102.101#15741: no serial 
Aug 7 09:29:50 mail named[8342]: zone gita-dev.ru/IN: Transfer started. 
Aug 7 09:29:50 mail named[8342]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: connected using 94.177.204.179#43199 
Aug 7 09:29:50 mail named[8342]: zone gita-dev.ru/IN: transferred serial 100181 
Aug 7 09:29:50 mail named[8342]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: Transfer status: success 
Aug 7 09:29:50 mail named[8342]: transfer of 'gita-dev.ru/IN' from 80.211.102.101#53: Transfer completed: 3 messages, 12 records, 361 
bytes, 0.116 secs (3112 bytes/sec) 
Aug 7 09:29:50 mail named[8342]: zone gita-dev.ru/IN: sending notifies (serial 100181)

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

# pdns_control notify gita-dev.ru

Во вторых, рекомендуется использовать для номера зоны специальный формат записи (хотя можно и не обращать на это особого внимания):

example.com.  3600  SOA  dns.example.com. hostmaster.example.com. (
                         1999022301   ; serial YYYYMMDDnn
                         86400        ; refresh (  24 hours)
                         7200         ; retry   (   2 hours)
                         3600000      ; expire  (1000 hours)
                         172800 )     ; minimum (   2 days)

Автоинкремент серийного номера зоны при добавлении, удалении или изменении DNS-записей

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

INSERT INTO domains (name, type) values ('test.local', 'MASTER');
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'test.local','ns1.test.local. anton.test.local. 100080 10380 3600 604800 3600','SOA',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'test.local','ns1.test.local','NS',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'test.local','ns2.test.local','NS',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'ns1.test.local','80.211.102.101','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'ns2.test.local','94.177.204.179','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'test.local','80.211.102.101','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'www.test.local','80.211.102.101','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'mail.test.local','94.177.204.179','A',3600,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (2,'test.local','mail.test.local','MX',3600,10);

Так мы добавили домен test.local и естественно, что по аналогии мы можем добавить test2.local и test3.local

Автоинкремент серийного номера SOA-записи мы будем выполнять при помощи функции и нескольких триггеров, начнем с функции которая будет инкрементировать серийный номер SOA-записи по переданному ID-домена:

-- FUNCTION: public."IncDomainSerial"(integer)

-- DROP FUNCTION public."IncDomainSerial"(integer);

CREATE OR REPLACE FUNCTION public.IncDomainSerial(
	domid integer)
    RETURNS character varying
    LANGUAGE 'plpgsql'

    COST 100
    VOLATILE 
AS $BODY$
DECLARE
  soacursor CURSOR FOR SELECT id,content FROM records WHERE domain_id=$1 AND type='SOA' LIMIT 1;
  soarecid int;
  res_soaserial varchar;
  soacontent varchar;
  res_soacontent varchar;
BEGIN
   OPEN soacursor;
   FETCH FIRST FROM soacursor INTO soarecid,soacontent;
   CLOSE soacursor;
   res_soacontent=split_part(soacontent,' ', 1) || ' ' ||split_part(soacontent,' ', 2);
   res_soaserial=int4(split_part(soacontent,' ', 3))+1;
   UPDATE records SET content=res_soacontent || ' ' || res_soaserial WHERE id=soarecid;
   RETURN res_soaserial;
END;

$BODY$;

ALTER FUNCTION public."IncDomainSerial"(integer)
    OWNER TO web_portal;

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

SELECT public.IncDomainSerial(2);

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

-- FUNCTION: public.serialupdateondatachange()

-- DROP FUNCTION public.serialupdateondatachange();

CREATE FUNCTION public.serialupdateondatachange()
  RETURNS trigger
  LANGUAGE 'plpgsql'
  COST 100
  VOLATILE NOT LEAKPROOF 
AS $BODY$
BEGIN

IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE') THEN
  IF NEW.type != 'SOA' THEN
    PERFORM "IncDomainSerial"(NEW.domain_id);
    END IF;
    RETURN NEW;
END IF;

IF (TG_OP = 'DELETE') THEN
    PERFORM "IncDomainSerial"(OLD.domain_id);
    RETURN OLD;
END IF;

END;
$BODY$;

ALTER FUNCTION public.serialupdateondatachange()
  OWNER TO web_portal;

Финальным аккордом мы назначаем триггерную функцию таблице:

CREATE TRIGGER "UpdateSerial" BEFORE INSERT OR DELETE OR UPDATE ON records FOR EACH ROW EXECUTE PROCEDURE serialupdateondatachange();

Теперь проверяем, что все работает как мы и задумывали, а именно при добавлении DNS-записей значение серийного номера SOA-зоны автоматически увеличивается и мастер-слэйв домены автоматически синхронизируются.

Моя официальная страница на FaceBook
Мой микроблог в твиттер

Устранение проблем с разрешением DNS-имен для домена .local в современенных дистрибутивах Linux

Устранение проблем с разрешением DNS-имен для домена .local в современенных дистрибутивах Linux

То, что мы сегодня будем разбирать - это не проблема, а просто "песня". Такого рода вопросы очень любят мои коллеги, так как они предоставляют им возможность поспорить на тему баг это или фича, перетряхнуть древние записи на Stack Overflow, разлиться мыслью по древу или банально затеять обсуждение на пару сотен комментариев под заданным в профильной группе вопросом.


Автоматическое обновление DNS-записи рабочей станции Linux при вводе в DNS-домен (No DNS domain configured for computer. Unable to perform DNS Update)

Автоматическое обновление DNS-записи рабочей станции Linux при вводе в DNS-домен (No DNS domain configured for computer. Unable to perform DNS Update)

После выхода Samba 4 ввести рабочую станцию под управлением Linux в Windows домен стало гораздо проще, но все еще переодически всплывают нестандартные ситации такие как ошибка No DNS domain configured for computer. Unable to perform DNS Update.


ON-Line утилиты тестирования DNS-серверов

ON-Line утилиты тестирования DNS-серверов

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


Сборка Power DNS рекусора из исходных кодов

Сборка Power DNS рекусора из исходных кодов

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


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


Есть вопросы?
Спрашивайте и я обязательно вам отвечу!

* Поля обязательные для заполнения .