↓ До тексту
  1. Статті/

PowerDNS: реплікація MariaDB між двома DNS-серверами

·6 хвилин
cat /etc/passwd
Автор
cat /etc/passwd
Технічний блог про Linux, DevOps та Open Source технології.
Зміст
Відмовостійкий DNS - частина серії статей.
Частина 2: ця стаття (ти тут)

У першій частині ми підняли авторитативний DNS на PowerDNS 5.1 з бекендом MariaDB і REST API. Він працює, але він один: впаде сервер - зникне весь DNS. Робимо його відмовостійким - додаємо другий сервер на репліці MariaDB.

Хитрість підходу в тому, що реплікацію робить база даних, а не сам PowerDNS. Обидва pdns читають зі свого локального MariaDB, а master-slave тримає бази синхронними. Жодних AXFR, also-notify чи налаштувань вторинних зон у самому PowerDNS - DNS-ноди «тупі», уся синхронізація живе в MariaDB.

Що потрібно зробити?
#

Налаштувати MariaDB master-slave між двома серверами: майстер (ns1 з частини 1) приймає зміни, репліка (ns2) отримує їх автоматично. Реплікаційний трафік пустимо окремою приватною мережею, а не тією, якою ходять DNS-запити. На репліці підніму другий pdns, що читає локальну копію бази, і покажу головне: зміна зони на майстрі з’являється на репліці сама. Усе записав наживо у двох панелях - ліворуч майстер, праворуч репліка.

З чим працюю?
#

Дві машини Debian 13, кожна з двома інтерфейсами:

1
2
3
4
5
6
7
8
        делегування example.com → NS: ns1 + ns2 (два окремі IP)
   ┌───────────────────────┐              ┌───────────────────────┐
   │ ns1 - МАЙСТЕР          │              │ ns2 - РЕПЛІКА          │
   │ pdns + API (запис зон) │              │ pdns (лише роздача)    │
   │ MariaDB ─── бінлог ────────REPL──────▶ MariaDB (read_only)    │
   └───────────────────────┘              └───────────────────────┘
     eth0 10.10.60.103 (публічна, DNS)      eth0 10.10.60.104 (публічна, DNS)
     eth1 10.20.0.1  ←── приватна мережа ──→ eth1 10.20.0.2 (реплікація)
  • eth0 - «публічна» мережа, якою ходять DNS-запити й керування (на реальному VPS це публічні IP).
  • eth1 - окрема приватна мережа 10.20.0.0/24 тільки для реплікації MariaDB. Так реплікаційний трафік (і відкритий порт 3306) не світиться там, де DNS.

На реальних серверах замість приватної SDN-мережі це буде приватна мережа провайдера між вашими VPS (або VPN). Логіка та сама.

Далі те саме, що в записі, тільки вже можна копіювати.

Майстер: вмикаємо бінлог
#

На ns1 (майстрі) створюємо /etc/mysql/mariadb.conf.d/60-replication.cnf:

1
2
3
4
[mariadb]
server-id    = 1
log-bin      = mariadb-bin
bind-address = 10.20.0.1

bind-address = 10.20.0.1 - MariaDB слухатиме лише на приватному інтерфейсі. Локальний pdns це не зачіпає: він ходить до бази через unix-сокет (gmysql-host=localhost), а не TCP.

1
systemctl restart mariadb

Створюємо користувача реплікації - дозволяємо підключатися тільки з приватної адреси репліки:

1
2
3
mysql -e "CREATE USER 'repl'@'10.20.0.2' IDENTIFIED BY 'надійний-пароль';
          GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.20.0.2';
          GRANT SELECT, SHOW VIEW ON pdns.* TO 'repl'@'10.20.0.2';"

REPLICATION SLAVE потрібен для самої реплікації, а SELECT, SHOW VIEW ON pdns.* - щоб цим же користувачем зручно залити початковий знімок бази (нижче). У проді seed-дамп можна робити й окремим користувачем.

Репліка: server-id і read-only
#

На ns2 (репліці) - свій 60-replication.cnf. Головне: інший server-id і read_only, щоб ніхто випадково не писав у репліку повз майстра:

1
2
3
[mariadb]
server-id = 2
read_only = 1
1
systemctl restart mariadb

Репліка: початковий знімок бази
#

Реплікація переносить лише нові зміни - тож спершу треба залити поточний стан бази pdns. Робимо це прямо з репліки, потоком по приватній мережі, без проміжних файлів:

1
2
mysqldump -h 10.20.0.1 -u repl -p'надійний-пароль' \
  --single-transaction --gtid --databases pdns | mysql

--gtid додає в дамп позицію GTID, з якої репліка почне доганяти майстра, а --single-transaction знімає узгоджений знімок без блокувань.

Користувачів MariaDB реплікація не переносить (вони в системній базі mysql), тож локального користувача pdns на репліці створюємо руками - з тим самим паролем, що й на майстрі:

1
2
mysql -e "CREATE USER 'pdns'@'localhost' IDENTIFIED BY 'той-самий-пароль';
          GRANT ALL ON pdns.* TO 'pdns'@'localhost';"

read_only блокує записи для звичайних користувачів, але root через сокет (він з привілеєм SUPER) - і потік реплікації - пишуть попри нього. Тому і seed, і створення користувача спокійно проходять.

Запускаємо реплікацію
#

Вказуємо репліці на майстра по GTID і стартуємо:

1
2
3
4
mysql -e "CHANGE MASTER TO MASTER_HOST='10.20.0.1', MASTER_USER='repl',
          MASTER_PASSWORD='надійний-пароль', MASTER_USE_GTID=slave_pos;
          START SLAVE;"
mysql -e 'SHOW SLAVE STATUS\G' | grep -E 'Running:|Seconds_Behind|Using_Gtid'

Маємо побачити:

1
2
3
4
   Slave_IO_Running: Yes
  Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
        Using_Gtid: Slave_Pos

Два Yes і нуль відставання - реплікація жива.

pdns на репліці
#

Залишилось сказати другому pdns читати локальну базу. Конфіг - той самий, що й на майстрі в частині 1 (прибираємо дефолтний bind-бекенд, вмикаємо gmysql):

1
rm -f /etc/powerdns/pdns.d/bind.conf

/etc/powerdns/pdns.d/gmysql.conf:

1
2
3
4
5
6
launch=gmysql
gmysql-host=localhost
gmysql-dbname=pdns
gmysql-user=pdns
gmysql-password=той-самий-пароль
gmysql-dnssec=yes
1
2
3
systemctl restart pdns
dig +short @127.0.0.1 www.example.com
# 203.0.113.20

Зону example.com на репліці ми не заводили - вона приїхала з майстра реплікацією. pdns просто читає локальну базу й роздає її.

Головне: зміна на майстрі - сама на репліці
#

Додаємо новий запис на майстрі через API (як у частині 1):

1
2
3
4
5
# на ns1 (майстрі)
curl -s -X PATCH -H 'X-API-Key: ваш-ключ' -H 'Content-Type: application/json' \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com. \
  -d '{"rrsets":[{"name":"mail.example.com.","type":"A","ttl":60,"changetype":"REPLACE",
       "records":[{"content":"203.0.113.77","disabled":false}]}]}'

І одразу питаємо репліку:

1
2
3
# на ns2 (репліці)
dig +short @127.0.0.1 mail.example.com
# 203.0.113.77

Запис з’явився на репліці сам, за долю секунди - ми його туди не заводили. Ось заради чого вся схема: керуєте зонами в одному місці (на майстрі, через API), а репліка підхоплює зміни автоматично.

Писати - тільки на майстрі
#

Спроба змінити зону на репліці очікувано відбивається:

1
2
3
4
# на ns2 (репліці)
pdnsutil add-record example.com test.example.com A 1.2.3.4
# Error: ... The MariaDB server is running with the --read-only option
# so it cannot execute this statement

Це правильно: master-slave - односторонній. Усі зміни йдуть на майстер, репліка лише роздає. Про зворотний бік (що буде, якщо впаде майстер) - нижче.

А відмовостійкість?
#

Тепер у вас дві ноди з однаковими даними. Як це вмикає HA для authoritative DNS:

  • Делегування на два NS. У реєстратора / батьківській зоні вказуєте обидва сервери: ns1.example.com і ns2.example.com з їхніми IP. Резолвери самі перебирають їх - не відповів один, питають інший. Це і є головний механізм відмовостійкості authoritative DNS, і він тут працює «безкоштовно».
  • Репліка роздає, навіть якщо майстер лежить. Поки живий ns2, запити обслуговуються - дані ж у нього вже є.
  • Чого ще немає. Master-slave односторонній: якщо впаде майстер, репліка сама писати не почне (зони тимчасово не можна змінювати, доки не піднімете майстер або не підвищите репліку вручну). Для authoritative DNS це зазвичай не біда - зони міняють рідко. Автоматичний failover запису - це вже інша історія (Galera чи ручний promote).

Граблі
#

  • Seed-дамп треба з --databases. mysqldump pdns без --databases не містить CREATE DATABASE/USE, і імпорт падає з No database selected. З --databases pdns усе на місці, а --gtid ще й виставляє стартову позицію реплікації.
  • Користувачів реплікація не переносить. Вони в системній базі mysql. Локального pdns@localhost на репліці створюйте руками, тим самим паролем, що на майстрі - інакше pdns на репліці не підключиться до бази.
  • read_only не заважає seed і реплікації. root через сокет і потік реплікації мають привілей SUPER і пишуть попри read_only. Блокуються лише звичайні користувачі - саме те, що треба.
  • Прив’язуйте реплікацію до приватної мережі. bind-address MariaDB на приватну адресу + 'repl'@'<приватний IP>' тримають порт 3306 і реплікаційний трафік подалі від публічної мережі. Локальний pdns це не зачіпає - він через сокет.

Що далі
#

Дані тепер надійні, але вхід - ще ні: DNS-запити йдуть прямо в pdns, без балансування й захисту. У наступній частині ставимо dnsdist спереду на кожній ноді: балансування на локальний pdns із резервом на сусіда, health-check і вимкнення хворої ноди з ротації. А потім - тюнинг проти флуду.

Підсумок
#

Відмовостійкий DNS вийшов простим, бо синхронізацію ми віддали базі. MariaDB master-slave з GTID по приватній мережі тримає дві копії зон; pdns на кожній ноді просто читає локальну базу. Керуємо з одного місця через API на майстрі, репліка підхоплює зміни сама, а два NS-записи дають резолверам запасний сервер. Далі зробимо надійним і сам вхід.

Відмовостійкий DNS - частина серії статей.
Частина 2: ця стаття (ти тут)

Related

PowerDNS: свій авторитативний DNS на MariaDB з REST API

·6 хвилин
Свій DNS-сервер на PowerDNS 5.1 з бекендом MariaDB і REST API: офіційне репо (не застарілий 4.9), схема gmysql, перша зона й керування записами через API. Живий запис з усіма конфігами у vim. Перша частина серії про відмовостійкий authoritative DNS.