html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

geoDNS с помощью Powerdns и nginx

Обожаю задачи “на стыке технологий”, это одна из таких.
Задача:
  • реализовать geoDNS*
  • c возможностью wildcard (*.some.tst. A 1.2.3.4)
  • с возможностью менять содержимое зон на ходу, добавлять новые зоны пачками
  • без необходимости запускать громоздкие скрипты на каждый запрос “мимо кеша”
  • научиться тестить этот реактор (с локалхоста, а не кучи proxy/VDS)


*) под geoDNS я подразумеваю возможность для клиентов из разных регионов отдавать разные, например, адреса сервера/А-записи (для США отдаётся IP сервера в США, для СНГ — в москве, для ЕС — в Европе ...)

Статья описывает
  • метод реализации geoDNS
  • метод тестирования
  • эскизное решение на “чистом nginx”

Если интересно, причём же здесь nginx, прошу под кат.


Существующие решения (патч для bind, geo_backend и pipe_backend у powerdns), допустим, нас чем-то не устроили.

Метод реализации geoDNS


Powerdns(pdns) — авторитативный dns сервер, который имеет кучу (аж 15 штук) бекендов (источников информации) от стандартных BIND-like до различных СУБД (MySQL, Oracle, PostgreSQL, sqlite), простого pipe и экзотики типа Lua, LDAP.

Бэкэнд выбирается глобально для всей инсталяции (нельзя 5 доменов на mysql, еще 5 на sqlite и т.д) так:
launch=remote
remote-connection-string=http:url=http://127.0.0.1:4343/dnsapi


При использовании remote backend, pdns посылает на указанный сервер http-запрос и ожидает получить от онного http-ответ, содержащий данные в любимом web-разработчиками формате json

Как пример:
> GET /dnsapi/lookup/www.example.com/ANY HTTP/1.1
< {"result":[{"qtype":"A", "qname":"www.example.com", "content":"192.168.1.2", "ttl": 60}]}


Очевидно, что ставить за вебсервер какую-то динамику нельзя (слишком жирно будет, да и ddos через DNS довольно распространён), поэтому, пробуем реализовать логику DNS на чистом nginx, отдающем обычную статику.

На удивление, логика оказалась очень простая и ничего, кроме try_files и rewrite не потребовалось, реализация geo составляющей усложнилось только на использование модуля ngx_http_geo_module
Потребовался немного хитровыдуманный генератор этой самой статики (см. ниже).

Будем хранить нашу зону (уже готовый заjson-еный ответ, без учёта geo-привязки) в файловой структуре вида
/$1/$2$1_$3.jsn
$1 — зона
$2 — поддомен (_ в случае wildcard)
$3 — тип запроса (например, A, CNAME,MX… ANY)
Пример: /domain.com/sub.domain.com_A.jsn

Важное уточнение: логически доменное имя nextsub.sub.domain.com может быть
  • самостоятельным доменом /nextsub.sub.domain.com/nextsub.sub.domain.com_A.jsn
  • поддоменом /sub.domain.com/nextsub.sub.domain.com_A.jsn
  • wildcard /sub.domain.com/_sub.domain.com_A.jsn


Поэтому перебрать нужно три варианта (укладываем в try_files).

Если такого поддомена не нашлось, ищем выше(это не по RFC, да и практическая польза сомнительна): просто повторяем поиск для sub.domain.com (укладываем в rewrite)

Самое время вспомнить про geo-составляющую.
Тут всё просто, добавляем буквенный код геозоны: /domain.com/def/sub.domain.com_A.jsn

Эскизное решение на чистом nginx


Костыль для wildcard: Важно понимать, что при wildcard запросе вида ddddd.domain.com мы должны отдать в ответе поддомен(а не *.domain.com), на помощь приходит ngx_http_sub_module, который заменяет %WC% в статике на запрошенный поддомен.

Конфиг nginx
# в хеадер  X-remotebackend powerdns кладёт IP клиента
# определяем по нему геозону, результат откажется в переменной $src
geo $http_x_remotebackend_remote $src{
	default def;
	127.1.0.0/16 i0;
	127.1.1.0/24 i1;
}
# формат лога, усиленный  информацией о geo-зоне и IP клиента
log_format ns '$remote_addr - [$time_local] "$request" $status '
'"$http_user_agent" $http_x_remotebackend_real_remote '
' $http_x_remotebackend_real_remote $http_x_remotebackend_remote $http_x_remotebackend_local $src';

server {
	listen   	127.0.0.1:4343;
	access_log  /var/www/dns/logs/nginx.access.log  ns;
	error_log  /var/www/dns/logs/nginx.error.log;
	# Дебажить тут !
	#rewrite_log 	on;
	root   /var/www/dns/store;

	# в любой непонятной ситуации отдаём синтаксически-корректную ошибку.
	error_page 403 /backend.jsn;

	location / {
		return 403;
	}
	location ~* ^/dnsapi/lookup/([^\.]+)\.([^/]*)/([a-z]+)$ {
		#Для дебага через http 
		add_header X-geo $src;

		sub_filter_types text/plain;
		sub_filter "%WC%" $1.$2. ;

		# Если вы хотите повторять поиск для домена более высокого уровня,
		# уберите /empty.jsn  

		try_files	/$2/$src/$1.$2_$3.jsn /$1.$2/$src/$1.$2_$3.jsn /$2/$src/_$2_$3.jsn
					/$2/def/$1.$2_$3.jsn /$1.$2/def/$1.$2_$3.jsn /$2/def/_$2_$3.jsn
					/empty.jsn @fallback;
		# сначала пробуем найти ответ для определившейся геозоны ($src)
		# если не нашлось, пробуем дефолтный.

		index  fallback.jsn;
		limit_except GET {deny all;}

		# ./nextsub.sub.domain.com/SOA
		# sub.domain.com/<geo>/nextsub.sub.domain.com_SOA
		# nextsub.sub.domain.com/<geo>/nextsub.sub.domain.com_SOA
		# sub.domain.com/<geo>/_sub.domain.com_SOA
		# ./sub.domain.com/SOA
		# ...

	}
	# идём на уровень выше.
	location @fallback{
		rewrite ^/dnsapi/lookup/([^\.]+)\.([^/]*)/([a-z]+) /dnsapi/lookup/$2/$3;
	}
} #server


Метод тестирования


Тут всё еще проще, обратите внимание, наши тестовые геозоны мы раздавали внутри 127.0.0.0/8, командам dig и wget можно запросто скормить нужный IP источника.

 wget -q -S -O - --bind-address=127.1.0.2  http://127.0.0.1:4343/dnsapi/lookup/d.q.qq/A
 dig -b 127.0.12.1 ANY q.qq @localhost

Для нашего случая всё отлично тестируется так:
# dig +short -b 127.0.0.1 A q.qq @localhost
1.1.1.1
# dig +short -b 127.1.0.1 A q.qq @localhost
127.0.0.1
# dig +short -b 127.1.1.1 A q.qq @localhost
127.1.99.123


Немного хитровыдуманный генератор


Есть немного такого кода, за который мне местами стыдно. Вот и он
Генератор статики
<?php
$empty=array();
define('TTL',3);
opt('empty',true,'empty');
opt('index','true','index');
opt('backend',false,'backend');

$zones=array();

//эталонная зона
$q=array();
$q[]=array('','NS','a.ns');
$q[]=array('','NS','b.ns');
$q[]=array('','A','1.1.1.1');
$q[]=array('www','CNAME','');
$q[]=array('*','A','3.2.1.4');
$q[]=array('','MX','mxs.ns',5);
$q[]=array('','SOA','a.ns domain.lazutov.net. 5 3600 3600 604800 0');

//запишем в дефолтный geo
$zones['q.qq']['def']=$q;
$q=unsetrr($q,'','A');
// и немного модифицируем для гео
$zones['q.qq']['i0']=$q;
$zones['q.qq']['i0'][]=array('','A','127.0.0.1');
$zones['q.qq']['i1']=$q;
$zones['q.qq']['i1'][]=array('','A','127.1.99.123');

foreach ($zones as $zone=>$locdata){
  foreach ($locdata as $loc=>$rrs){
	$sub=array();
	$all=$rrs;
    // разложим зону "поподдоменно"
	foreach ($rrs as $r){
 	if ($r[0]==='*'){
   	$sub['*'][]=$r;
 	} elseif ($r[0]==='') {
   	$sub['@'][]=$r;
 	} else {
   	$sub[$r[0]][]=$r;
 	}
	}
    // сформируем массив для записи в файлы и запишем.
	foreach ($sub as $sd=>$rrs){
 	$rrs=formdata($zone,$rrs);
 	foreach ($rrs as $type=>$v) writedown($zone,$loc,$sd,$type,$v);
	}
  }
}
// пишем инфломацию о записях типа type поддомена sub зоны zone в гео loc
function writedown ($zone,$loc,$sub,$type,$data){
  $fn="{$sub}.{$zone}";
  if ($sub=='@') $fn=$zone;
  elseif ($sub=='*') $fn='_'.$zone;
  opt("{$zone}/{$loc}/{$fn}_{$type}",$data);
}
//формируем данные для записи в json (раскладываем по типам)
function formdata($zone,$rrs){
  $r=array();
  foreach ($rrs as $rr){
	$qname=(empty($rr[0])?$zone:"{$rr[0]}.{$zone}");
	$pr=(empty($rr[3])?0:intval($rr[3]));
	$c=(empty($rr[2])?$zone:$rr[2]);
	$rd=array('qname'=>$qname,'qtype'=>$rr[1],'content'=>$c,'ttl'=>TTL,'priority'=>$pr,'domain_id'=>-1);
	if ($rr[0]==='*' AND $rd['qtype']!=='ANY') $rd['qname']='%WC%';
	$r[$rr[1]][]=$rd;
	$r['ANY'][]=$rd;
  }
  return $r;
}

function unsetrr($data,$src,$type){
    foreach ($data as $k=>$v) if ($v[0]===$src and $v[1]===$type) unset($data[$k]);
    return $data;
}
// типа OutPuT данных data в файл file с комментом add
function opt($file,$data,$add=NULL){
    $r=array('result'=>$data);
    if (!empty($add)) $r['desc']=$add;
    $dir=dirname(__FILE__);
    $cd=dirname($dir.'/'.$file) ;
    //echo "{$cd}\n";
    if (!is_dir($cd)) mkdir($cd );
    file_put_contents($dir.'/'.$file.'.jsn',json_encode($r) );
}


Плюсы данного решения:
  • “Горячее” добавление/изменение
  • Отдача статики через nginx хорошо изучена и довольно проста
  • nginx_geo хорошо изучен и документирован
  • Масштабируется горизонтальненько добалением новых сначала воркеров pdns, а затем серверов связки pdns+nginx
  • Допиливается под ваши нужды синтаксисом конфигов nginx


Но я не считаю его готовым к использованию в боевых условиях и вот почему:


Спасибо за внимание!

Вопросы прошу направлять в комментарии, а опечатки — в личку.
Желающие попиарить свой DNS сервис пожалуйста, проследуйте в свой двор, извините.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

0

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции