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

dnsdist: балансування і failover для PowerDNS

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

У частині 1 ми підняли PowerDNS із бекендом MariaDB і API, у частині 2 - зробили дані надійними через реплікацію. Дані живуть на обох серверах, але вхід у DNS - ще ні: запити йдуть прямо в pdns, без балансування й без захисту від того, що один pdns впаде чи захлинеться.

Ставимо перед кожним pdns dnsdist - спеціалізований DNS-балансувальник від тих самих авторів. Він бере на себе порт :53, балансує запити на локальний pdns, тримає сусідній сервер як резерв, перевіряє бекенди health-check-ом і сам виводить хворий з ротації.

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

Поставити dnsdist на обидві ноди, зсунути pdns на :5300 (щоб звільнити :53 для dnsdist), налаштувати dnsdist так, щоб він слав запити на локальний pdns, а якщо той недоступний - на сусідній pdns по приватній мережі. А тоді - перевірити наживо: вимкнути pdns на одній ноді й переконатися, що запити (навіть нові, не з кешу) далі резолвляться. Усе записав у двох панелях: ліворуч ns1, праворуч ns2.

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

Дві ноди Debian 13 з частини 2 (pdns + MariaDB-реплікація). Тепер перед кожним pdns стає dnsdist:

1
2
3
4
5
6
7
8
9
         клієнти → :53 (dnsdist) на кожній ноді
   ┌────────────────────────┐        ┌────────────────────────┐
   │ ns1                    │        │ ns2                    │
   │ dnsdist :53            │        │ dnsdist :53            │
   │   ├─ local pdns :5300  │        │   ├─ local pdns :5300  │
   │   └─ peer ─────────────┼───10.20.0.x───┤ (резерв)       │
   │ pdns :5300             │        │ pdns :5300             │
   └────────────────────────┘        └────────────────────────┘
          два NS-записи (ns1 + ns2): резолвер сам обирає живий сервер

Резервний бекенд (сусідній pdns) dnsdist смикає по тій самій приватній мережі 10.20.0.0/24, що й реплікацію - inter-node трафік не світиться в публічній мережі.

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

Встановлення dnsdist
#

dnsdist - в окремому компоненті того самого репозиторію PowerDNS (гілка dnsdist-21 = dnsdist 2.1). Додаємо джерело й ставимо (ключ і pinning уже є з частини 1):

1
2
3
4
5
6
# /etc/apt/sources.list.d/dnsdist.sources
Types: deb
URIs: http://repo.powerdns.com/debian
Suites: trixie-dnsdist-21
Components: main
Signed-By: /etc/apt/keyrings/pdns-auth.asc
1
apt update && apt install -y dnsdist

Звільняємо :53 - pdns на :5300
#

Поки :53 займає pdns. Переносимо його на :5300, слухаючи localhost (для dnsdist) і приватну адресу (щоб сусідній dnsdist міг дістатися як до резерву). Файл /etc/powerdns/pdns.d/listen.conf на ns1:

1
local-address=127.0.0.1:5300, 10.20.0.1:5300

Грабля pdns 5.x: стара опція local-port тут ігнорується - порт треба вписувати прямо в local-address через двокрапку. На ns2, відповідно, 10.20.0.2:5300.

1
2
systemctl restart pdns
ss -ulnp | grep 5300    # pdns тепер на :5300, :53 вільний

Конфіг dnsdist
#

Головний файл - /etc/dnsdist/dnsdist.conf (мова Lua). На ns1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- клієнтські запити приймаємо на :53
setLocal("0.0.0.0:53")

-- консоль керування (ключ має бути рівно 32 байти в base64)
controlSocket("127.0.0.1:5199")
setKey("ваш-ключ")

-- бекенди: локальний pdns у пріоритеті (order=1), сусід - резерв (order=2)
-- health-check питає SOA зони, яку pdns реально обслуговує
newServer({address="127.0.0.1:5300", name="local", order=1, checkName="example.com.", checkType="SOA"})
newServer({address="10.20.0.2:5300", name="peer",  order=2, checkName="example.com.", checkType="SOA"})

-- беремо найменший order, що живий
setServerPolicy(firstAvailable)

-- кеш пакетів: однакові запити не доходять до pdns
pc = newPacketCache(10000, {maxTTL=60})
getPool(""):setCache(pc)

-- зручний статус бекендів для консолі
function status()
  local t = {}
  for _, s in ipairs(getServers()) do t[#t+1] = s:getName()..": "..(s:isUp() and "UP" or "DOWN") end
  return table.concat(t, "  |  ")
end

Ключові рядки:

  • newServer({... order=...}) - два бекенди. order=1 (локальний pdns) - основний, order=2 (сусід по приватній адресі) - резерв.
  • checkName/checkType - health-check. Важливо: типовий health-check питає кореневий домен, на який наш authoritative-сервер відповість REFUSED - і dnsdist може вирішити, що бекенд живий, коли той насправді не обслуговує нічого потрібного. Тому питаємо SOA зони, яку pdns реально тримає (example.com) - так «живий» означає «справді віддає наші зони».
  • setServerPolicy(firstAvailable) - завжди бери перший за order бекенд, що зараз UP. Поки локальний живий - усе йде на нього; впав - на сусіда.
  • newPacketCache - кеш однакових відповідей, щоб розвантажити pdns.

На ns2 конфіг такий самий, тільки резервний бекенд вказує на 10.20.0.1:5300.

controlSocket/setKey потрібні лише для консолі (dnsdist -c), якою зручно дивитися статус бекендів. Ключ згенеруйте так: openssl rand -base64 32 (саме 32 байти - коротший консоль відкине).

Запускаємо:

1
systemctl enable --now dnsdist

Перевірка
#

Запит клієнта тепер іде на :53 (dnsdist) і далі на локальний pdns:

1
2
dig +short @127.0.0.1 www.example.com
# 203.0.113.20

Стан бекендів дивимось через консоль (нашою функцією status()):

1
2
dnsdist -c -e 'status()'
# local: UP  |  peer: UP

Failover наживо
#

Найцікавіше - вимикаємо локальний pdns на ns1 і дивимось, що буде:

1
2
3
4
5
# на ns1
systemctl stop pdns
sleep 3
dnsdist -c -e 'status()'
# local: DOWN  |  peer: UP

Health-check за пару секунд помітив, що локальний pdns не відповідає, і вивів його з ротації. А запити - працюють далі, бо dnsdist шле їх на сусіда по приватній мережі. Перевіряємо новим запитом (щоб точно не з кешу):

1
2
dig +short @127.0.0.1 api.example.com
# 203.0.113.99

Відповідь прийшла - хоча локального pdns немає. Для клієнта нічого не змінилось. Повертаємо pdns:

1
2
3
4
systemctl start pdns
sleep 3
dnsdist -c -e 'status()'
# local: UP  |  peer: UP

dnsdist сам побачив, що локальний бекенд ожив, і повернув його в роботу - знову все йде на нього.

А якщо впаде вся нода?
#

Failover вище рятує, коли на ноді лягає pdns, а сама нода й dnsdist живі. Якщо ж падає вся ns1 - тут працює механізм рівня делегування, про який ми говорили в частині 2: у зоні вказані два NS-записи (ns1 і ns2 з різними IP), і резолвери самі перемикаються на той, що відповідає. Два шари разом - dnsdist на кожній ноді плюс два NS - покривають і «впав демон», і «впала нода».

Граблі
#

  • pdns 5.x ігнорує local-port. Порт вказуйте прямо в local-address (127.0.0.1:5300), інакше pdns лишиться на :53 і не пустить туди dnsdist.
  • Ключ консолі dnsdist - рівно 32 байти. openssl rand -base64 32. Коротший (16 байт) консоль відкине з «console key is not valid», хоча сам dnsdist працюватиме.
  • Health-check для authoritative. Типовий health-check питає те, чого ваш сервер не обслуговує, і статус бекенда може бути оманливим. Задавайте checkName/checkType на зону, яку pdns реально тримає.
  • У консолі dnsdist -c -e видно лише те, що функція ПОВЕРТАЄ. print() пише в лог демона, не клієнту - тому status() збирає рядок і return-ить його, а не друкує.
  • Кеш пакетів може маскувати failover. Якщо запит уже в кеші dnsdist, відповідь прийде з кешу, не торкаючись бекенда. Перевіряйте failover новим іменем, яке ще не кешувалось.

Що далі
#

Тепер вхід у DNS відмовостійкий: балансування, health-check, резерв і два NS. Лишилось захистити його від перевантаження - коли резолвери починають заливати нас запитами. У наступній частині: тюнинг dnsdist проти флуду - обмеження запитів на один IP і динамічні блоки.

Підсумок
#

dnsdist перетворив два окремі pdns на відмовостійкий вхід: клієнти стукають у :53, а балансувальник сам вирішує, куди слати - на локальний pdns чи, якщо той впав, на сусіда по приватній мережі. Health-check тримає картину актуальною, кеш розвантажує бекенди, а два NS-записи додають відмовостійкості на рівні делегування. Падіння pdns тепер - не подія для клієнта.

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

Related

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

·6 хвилин
Другий DNS-сервер на репліці MariaDB: master-slave з GTID по окремій приватній мережі, pdns на репліці read-only. Зміна через API на майстрі миттєво на репліці. Живий двопанельний запис. Друга частина серії про відмовостійкий authoritative DNS.

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

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