Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud
Здравейте, аз съм Сергей Еланцев, развивам се балансьор на мрежовото натоварване в Yandex.Cloud. Преди това ръководех разработването на балансьора L7 за портала Yandex - колегите се шегуват, че каквото и да правя, се оказва балансьор. Ще кажа на читателите на Habr как да управляват натоварването в облачна платформа, какво виждаме като идеалния инструмент за постигане на тази цел и как се движим към изграждането на този инструмент.

Първо, нека въведем някои термини:

  • VIP (Виртуален IP) - IP адрес на балансьор
  • Сървър, бекенд, инстанция - виртуална машина, изпълняваща приложение
  • RIP (Real IP) - IP адрес на сървъра
  • Healthcheck - проверка на готовността на сървъра
  • Availability Zone, AZ - изолирана инфраструктура в център за данни
  • Регион - обединение на различни АЗ

Балансиращите натоварване решават три основни задачи: те извършват самото балансиране, подобряват устойчивостта на грешки на услугата и опростяват нейното мащабиране. Толерантността към грешки се осигурява чрез автоматично управление на трафика: балансьорът следи състоянието на приложението и изключва екземпляри от балансиране, които не преминават проверката за жизнеспособност. Мащабирането е осигурено чрез равномерно разпределяне на натоварването между екземплярите, както и актуализиране на списъка с екземпляри в движение. Ако балансирането не е достатъчно равномерно, някои от инстанциите ще получат натоварване, което надвишава лимита на капацитета им, и услугата ще стане по-малко надеждна.

Балансиращият натоварване често се класифицира по протоколния слой от OSI модела, на който работи. Cloud Balancer работи на TCP ниво, което съответства на четвъртия слой, L4.

Нека да преминем към преглед на архитектурата на облачния балансьор. Постепенно ще повишаваме нивото на детайлност. Разделяме компонентите на балансира в три класа. Класът на конфигурационната равнина е отговорен за взаимодействието с потребителя и съхранява целевото състояние на системата. Контролната равнина съхранява текущото състояние на системата и управлява системи от класа на равнината на данни, които са пряко отговорни за доставянето на трафик от клиенти към вашите инстанции.

Равнина на данни

Трафикът завършва на скъпи устройства, наречени гранични рутери. За да се увеличи устойчивостта на грешки, няколко такива устройства работят едновременно в един център за данни. След това трафикът отива към балансьори, които съобщават произволни IP адреси на всички AZ чрез BGP за клиенти. 

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Трафикът се предава през ECMP - това е стратегия за маршрутизиране, според която може да има няколко еднакво добри маршрута до целта (в нашия случай целта ще бъде IP адресът на местоназначението) и пакетите могат да се изпращат по всеки от тях. Поддържаме и работа в няколко зони на достъпност по следната схема: рекламираме адрес във всяка зона, трафикът отива към най-близката и не излиза извън нейните граници. По-късно в публикацията ще разгледаме по-подробно какво се случва с трафика.

Конфигурационна равнина

 
Ключовият компонент на конфигурационната равнина е API, чрез който се извършват основни операции с балансьори: създаване, изтриване, промяна на състава на инстанции, получаване на резултати от проверки на състоянието и т.н. От една страна, това е REST API, а от друга друго, ние в облака много често използваме рамката gRPC, така че „превеждаме“ REST в gRPC и след това използваме само gRPC. Всяка заявка води до създаване на поредица от асинхронни идемпотентни задачи, които се изпълняват в общ пул от работници на Yandex.Cloud. Задачите са написани по такъв начин, че да могат да бъдат спрени по всяко време и след това рестартирани. Това гарантира мащабируемост, повторяемост и регистриране на операциите.

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

В резултат на това задачата от API ще отправи заявка към контролера на услугата за балансиране, който е написан на Go. Може да добавя и премахва балансьори, да променя състава на бекенда и настройките. 

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Услугата съхранява състоянието си в Yandex Database, разпределена управлявана база данни, която скоро ще можете да използвате. В Yandex.Cloud, както вече каза, се прилага концепцията за храна за кучета: ако ние самите използваме нашите услуги, тогава нашите клиенти също ще се радват да ги използват. Yandex Database е пример за прилагане на такава концепция. Ние съхраняваме всичките си данни в YDB и не е нужно да мислим за поддържане и мащабиране на базата данни: тези проблеми са решени за нас, ние използваме базата данни като услуга.

Да се ​​върнем на балансиращия контролер. Неговата задача е да запази информация за балансира и да изпрати задача за проверка на готовността на виртуалната машина към контролера за проверка на здравето.

Контролер за проверка на здравето

Той получава заявки за промяна на правилата за проверка, записва ги в YDB, разпределя задачите между възлите на healtcheck и агрегира резултатите, които след това се записват в базата данни и се изпращат до контролера за балансиране на натоварването. Той от своя страна изпраща заявка за промяна на състава на клъстера в равнината на данни към възела за балансиране на натоварването, който ще обсъдя по-долу.

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Нека поговорим повече за здравните проверки. Те могат да бъдат разделени на няколко класа. Одитите имат различни критерии за успех. TCP проверките трябва успешно да установят връзка в рамките на фиксиран период от време. HTTP проверките изискват както успешна връзка, така и отговор с код на състоянието 200.

Също така чековете се различават по класа на действие - те са активни и пасивни. Пасивните проверки просто наблюдават какво се случва с трафика, без да предприемат никакви специални действия. Това не работи много добре на L4, защото зависи от логиката на протоколите от по-високо ниво: на L4 няма информация колко време е отнела операцията или дали завършването на връзката е било добро или лошо. Активните проверки изискват балансьорът да изпраща заявки до всеки екземпляр на сървъра.

Повечето устройства за балансиране на натоварването сами извършват проверки на жизнеспособността. В Cloud решихме да разделим тези части на системата, за да увеличим скалируемостта. Този подход ще ни позволи да увеличим броя на балансьорите, като същевременно запазим броя на заявките за проверка на състоянието към услугата. Проверките се извършват от отделни възли за проверка на състоянието, в които целите за проверка се разделят и репликират. Не можете да извършвате проверки от един хост, тъй като може да се провали. Тогава няма да получим състоянието на инстанциите, които той провери. Извършваме проверки на всеки от екземплярите от поне три възела за проверка на състоянието. Ние разделяме целите на проверките между възлите, използвайки последователни алгоритми за хеширане.

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Разделянето на балансирането и проверката на състоянието може да доведе до проблеми. Ако възелът за проверка на здравето прави заявки към екземпляра, заобикаляйки балансиращия (който в момента не обслужва трафик), тогава възниква странна ситуация: ресурсът изглежда жив, но трафикът няма да достигне до него. Решаваме този проблем по следния начин: гарантирано ни е да инициираме трафик за проверка на състоянието чрез балансьори. С други думи, схемата за преместване на пакети с трафик от клиенти и от проверки на здравето се различава минимално: и в двата случая пакетите ще достигнат до балансьорите, които ще ги доставят до целевите ресурси.

Разликата е, че клиентите правят заявки към VIP, докато здравните проверки правят заявки към всеки отделен RIP. Тук възниква интересен проблем: ние даваме на нашите потребители възможност да създават ресурси в сиви IP мрежи. Нека си представим, че има двама различни собственици на облак, които са скрили услугите си зад балансьори. Всеки от тях има ресурси в подмрежата 10.0.0.1/24, с еднакви адреси. Трябва да можете по някакъв начин да ги различите и тук трябва да се потопите в структурата на виртуалната мрежа Yandex.Cloud. По-добре е да разберете повече подробности в видео от about:cloud събитие, сега за нас е важно, че мрежата е многопластова и има тунели, които могат да бъдат разграничени по идентификатор на подмрежа.

Healthcheck възлите се свързват с балансьори, използвайки така наречените квази-IPv6 адреси. Квазиадресът е IPv6 адрес с IPv4 адрес и идентификатор на потребителска подмрежа, вградени в него. Трафикът достига до балансира, който извлича IPv4 адреса на ресурса от него, заменя IPv6 с IPv4 и изпраща пакета към мрежата на потребителя.

Обратният трафик върви по същия начин: балансиращият вижда, че дестинацията е сива мрежа от проверките на здравето и преобразува IPv4 в IPv6.

VPP - сърцето на равнината на данни

Балансьорът е реализиран чрез технологията Vector Packet Processing (VPP), рамка от Cisco за групова обработка на мрежов трафик. В нашия случай рамката работи върху библиотеката за управление на мрежови устройства в потребителското пространство - Data Plane Development Kit (DPDK). Това гарантира висока производителност на обработка на пакети: много по-малко прекъсвания възникват в ядрото и няма контекстни превключвания между пространството на ядрото и потребителското пространство. 

VPP отива дори по-далеч и изстисква още повече производителност от системата чрез комбиниране на пакети в партиди. Подобренията в производителността идват от агресивното използване на кешове на съвременните процесори. Използват се както кеша за данни (пакетите се обработват във „вектори“, данните са близки един до друг), така и кеша за инструкции: във VPP обработката на пакети следва графика, чиито възли съдържат функции, които изпълняват една и съща задача.

Например обработката на IP пакети във VPP се извършва в следния ред: първо заглавките на пакетите се анализират в възела за анализ и след това се изпращат до възела, който препраща пакетите по-нататък според таблиците за маршрутизиране.

Малко хардкор. Авторите на VPP не толерират компромиси при използването на кеш паметта на процесора, така че типичният код за обработка на вектор от пакети съдържа ръчна векторизация: има цикъл на обработка, в който се обработва ситуация като „имаме четири пакета в опашката“, после същото за двама, после - за един. Инструкциите за предварително извличане често се използват за зареждане на данни в кеш памети, за да се ускори достъпът до тях в следващите итерации.

n_left_from = frame->n_vectors;
while (n_left_from > 0)
{
    vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
    // ...
    while (n_left_from >= 4 && n_left_to_next >= 2)
    {
        // processing multiple packets at once
        u32 next0 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        u32 next1 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        // ...
        /* Prefetch next iteration. */
        {
            vlib_buffer_t *p2, *p3;

            p2 = vlib_get_buffer (vm, from[2]);
            p3 = vlib_get_buffer (vm, from[3]);

            vlib_prefetch_buffer_header (p2, LOAD);
            vlib_prefetch_buffer_header (p3, LOAD);

            CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
            CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
        }
        // actually process data
        /* verify speculative enqueues, maybe switch current next frame */
        vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
                to_next, n_left_to_next,
                bi0, bi1, next0, next1);
    }

    while (n_left_from > 0 && n_left_to_next > 0)
    {
        // processing packets by one
    }

    // processed batch
    vlib_put_next_frame (vm, node, next_index, n_left_to_next);
}

И така, Healthchecks говорят през IPv6 с VPP, което ги превръща в IPv4. Това се прави от възел в графиката, който наричаме алгоритмичен NAT. За обратен трафик (и преобразуване от IPv6 към IPv4) има същия алгоритмичен NAT възел.

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Директният трафик от клиентите на балансиращия минава през графичните възли, които извършват самото балансиране. 

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Първият възел е лепкави сесии. Той съхранява хеша на 5-кортеж за установени сесии. 5-кортежът включва адреса и порта на клиента, от който се предава информацията, адреса и портовете на наличните ресурси за получаване на трафик, както и мрежовия протокол. 

Хешът с 5 кортежа ни помага да извършваме по-малко изчисления в последващия възел за последователно хеширане, както и да обработваме по-добре промените в списъка с ресурси зад балансира. Когато пакет, за който няма сесия, пристигне в балансира, той се изпраща до последователния хеширащ възел. Това е мястото, където се извършва балансиране с помощта на последователно хеширане: избираме ресурс от списъка с налични „живи“ ресурси. След това пакетите се изпращат до NAT възела, който всъщност замества адреса на дестинацията и преизчислява контролните суми. Както можете да видите, ние следваме правилата на VPP - like to like, групиране на подобни изчисления, за да увеличим ефективността на кеш паметта на процесора.

Последователно хеширане

Защо го избрахме и какво изобщо представлява? Първо, нека разгледаме предишната задача - избор на ресурс от списъка. 

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

При непоследователно хеширане се изчислява хешът на входящия пакет и се избира ресурс от списъка чрез остатъка от разделянето на този хеш на броя на ресурсите. Докато списъкът остава непроменен, тази схема работи добре: ние винаги изпращаме пакети с един и същи 5-кортеж до едно и също копие. Ако например някой ресурс спря да отговаря на проверките на здравето, тогава за значителна част от хешовете изборът ще се промени. TCP връзките на клиента ще бъдат прекъснати: пакет, който преди това е достигнал екземпляр А, може да започне да достига до екземпляр Б, който не е запознат със сесията за този пакет.

Последователното хеширане решава описания проблем. Най-лесният начин да обясните тази концепция е следният: представете си, че имате пръстен, към който разпределяте ресурси по хеш (например по IP:порт). Избирането на ресурс представлява завъртане на колелото на ъгъл, който се определя от хеша на пакета.

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Това минимизира преразпределението на трафика при промяна на състава на ресурсите. Изтриването на ресурс ще засегне само частта от последователния пръстен за хеширане, в който се намира ресурсът. Добавянето на ресурс също променя разпределението, но имаме лепкав възел за сесии, който ни позволява да не превключваме вече установени сесии към нови ресурси.

Разгледахме какво се случва с насочването на трафика между балансьора и ресурсите. Сега нека разгледаме обратния трафик. Той следва същия модел като трафика за проверка - чрез алгоритмичен NAT, тоест чрез обратен NAT 44 за клиентски трафик и през NAT 46 за трафик за проверка на състоянието. Ние се придържаме към нашата собствена схема: ние обединяваме трафика от проверки на здравето и реалния потребителски трафик.

Loadbalancer-възел и сглобени компоненти

Съставът на балансьорите и ресурсите във VPP се отчита от локалната услуга - loadbalancer-node. Той се абонира за потока от събития от loadbalancer-controller и може да начертае разликата между текущото VPP състояние и целевото състояние, получено от контролера. Получаваме затворена система: събития от API идват към контролера за балансиране, който възлага задачи на контролера за проверка на здравето, за да провери „жизнеността“ на ресурсите. Това от своя страна възлага задачи на възела за проверка на здравето и обобщава резултатите, след което ги изпраща обратно към контролера за балансиране. Loadbalancer-node се абонира за събития от контролера и променя състоянието на VPP. В такава система всяка услуга знае само това, което е необходимо за съседните услуги. Броят на връзките е ограничен и ние имаме способността да управляваме и мащабираме различни сегменти независимо.

Архитектура за балансиране на мрежовото натоварване в Yandex.Cloud

Какви проблеми бяха избегнати?

Всички наши услуги в контролната равнина са написани на Go и имат добри характеристики за мащабиране и надеждност. Go има много библиотеки с отворен код за изграждане на разпределени системи. Ние активно използваме GRPC, всички компоненти съдържат внедряване с отворен код за откриване на услуги - нашите услуги наблюдават ефективността на другите, могат да променят състава си динамично и ние свързахме това с GRPC балансиране. За показателите ние също използваме решение с отворен код. В равнината на данните получихме прилична производителност и голям резерв от ресурси: оказа се много трудно да се събере стойка, на която можем да разчитаме на производителността на VPP, а не на желязна мрежова карта.

Проблеми и решения

Какво не работи толкова добре? Go има автоматично управление на паметта, но течове на памет все още се случват. Най-лесният начин да се справите с тях е да стартирате goroutines и не забравяйте да ги прекратите. Извод за вкъщи: Гледайте потреблението на памет от вашите Go програми. Често добър показател е броят на goroutines. Има плюс в тази история: в Go е лесно да получите данни за времето на изпълнение - потребление на памет, брой изпълнявани goroutines и много други параметри.

Освен това Go може да не е най-добрият избор за функционални тестове. Те са доста многословни и стандартният подход за „пускане на всичко в CI в пакет“ не е много подходящ за тях. Факт е, че функционалните тестове са по-изискващи ресурси и причиняват реални изчаквания. Поради това тестовете може да се провалят, тъй като процесорът е зает с тестове на единици. Заключение: Ако е възможно, извършете „тежки“ тестове отделно от тестовете на единици. 

Архитектурата на събитията на микросервизите е по-сложна от монолит: събирането на регистрационни файлове на десетки различни машини не е много удобно. Заключение: ако правите микроуслуги, незабавно помислете за проследяване.

Нашите планове

Ще стартираме вътрешен балансьор, IPv6 балансьор, ще добавим поддръжка за Kubernetes скриптове, ще продължим да разделяме нашите услуги (понастоящем само healthcheck-node и healthcheck-ctrl са шардирани), ще добавим нови проверки на здравето и също ще внедрим интелигентно агрегиране на проверки. Обмисляме възможността да направим нашите услуги още по-независими - така че те да комуникират не директно помежду си, а чрез опашка от съобщения. Наскоро в облака се появи услуга, съвместима с SQS Опашка за съобщения на Yandex.

Наскоро се състоя публичното издание на Yandex Load Balancer. Разгледайте документация към услугата, управлявайте балансьори по удобен за вас начин и увеличете отказоустойчивостта на вашите проекти!

Източник: www.habr.com

Добавяне на нов коментар