Posterous theme by Cory Watilo

Filed under: PowerDNS

PowerDNS - master and slave

В связи с переездом на новый сервер пришлось переносить и кучу сервисов, которые крутились на старом. Одним из них был DNS.

Настоятельно рекомендуется делать master и slave (primary и secondary) на разных машинах, дабы в случае отказа одного из них второй мог полноценно работать. Ну это в теории, на практике все несколько упрощается до одного сервера, который если ляжет – то DNS будет уже не столь важен.

Начал подымать master на OpenSuSE 11.1. Конфиг простой и незамысловатый:

#each slave should be put here (space separated) to allow zone details transfer
allow-axfr-ips=1.1.1.2
disable-axfr=no
daemon=yes
guardian=yes
default-ttl=3600
soa-refresh-default=3600
lazy-recursion=yes
local-address=0.0.0.0
master=yes
recursor=127.0.0.1:5300
launch=gmysql
gmysql-host=localhost
gmysql-port=3306
gmysql-dbname=powerdns
gmysql-user=powerdns
gmysql-password=powerdns
gmysql-socket=/var/run/mysql/mysql.sock

и рекурсор:

chroot=/var/lib/pdns
local-address=127.0.0.1
local-port=5300
setgid=pdns
setuid=pdns

Базу для него создавал из PowerDNS on Rails, хотя можно ограничиться и той, что в документации.

И напоролся на первый занятный баг – со включенным режимом master он взлетает и долбит базу одинаковыми запросами (пытается получить все записи). На стоящей рядом ubuntu с той же версией такого не наблюдается. Долго пытался отловить что, где и почему – и решил пересобрать из сорсов. Когда обнаружилась нехватка boost я решил проверить, а нет ли готового свеженького пакета с собраным powerdns? Таки есть – надо было всего лишь добавить в списки репо вот этот:

http://download.opensuse.org/repositories/server:/dns/openSUSE_11.1/

и была установлена версия pdns-2.9.22-1.1. Проблема с нагрузкой на БД полечилась, но появилась новая – с репликацией на slave.

В PowerDNS есть такое понятие как supermaster. В общем случае нужно прописывать зону на мастере и слейве. Когда слейв получит от мастера notify, он запросит у мастера записи из этой зоны. Но если сервер, от которого пришел notify, будет обнаружен в списке supermasters, и такой зоны на слейве не будет описано, она создастся автоматически. И потом в нее будут добавлены записи с мастера. Это приятно упрощает жизнь.

В табличке запись довольно простая – ip мастера, имя слейва (ns2.example.org), имя аккаунта (на работу никак не влияет – просто будет фигурировать в описании зоны; удобно для указания клиента, например).

В порядке эксперимента попинал слейв нотификейшенами для разных доменов. Не с первого раза, но заработало:

pdns_control notify example.org

С первого раза не заработало потому, что в PowerDNS on Rails в имя поддомена по привычке вписал “@”, и потом получил записи вида “@.example.org” вместо “example.org” либо “@”. Прошелся простым апдейтом по базе и убрал этот баг.

PS: Хочу выразить благодарность Патрику Фею за его недавний пост, который помог постичь смысл supermaster'ов.

PPS: конфиг pdns для слейва мало чем отличается от мастера:

config-dir=/etc/powerdns
daemon=yes
disable-axfr=yes
guardian=yes
launch=gmysql
lazy-recursion=yes
local-address=0.0.0.0
local-port=53
module-dir=/usr/lib/powerdns
setgid=pdns
setuid=pdns
slave=yes
socket-dir=/var/run
version-string=powerdns
gmysql-host=localhost
gmysql-port=3306
gmysql-dbname=powerdns
gmysql-user=powerdns
gmysql-password=powerdns

PowerDNS URL records and redirector

В PowerDNS есть пара плюшек, которые я раньше не замечал. Одну из них - записи "URL" я сегодня попробовал, настроил и остался вполне доволен. Что же такое URL-запись? Это фича PowerDNS, при помощи которой можно делать редирект домена на заданный URL. Например, запись выглядит как [cc lang="text"] some.domain.com URL http://other.domain.com/some_domain_com [/cc] На самом деле в поле со значением записи может быть ссылка куда угодно. В конфиге сервера нужно указать [cc lang="ini"] ... fancy-records=yes urlredirector=77.120.99.180 ... [/cc] Когда приходит запрос к DNS'у для домена some.domain.com отдается 77.120.99.180, указанный в конфиге, как если бы это была обычная "А"-запись. Браузер отрезолвив имя идет на http://some.domain.com. На этом адресе висит http-сервер (я повесил lighttpd), у которого скрипт лезет в БД и для указанного домена ищет соответствующую ему URL-запись и редиректит браузер туда. Все просто и тривиально. Для lighttpd у меня конфиг дополнился такими строками: [cc lang="ini"] $SERVER["socket"] == "77.120.99.180:80" { $HTTP["host"] =~ "(.*)" { magnet.attract-raw-url-to = ("/var/www/lighttpd/pdns-url-redirector.lua") } } [/cc] То есть все что приходит незамедлительно обрабатывается упомянутым lua-скриптом. Почему lua? С ним работает mod_magnet, и мне было интересно написать этот скриптик. Вот он: [cc lang="lua"] require "luasql.mysql" env = assert (luasql.mysql()) con = assert (env:connect("dnsdb", "dns_user", "dns_pass_for_readonly_records_table", "localhost")) domain = lighty.request["Host"] mysql_replacements = { ["\0"] = "\\0", ["\n"] = "\\n", ["\r"] = "\\r", ["\'"] = "\\\'", ["\""] = "\\\"", ["\026"] = "\\Z", ["\b"] = "\\b", ["\t"] = "\\t", } function fixsql (s) return (string.gsub (tostring (s), "[%z\n\r\'\"\026\b\t]", function (str) return mysql_replacements [str] or str end )) end -- fixsql cur = assert (con:execute( string.format("SELECT content FROM records WHERE `type` = 'URL' AND `name` = '%s'", fixsql(domain) ) ) ) result = cur:fetch() if result then redirect_to = string.format("%s", result) else redirect_to = "http://daemons.org.ua/" end cur:close() con:close() env:close() --lighty.content = { string.format("SELECT content FROM records WHERE `type` = 'URL' AND `name` = '%s'", fixsql(domain) ) } --lighty.header["Content-Type"] = "text/plain" --return 200 lighty.header["Location"] = redirect_to return 301 [/cc] Комментарий начинается с "--". Закоментарены строки для отладки скрипта. Для соединения с мускулем из lua доставил пакет liblua5.1-sql-mysql-2. Скрипт писал долго, но это было интересно. И небесполезно =)
UPD: во избежание DoS/DDoS атаки на мускуль возможны два варианта апгрейда метода: 1. По крону раз в 5 минут генерить sqlite-БД состоящую из одной таблицы из двух полей - domain, target 2. Раз в 5 минут генерить сам скрипт с вынесенными в него в виде хеша записями. Тогда скрипт вообще никуда подглядывать не будет.

Migration to PowerDNS

Наверняка многие видели сообщение об уязвимости в DNS. Во многих дистрибах линукса весьма скоро появились секьюрити-патчи, которые эту уязвимость исправляли. Уязвимость, может, и не слишком уж страшная, но она напомнила мне о PowerDNS, который я с интересом рассматривал в начале 2008го года. Расматривал я его в основном из-за родной интеграции с MySQL. Мне весьма понравилось как она реализована. Однако у меня были уже рабочие конфиги BIND9, и я просто ленился испытать PowerDNS на собственной шкуре. А зря боялся, как оказалось. Упомянутая уязвимость не задела PowerDNS, и этим мое внимание было вновь привлечено к нему. И переход таки состоялся.

Что дал переход

Во-первых, я получил возможность использовать найденый вчера poweradmin. Штука оказалась немного неуклюжая, но порадовала меня тем, что не надо более ковыряться в конфигах. Во-вторых, при изменении/добавлении/удалении записей в зоне маркер (Serial) в SOA меняется сам. Мелочь, но приятно. В-третьих, теперь можно давать доступ к редактированию зон другим людям. Вот то, что бросилось мне в глаза. А теперь более подробно о самом переходе.

Процесс перехода

PowerDNS состоит из двух компонентов - основной сервер имен и преобразователь имен. Основной сервер имен выдает информацию только о тех записях, которые в нем есть. Преобразователь имен же помогает резолвить то, чего нету непосредственно у данного сервера. Итак, понадобится выполнить следующие действия: [cc lang="text"] apt-get install pdns-server pdns-recursor pdns-backend-mysql [/cc] Желательно во время установки стопнуть bind, потому как dpkg будет ругаться из-за неудачного запуска демона. После установки можно тушить pdns и стартовать bind до окончания миграции. В конфиге /etc/powerdns/pdns.conf надо сделать следующие изменения: [cc lang="text"] ... #allow-recursion=127.0.0.1 ... launch=gmysql ... local-address=_real_external_ip_here_ ... recursor=127.0.0.1 ... [/cc] Этим мы будем передавать все неизвестные запросы от основного сервера к recursor'у. У рекурсора в конфиге стоит local-address=127.0.0.1 по умолчанию, если мне не изменяет память. В /etc/powerdns/pdns.d/pdns.local вписываем [cc lang="text"] gmysql-host=localhost gmysql-port=3306 gmysql-dbname=_username_here_ gmysql-user=_dbname_here_ gmysql-password=_dbpass_here_ gmysql-socket=/var/run/mysqld/mysqld.sock [/cc] Проверяем, есть ли нужная БД и пользователь, и может ли он подключиться к базе. Пример начальной базы есть в /usr/share/doc/pdns-backend-mysql. Советую туда сразу добавить в конец [cc lang="sql"] ALTER TABLE records ADD CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE; [/cc] При его тестировании было замечено что работает он бытрее с таблицами InnoDB. Наверное, из-за большого кеша у этого типа таблиц. Я не стал менять таблицы на MyISAM, однако добавил при создании таблиц default collate utf8_general_ci - для надежности. Потом надо втянуть записи имеющиеся у BIND. Для этого в комплекте есть утилита zone2sql. Рекомендую использовать ее с ключиком --gmysql - так она правильнее работает, создавая записи и в domains и в records. [cc lang="text"] zone2sql --named-conf=/etc/bind/named.conf --gmysql > import.sql [/cc] и вначале выкосил все до основных зон. Потом скормил это mysql'ю. Также можно во всех записях SOA поставить Serial в 0 - так PowerDNS будет вычислять его автоматически. Пробуем стопнуть bind9 и запустить pdns и pdns-recursor. Если все правильно - все взлетит, и в сислоге будут сообщения о состоянии полета. Проверить можно набрав [cc lang="text"] nslookup - dns.yourserver.com [/cc] и попробовать ввести что-то существующее на сервере и что-то несуществующее. У меня основной сервер без recursor'а не хотел отдавать записи типа CNAME при просмотре nslookup'ом. Пришлось установит recursor и настроить их общение. Ну а установка poweradmin для управления сервером не должна вызвать трудностей вообще.