dnsbalancer — II

У попе­ре­дньо­му запи­сі я роз­по­вів про основ­ні кон­це­пції, які ми вико­ри­сто­ву­є­мо для балан­су­ва­н­ня DNS-запи­тів від клі­єн­тів. Тепер давай­те поди­ви­мо­ся, що змі­ни­ло­ся за чоти­ри міся­ці.

А за цей час змі­ни­ло­ся три речі.

По-пер­ше, виро­сло наван­та­же­н­ня. Пере­ва­жна біль­шість клі­єн­тів пере­лі­зла на нові DNS-сер­ве­ри, і тепер через primary-балан­су­валь­ник ми про­пу­ска­є­мо 14,5 тисяч запи­тів на секун­ду, а сумар­но через оби­два — близь­ко 25 тисяч (неве­ли­кий пік був від DDoS, із яким балан­су­валь­ни­ки успі­шно впо­ра­ли­ся без будь-яких три­ге­рів). Ресур­си вір­ту­аль­них машин при цьо­му не дода­ва­ли­ся і не заби­ра­ли­ся. Після вимкне­н­ня ста­рих рекур­со­рів до цьо­го наван­та­же­н­ня дода­сться ще 2 кіло­па­ке­та (хом’ячки зі ста­ти­кою, з яки­ми ми поти­хень­ку боре­мо­ся). Асин­хрон­на кон­це­пція оброб­ки запи­тів не змі­ню­ва­ла­ся — ті самі вор­ке­ри, по одно­му на про­це­сор­не ядро, ядро ОС балан­сує запи­ти між ними зав­дя­ки SO_REUSEPORT, клі­єнт­ський conntrack вимкне­ний, запи­ти до рекур­со­рів над­си­ла­ю­ться з фіксо­ва­них пор­тів (дяку­ю­чи під­трим­ці connect() для UDP).

По-дру­ге, я таки змі­нив схе­му збе­ре­же­н­ня від­по­від­но­сті «клі­єнт-рекур­сор». Тепер хеш-табли­ця не вико­ри­сто­ву­є­ться. Нато­мість, для кожно­го запи­ту про­сто під­став­ля­є­ться новий ID з ліній­но­го інкре­мен­ту і запам’ятовується від­по­від­ність двох іден­ти­фі­ка­то­рів у табли­ці. Хеш CRC64 при цьо­му все ще вико­ри­сто­ву­є­ться на випа­док, якщо в чер­зі збе­ре­ться біль­ше 65536 запи­тів (highload!), але пер­ший крок вибо­ру доме­на колі­зій від­бу­ва­є­ться без під­ра­хун­ку хеша. Схе­му з під­ста­нов­кою ID я під­ди­вив­ся у кон­ку­рен­тів — dnsdist від PowerDNS. Однак ця шту­ка ще не допи­ля­на, вона не вміє бага­то­по­то­чність і біль­ше 65 кіло­за­пи­тів. Тому ми тут зі сво­їм ріше­н­ням попе­ре­ду всі­єї пла­не­ти (зві­сно, серед тих опен­со­ур­сних про­е­ктів, які я зміг нагу­гли­ти).

По-тре­тє, я при­кру­тив фун­кцію філь­тра­ції запи­тів. Нам це зна­до­би­ло­ся для того, щоб, по-пер­ше, хова­ти локаль­ні зони від зов­ні­шніх запи­тів (оскіль­ки PowerDNS не вміє views), а, по-дру­ге, щоб від­би­ва­ти DDoS. В ACL можна ука­за­ти, що ота­кі-от доме­ни (за точним збі­гом, з під­до­ме­на­ми чи за регекс­па­ми, а також за типом запи­тів — так ми, напри­клад, ріже­мо всі ANY-запи­ти) ми не обслу­го­ву­є­мо з таких-то адрес. Замість про­сто­го дро­па можна посла­ти у від­по­відь NXDOMAIN або навіть спе­ци­фі­чну IP-адре­су, якщо хоче­ться завер­ну­ти тра­фік на пев­ну заглу­шку. Зага­лом, поту­жна і гну­чка схе­ма. Тут, зві­сно, є ще про­стір для покра­щень: по-пер­ше, потрі­бна інте­гра­ція з MySQL (щоб пра­ви­ла можна було міня­ти «на льо­ту»), по-дру­ге, я не знаю, як воно мас­шта­бу­ва­ти­ме­ться, якщо таких пра­вил буде тися­ча. Але покла­де­ні фун­кції зараз схе­ма успі­шно вико­нує.

Я дуже люблю гра­фі­ки. Тому дода­тко­во я при­кру­тив різну кори­сну ста­ти­сти­ку. Напри­клад, можна диви­ти­ся затрим­ки в оброб­ці DNS-запи­тів. Якщо взя­ти primary-балан­сер, гра­фік вигля­дає якось так:

lats

Вели­кий пік — запи­ти, вида­ні з кеша рекур­со­ра, тому там затрим­ка близь­ко чвер­ті мілі­се­кун­ди. Малий пік спра­ва — це запи­ти не з кеша, тому на них і витра­ча­ю­ться деся­тки мілі­се­кунд. Як бачи­те, кеш рекур­со­рів над­зви­чай­но ефе­ктив­ний, незва­жа­ю­чи на те, що зараз він у нас живе всьо­го 30 хв.

Також можу пока­за­ти гра­фік інтен­сив­но­сті запи­тів за добу з пер­вин­но­го балан­се­ра:

reqs

Ми на одно­му гра­фі­ку тут малю­є­мо кіль­кість паке­тів на вхід і на вихід. Там, де вини­ка­ють дис­про­пор­ції — це пра­цю­ють ACL. Нас остан­ній тиждень нама­га­ю­ться DDoS-ити через від­кри­ті рекур­со­ри клі­єн­тів, і ком­пле­ксна бороть­ба з цим яви­щем успі­шна, але ще в про­це­сі.

Нага­дую, код балан­се­ра від­кри­тий (GPLv3-only), а доку­мен­та­ція по ньо­му зав­жди акту­аль­на.

UPDATE 1: забув зга­да­ти про один недо­лік LDNS, який виплив після того, як я замі­нив хешта­бли­цю про­стою під­ста­нов­кою ID. Спо­ча­тку під­ста­нов­ка вико­ну­ва­ла­ся сила­ми LDNS — пакет роз­би­рав­ся на части­ни, викли­ка­ла­ся від­по­від­на фун­кція LDNS, пакет зби­рав­ся назад. Усе пра­цю­ва­ло добре на тесто­во­му стен­ді, а після вика­ту­ва­н­ня онов­ле­н­ня на робо­чі сер­ве­ри в части­ни клі­єн­тів поча­ли­ся чуде­са. Вияви­ло­ся, що LDNS не під­три­мує ком­пре­сію від­по­від­ей (це така шту­ка, яка дає змо­гу не дублю­ва­ти FQDN у кожно­му RR, а про­сто про­став­ля­ти дво­бай­то­ві зсу­ви на спіль­ний для всіх RR FQDN, що силь­но еко­но­мить тра­фік, змен­шу­ю­чи роз­мір паке­та), і тому UDP, який при­йшов від рекур­со­ра і займав мен­ше 512 байт, але містив бага­то RR у від­по­віді (напри­клад, 20), після пере­па­ко­ву­ва­н­ня мав роз­мір близь­ко 700 байт, що пору­шує RFC. Від цьо­го деяким мар­шру­ти­за­то­рам зно­си­ло голо­ву (у части­ни полі­ку­ва­ло­ся онов­ле­н­ням про­шив­ки, у части­ни не ліку­ва­ло­ся нічим, напри­клад, у Apple AirPort), і пере­ста­ва­ли пра­цю­ва­ти деякі ресур­си (напри­клад, Viber). На гра­фі­ках така пове­дін­ка вигля­да­ла зна­чною дис­про­пор­ці­єю вхі­дно­го і вихі­дно­го тра­фі­ку, що мене спо­ча­тку зди­ву­ва­ло, але я не звер­нув на це нале­жної ува­ги. Полі­ку­ва­ло­ся це тим, що я обі­йшов­ся на цьо­му ета­пі без фун­кцій LDNS і став запи­су­ва­ти новий ID пря­мо в сирий пакет (через htons(), зві­сно). ID у DNS-паке­ті зав­жди займає пер­ші два бай­та, тому це зро­би­ти дуже лег­ко.

Мітки: , , , , ,

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

*

Цей сайт використовує Akismet для зменшення спаму. Дізнайтеся, як обробляються ваші дані коментарів.