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

PowerDNS: health-check прямо в DNS - LUA-записи

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

Усю серію ми робили відмовостійким сам DNS: реплікація бази, два NS за dnsdist, захист від флуду, підпис. Але була одна сліпа зона: записи статичні. www віддає умовний 203.0.113.20 - і якщо цей вебсервер ляже, DNS незворушно шле на нього людей далі. Resolver не знає, що там уже нікого немає.

PowerDNS уміє це виправити - health-check прямо в DNS. Через LUA-записи сервер сам опитує бекенди і віддає в відповіді лише ті, що насправді живі. Відмовостійкість спускається на рівень самого DNS.

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

Навчити www.example.com вказувати на два вебсервери так, щоб PowerDNS сам перевіряв кожен (HTTP-запитом) і прибирав із відповіді той, що не відповідає. Поставити LUA-запис з ifurlup, подивитись, як трафік балансується між живими, а тоді вимкнути один бекенд і переконатися, що він зникає з відповіді сам - і на майстрі, і на репліці незалежно.

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

Той самий стенд: ns1 (майстер) + ns2 (репліка), зона example.com з DNSSEC. Додаю два простих вебсервери - web1 і web2 (слухають :8080):

1
2
3
4
5
6
7
8
9
                     health-check кожні 5 с (HTTP 200 + потрібний вміст)
              ┌───────────────────────────────────────────┐
              │                                            ▼
  resolver ─▶ ns1 (pdns + LUA ifurlup) ─┐           web1  10.10.60.104:8080
              │                         ├─▶ у відповіді лише ЖИВІ
  resolver ─▶ ns2 (pdns + LUA ifurlup) ─┘           web2  10.10.60.105:8080
              │                                            ▲
              └───────────────────────────────────────────┘
          кожна нода опитує бекенди САМА, незалежно від іншої

Ключова ідея: health-check виконує сам авторитативний сервер, фоновим потоком. Тож і майстер, і репліка стежать за бекендами кожен окремо - жодної спільної точки, яка вирішує «живий / мертвий».

Весь процес - у записі (ліворуч ns1, праворуч - вузол із web1):

Далі те саме по кроках, уже з поясненнями.

Вмикаємо LUA-записи
#

За замовчуванням PowerDNS LUA-записи не обробляє - їх треба явно ввімкнути. Оскільки health-check робитимуть обидві ноди, вмикаємо на кожній (ns1 і ns2):

1
2
echo "enable-lua-records=yes" > /etc/powerdns/pdns.d/lua.conf
systemctl restart pdns

⚠️ LUA-запис - це код, який виконується на вашому DNS-сервері. Глобальний enable-lua-records=yes дозволяє його для всіх зон. У проді безпечніше вмикати точково - через метадані лише для довірених зон:

1
pdnsutil set-meta example.com ENABLE-LUA-RECORDS 1

Тоді чужа (наприклад, делегована клієнтові) зона не зможе виконувати Lua на вашому сервері.

Запис із health-check
#

Міняємо статичний www A на LUA-запис. У полі даних - тип відповіді (A) і Lua-вираз, що її обчислює. Для перевірки живучості беремо ifurlup - вона робить HTTP-запит до кожного бекенда й лишає тих, хто відповів 200 (а з опцією stringmatch - ще й з потрібним вмістом на сторінці):

1
www.example.com.  30  IN  LUA  A  "ifurlup('http://www.example.com:8080/', {{'10.10.60.104', '10.10.60.105'}}, {stringmatch='linuxway-web-ok'})"

Важлива деталь: LUA-записи не можна додати через API - PowerDNS їх навмисно блокує (update of Lua records is not allowed), бо це виконуваний код. Тож і веб-панель через API ними не керуватиме - тільки pdnsutil або пряма база. Найзручніше - edit-zone (не треба воювати з лапками):

1
2
pdnsutil edit-zone example.com      # у редакторі додаємо рядок вище
pdns_control rediscover             # pdnsutil пише повз кеш зон - перечитуємо

Еквівалент одним рядком - pdnsutil add-record example.com www.example.com LUA 30 '...'. Але Lua-вираз сам повний лапок (' всередині, " зовні), тож у шелі edit-zone простіший. І не забувайте FQDN: PowerDNS 5.x хоче www.example.com, а не просто www.

Невеликий TTL (тут 30) тут доречний: що менший, то швидше резолвери підхоплять зміну складу живих бекендів.

Обидва живі - балансуємо
#

Питаємо www напряму в pdns (:5300, повз кеш dnsdist). Обидва бекенди відповідають, тож PowerDNS віддає то один, то інший - виходить заразом ще й балансування навантаження:

1
2
3
4
5
6
7
for i in $(seq 6); do dig +short @127.0.0.1 -p 5300 www.example.com; done
# 10.10.60.105
# 10.10.60.104
# 10.10.60.104
# 10.10.60.105
# 10.10.60.104
# 10.10.60.105

Гасимо бекенд - він зникає сам
#

Вимикаємо web1 (10.10.60.104):

1
systemctl stop lab-web          # на вузлі web1

І за кілька секунд (інтервал перевірки - 5 с) www перестає його віддавати - лишається тільки живий web2:

1
2
3
4
5
6
7
for i in $(seq 6); do dig +short @127.0.0.1 -p 5300 www.example.com; done
# 10.10.60.105
# 10.10.60.105
# 10.10.60.105
# 10.10.60.105
# 10.10.60.105
# 10.10.60.105

Ніхто руками нічого не правив - PowerDNS сам побачив, що бекенд не відповідає, і прибрав його з відповіді. Повертаємо web1 (systemctl start lab-web) - і так само сам, за кілька секунд, він повертається в ротацію.

Репліка перевіряє незалежно
#

Найкраще для нашої схеми: на ns2 ми нічого окремо не налаштовували - LUA-запис приїхав реплікацією бази, а enable-lua-records=yes ми ввімкнули на обох нодах. Тож репліка опитує бекенди самостійно і теж віддає лише живі:

1
2
3
# на ns2 (репліці), web1 вимкнений
dig +short @127.0.0.1 -p 5300 www.example.com
# 10.10.60.105

Жодної спільної точки, яка вирішує за всіх. Якщо впаде навіть сам ns1 - ns2 однаково віддає коректну, перевірену відповідь.

А якщо лягли всі?
#

ifurlup за замовчуванням fail-open: якщо живих не лишилось, вона віддає випадковий IP із пулу, а не порожнечу. Логіка проста - краще відправити на «можливо, живий» сервер, ніж повернути NXDOMAIN і гарантовано зламати сайт:

1
2
3
# обидва бекенди вимкнені
dig +short @127.0.0.1 -p 5300 www.example.com
# 10.10.60.105      (DNS усе одно відповідає)

Бонус: відповідь підписана DNSSEC
#

LUA-запис синтезує A на льоту - і PowerDNS тут же підписує його нашим ключем DNSSEC (той самий тег 56910). Health-check і криптопідпис не конфліктують:

1
2
3
dig +dnssec @127.0.0.1 -p 5300 www.example.com A +noall +answer
# www.example.com.  30  IN  A      10.10.60.104
# www.example.com.  30  IN  RRSIG  A 13 3 30 ... 56910 example.com. 6J5xkgum...==

Граблі й нюанси
#

  • Кеш dnsdist маскує зміни. Через :53 (dnsdist) packet cache віддає закешовану відповідь до свого TTL - тому для демонстрації ми питаємо pdns напряму (:5300). У проді тримайте TTL LUA-запису низьким, щоб і кеш, і резолвери швидко підхоплювали failover.
  • Інтервал і «засинання». Перевірки йдуть кожні lua-health-checks-interval секунд (дефолт 5). А lua-health-checks-expire-delay (дефолт 3600) зупиняє перевірку записів, яких ніхто не питав годину - перша після паузи відповідь може бути ще за старим станом.
  • ifurlup vs ifportup. ifportup(443, {...}) - простіша перевірка: лише чи відкритий TCP-порт. ifurlup чесніша: робить HTTP-запит і може звіряти вміст (stringmatch), тобто ловить «порт відкритий, а застосунок віддає 500».
  • Пули й пріоритет. Адреси в ifurlup - це список пулів: {{'основний1','основний2'}, {'резерв'}}. Поки живий хоч хтось у першому пулі - резерв мовчить; усі впали - вмикається наступний. Зручно для «основний ЦОД / запасний».
  • Безпека понад усе. Ще раз: LUA-запис виконується на сервері. Глобально вмикайте лише там, де всі зони свої; для хостингу з чужими зонами - тільки точкові метадані.
  • HTTPS теж можна. У проді health-check робіть на бойовий https://www.example.com/ (:443) - ifurlup вміє TLS; ми в лабі взяли :8080, щоб не чіпати решту сервісів на стенді.

Що далі
#

Відмовостійкість ми довели до рівня окремого запису: DNS більше не шле на мертві бекенди. Відкрите питання з DNSSEC-частини - DS у реєстратора як єдиний ручний крок - напрошується закрити автоматизацією (CDS/CDNSKEY, RFC 7344/8078), щоб батьківська зона підхоплювала зміну ключа сама. А хто хоче «справжньої» промислової відмовостійкості - далі лежить anycast (один IP на обидва NS, маршрутизація через BGP).

Підсумок
#

Звичайний DNS віддає фіксовані адреси й не знає, живі вони чи ні. PowerDNS із LUA-записами перетворює DNS на активного учасника відмовостійкості: ifurlup сам опитує бекенди й лишає у відповіді лише ті, що працюють, - з балансуванням, fail-open і підписом DNSSEC «з коробки». А оскільки перевіряє кожна нода окремо, схема не має спільної точки відмови навіть у самому health-check. Єдине, про що варто памʼятати, - LUA-запис це код: вмикайте його свідомо й лише для довірених зон.

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

Related

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

·6 хвилин
dnsdist перед кожним pdns: балансування на локальний бекенд, резервний сусід по приватній мережі, health-check і автоматичне виведення хворої ноди. Вимикаємо pdns наживо - запити далі резолвляться через сусіда. Плюс кеш пакетів. Двопанельний запис. Третя частина серії про відмовостійкий authoritative DNS.

PowerDNS-Admin проти Poweradmin: дві веб-панелі, дві філософії

PowerDNS-Admin і Poweradmin - дві веб-панелі для PowerDNS зі схожими назвами, але протилежною архітектурою: перша керує через REST API (слабка звʼязність, Docker/Python), друга пише прямо в базу (тісна звʼязність, PHP/LAMP). Ставимо обидві проти одного стенду й порівнюємо наживо: підключення, вигляд, керування записами, DNSSEC, функції. І розбираємо, кому що підходить.

PowerDNS: DNSSEC за одну команду

DNSSEC підтверджує, що відповідь DNS справжня, а не підмінена. У PowerDNS увімкнути його - одна команда: secure-zone генерує ключ і сервер підписує відповіді онлайн. Ключі лежать у MariaDB, тож їдуть реплікацією - репліка віддає зону підписаною сама. Показуємо RRSIG, DS для реєстратора і перевіряємо на обох нодах. Частина серії про відмовостійкий authoritative DNS.