Тема: DNS slave и записи в MySQL

Танцы с бубном продолжаются...

Что мы имеем, pdns сервер с записями в mysql и несколько bind'ов которые используют его как мастера. Все бы хорошо да вся эта цепочка глючит так как в бинды надо добавлять новые зоны достаточно часто и обновлять. Зон окото 10000 и в день 200-300 меняются, плюс появляются новые и умирают старые. Рещение простое, сделать еще пару pdns серверов, которые будут кешировать записи из главного и выдавать их. Проблема основная в том что нельзя включить бинарные логи, а следовательно нельзя использовать репликации.

Задача простая, надежность и дуракоустойчивость.

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

Задачи:

1. Поднять вторичный mysql который будит переодичиски синхронизировать записи и выступать их, гарант работы в случае если загнется первый
2. DNS сервер должен искать записи во вторичном мускуле и при его отказе спрашивать записи у главного сервера. Это для того чтобы уменьшить нагрузку.

Поехали.

== Mysql
Поскольку репликации использовать я немогу, появилась мысля использовать federated tables (кто-то мне пытался расказать что с ними нельзя организовать некоторые действия, так вот, можно, было бы желание...)

Основная система собиралась в джаиле из портов, никакой самодеятельности, все по мануалу, мне еще работать с этим, а не дыры латать. После сборки mysqla (кстати freebsd не потому что я большой патриот, первоначально все планировалось на centos, просто работа этой системы на линуксе меня неустроила, а именно mysql тупит метсами) создаем пользователя на mysql-master.domain.com (назавем его так) и вытаскиваем mysqldumpом те таблицы которые я хочу перенести на mysql-slave.domain.com. Таблицы три

dm_record, dm_zone, pdns_data

CREATE TABLE `dm_record` (
  `record_id` bigint(20) unsigned NOT NULL auto_increment,
  `zone_id` int(10) unsigned NOT NULL default '0',
  `name` varchar(255) NOT NULL default '',
  `ttl` int(10) unsigned default NULL,
  `class` varchar(5) NOT NULL default '',
  `rr_type` tinyint(3) unsigned NOT NULL default '0',
  `value` varchar(255) NOT NULL default '',
  `type_txt` varchar(10) NOT NULL default '',
  `priority` int(10) unsigned default NULL,
  `request_id` int(10) unsigned default NULL,
  `is_edit_allowed` tinyint(3) unsigned default '1',
  `descr` varchar(255) default '',
  `ptr_direct_zone` varchar(255) default NULL,
  PRIMARY KEY  (`record_id`),
  KEY `record__request_id` (`request_id`),
  KEY `record__name` (`name`),
  KEY `record__zone_id` (`zone_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `dm_zone` (
  `zone_id` int(10) unsigned NOT NULL auto_increment,
  `account_no` int(10) unsigned NOT NULL default '0',
  `zone_type` tinyint(3) unsigned NOT NULL default '0',
  `zone` varchar(255) NOT NULL default '',
  `is_manual` tinyint(4) default NULL,
  `is_active` tinyint(4) default '1',
  PRIMARY KEY  (`zone_id`),
  KEY `zone__zone` (`zone`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `pdns_data` (
  `zone_id` int(10) unsigned NOT NULL default '0',
  `notified_serial` int(10) unsigned default NULL,
  PRIMARY KEY  (`zone_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

эти три таблицы создаем как фетеральные на mysql-slave.domain.com. Тоесть меняем тока конец таблицы, сама она остается идентичной

CREATE TABLE `pbas_dm_record` (
  `record_id` bigint(20) unsigned NOT NULL auto_increment,
  `zone_id` int(10) unsigned NOT NULL default '0',
  `name` varchar(255) NOT NULL default '',
  `ttl` int(10) unsigned default NULL,
  `class` varchar(5) NOT NULL default '',
  `rr_type` tinyint(3) unsigned NOT NULL default '0',
  `value` varchar(255) NOT NULL default '',
  `type_txt` varchar(10) NOT NULL default '',
  `priority` int(10) unsigned default NULL,
  `request_id` int(10) unsigned default NULL,
  `is_edit_allowed` tinyint(3) unsigned default '1',
  `descr` varchar(255) default '',
  `ptr_direct_zone` varchar(255) default NULL,
  PRIMARY KEY  (`record_id`),
  KEY `record__request_id` (`request_id`),
  KEY `record__name` (`name`),
  KEY `record__zone_id` (`zone_id`)
) ENGINE=FEDERATED DEFAULT CHARSET=utf8 CONNECTION='mysql://rpdns:[email protected]/db/dm_record';

CREATE TABLE `pbas_dm_zone` (
  `zone_id` int(10) unsigned NOT NULL auto_increment,
  `account_no` int(10) unsigned NOT NULL default '0',
  `zone_type` tinyint(3) unsigned NOT NULL default '0',
  `zone` varchar(255) NOT NULL default '',
  `is_manual` tinyint(4) default NULL,
  `is_active` tinyint(4) default '1',
  PRIMARY KEY  (`zone_id`),
  KEY `zone__zone` (`zone`)
) ENGINE=FEDERATED DEFAULT CHARSET=utf8 CONNECTION='mysql://rpdns:[email protected]/db/dm_zone';

CREATE TABLE `pbas_pdns_data` (
  `zone_id` int(10) unsigned NOT NULL default '0',
  `notified_serial` int(10) unsigned default NULL,
  PRIMARY KEY  (`zone_id`)
) ENGINE=FEDERATED DEFAULT CHARSET=utf8 CONNECTION='mysql://rpdns:[email protected]/db/pdns_data';

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

CREATE TABLE `buffer_dm_record` (
  `record_id` bigint(20) unsigned NOT NULL auto_increment,
  `zone_id` int(10) unsigned NOT NULL default '0',
  `name` varchar(255) NOT NULL default '',
  `ttl` int(10) unsigned default NULL,
  `class` varchar(5) NOT NULL default '',
  `rr_type` tinyint(3) unsigned NOT NULL default '0',
  `value` varchar(255) NOT NULL default '',
  `type_txt` varchar(10) NOT NULL default '',
  `priority` int(10) unsigned default NULL,
  `request_id` int(10) unsigned default NULL,
  `is_edit_allowed` tinyint(3) unsigned default '1',
  `descr` varchar(255) default '',
  `ptr_direct_zone` varchar(255) default NULL,
  PRIMARY KEY  (`record_id`),
  KEY `record__request_id` (`request_id`),
  KEY `record__name` (`name`),
  KEY `record__zone_id` (`zone_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `buffer_dm_zone` (
  `zone_id` int(10) unsigned NOT NULL auto_increment,
  `account_no` int(10) unsigned NOT NULL default '0',
  `zone_type` tinyint(3) unsigned NOT NULL default '0',
  `zone` varchar(255) NOT NULL default '',
  `is_manual` tinyint(4) default NULL,
  `is_active` tinyint(4) default '1',
  PRIMARY KEY  (`zone_id`),
  KEY `zone__zone` (`zone`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `buffer_pdns_data` (
  `zone_id` int(10) unsigned NOT NULL default '0',
  `notified_serial` int(10) unsigned default NULL,
  PRIMARY KEY  (`zone_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

и делаем процедуру которая при живом мастеер обновляет запси каждые 10 минут:

DELIMITER ;;
DROP PROCEDURE IF EXISTS `update_from_pbas` ;;
CREATE PROCEDURE `update_from_pbas`()
    DETERMINISTIC
begin
    declare x int;
    declare pbas_alive boolean default true;
    declare curx cursor for select record_id from pdns.pbas_dm_record;
    declare continue handler for SQLSTATE 'HY000' set pbas_alive = false;
    open curx;

    if (pbas_alive) then
        replace into pdns.buffer_dm_record select * from pdns.pbas_dm_record;
        replace into pdns.buffer_dm_zone select * from pdns.pbas_dm_zone;
        replace into pdns.buffer_pdns_data select * from pdns.pbas_pdns_data;
    end if;
end ;;

DELIMITER ;

и еще одну, которая каждую ночь удаляем все и создает заного, чтобы очистить таблицу от уже несуществующих зон.

DELIMITER ;;
DROP PROCEDURE IF EXISTS `delandupdate_from_pbas` ;;
CREATE PROCEDURE `delandupdate_from_pbas`()
    DETERMINISTIC
begin
    declare x int;
    declare pbas_alive boolean default true;
    declare curx cursor for select record_id from pdns.pbas_dm_record;
    declare continue handler for SQLSTATE 'HY000' set pbas_alive = false;
    open curx;

    if (pbas_alive) then
        delete from pdns.buffer_dm_record;
        replace into pdns.buffer_dm_record select * from pdns.pbas_dm_record;
        delete from pdns.buffer_dm_zone;
        replace into pdns.buffer_dm_zone select * from pdns.pbas_dm_zone;
        delete from pdns.buffer_pdns_data;
        replace into pdns.buffer_pdns_data select * from pdns.pbas_pdns_data;
    end if;
end ;;

DELIMITER ;

после чего в крон добавляем

*/10    *       *       *       *       root    /usr/local/bin/mysql -se 'call update_from_pbas;' pdns >/dev/null 2>&1
25      4       *       *       *       root    /usr/local/bin/mysql -se 'call delandupdate_from_pbas;' pdns >/dev/null 2>&1

== PDNS

pdns.conf

daemon=yes
distributor-threads=10
launch=gmysql
loglevel=5
query-logging=yes
recursor=dns-master.domain.com:53
setgid=pdns
setuid=pdns
slave=slave
gmysql-user=pdns
gmysql-dbname=pdns
gmysql-password=pass
gmysql-socket=/tmp/mysql.sock
gmysql-info-all-master-query=SELECT z.zone_id, z.zone, NULL, NULL, p.notified_serial, 'MASTER' FROM buffer_dm_zone z LEFT JOIN buffer_pdns_d
ata p ON (z.zone_id = p.zone_id) WHERE z.is_active > 0
gmysql-info-all-slaves-query=SELECT * from buffer_dm_zone where 1 = 2
gmysql-update-serial-query=REPLACE INTO buffer_pdns_data (notified_serial, zone_id) VALUES (%d, %d)
gmysql-info-zone-query=SELECT z.zone_id, z.zone, NULL, NULL, p.notified_serial, 'MASTER' FROM buffer_dm_zone z LEFT JOIN buffer_pdns_data p
ON (z.zone_id = p.zone_id) WHERE z.zone = '%s' and z.is_active > 0
gmysql-list-query=SELECT r.value, r.ttl, r.priority, r.type_txt, r.zone_id, r.name FROM buffer_dm_record r, buffer_dm_zone z WHERE z.zone_id
 = r.zone_id and r.zone_id = %d and z.is_active > 0
gmysql-id-query=SELECT r.value, r.ttl, r.priority, r.type_txt, r.zone_id, r.name FROM buffer_dm_record r, buffer_dm_zone z WHERE z.zone_id =
 r.zone_id and r.type_txt = '%s' and r.name = '%s' and r.zone_id = %d and z.is_active > 0
gmysql-any-query=SELECT r.value, r.ttl, r.priority, r.type_txt, r.zone_id, r.name FROM buffer_dm_record r, buffer_dm_zone z WHERE z.zone_id
= r.zone_id and r.name = '%s' and z.is_active > 0
gmysql-any-id-query=SELECT r.value, r.ttl, r.priority, r.type_txt, r.zone_id, r.name FROM buffer_dm_record r, buffer_dm_zone z WHERE r.zone_
id = z.zone_id and r.name = '%s' and z.is_active > 0
gmysql-basic-query=SELECT r.value, r.ttl, r.priority, r.type_txt, r.zone_id, r.name FROM buffer_dm_record r, buffer_dm_zone z WHERE z.zone_i
d = r.zone_id and r.type_txt = '%s' and r.name = '%s' and z.is_active > 0

Все!

Вопросы/Пожелания/Предлажения принимаются