Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes
Тази статия ще ви помогне да разберете как работи балансирането на натоварването в Kubernetes, какво се случва при мащабиране на дългосрочни съединения и защо трябва да се обмисли клиентското балансиране, ако използвате HTTP/2, gRPC, RSockets, AMQP или други дългосрочни протоколи. 

Няколко думи за това как трафикът се разпределя в Kubernetes 

Kubernetes предоставя две удобни абстракции за внедряване на приложения: услуги (Services) и разгръщания (Deployments).

Разгръщанията описват как и колко копия от вашето приложение трябва да бъдат стартирани по всяко време. Всяко приложение се разгръща като под (Pod) и му се назначава IP адрес.

Услугите по функции наподобяват балансир на натоварването. Те са предназначени за разпределяне на трафика между множество подове.

Нека видим как изглежда това.

  1. На диаграмата по-долу виждате три инстанции на едно приложение и балансир на натоварването:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  2. Балансирът на натоварването се нарича услуга (Service) и му е присвоен IP адрес. Всеки входящ запрос се пренасочва към един от подовете:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  3. Сценарият за разгръщане определя броя на инстанциите на приложението. Вие практически никога няма да разгръщате непосредственно под:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  4. На всеки под се назначава негов собствен IP адрес:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Полезно е да се разглеждат услугите като набор от IP адреси. Всеки път, когато се обърнете към услугата, един от IP адресите се избира от списъка и се използва като адрес на дестинация.

Това изглежда по следния начин.

  1. Постъпва запитване curl 10.96.45.152 към услугата:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  2. Услугата избира един от трите адреса на подовете като дестинация:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  3. Трафикът се пренасочва към конкретен под:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Ако вашето приложение се състои от фронтенд и бекенд, то ще имате и услуга, и разгръщане за всеки.

Когато фронтендът прави запитване към бекенда, той няма нужда да знае колко точно пода обслужва бекендът: те могат да бъдат един, десет или сто.

Също така, фронтендът не знае адресите на подовете, обслужващи бекенда.

Когато фронтендът прави запитване към бекенда, той използва IP адреса на услугата на бекенда, който не се променя.

Ето как изглежда това.

  1. Под 1 прави запитване към вътрешния компонент на бекенда. Вместо да избере конкретен под на бекенда, той прави запитване към услугата:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  2. Сервисът избира един от подовете на бекенда като целеви адрес:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  3. Трафикът минава от под 1 към под 5, избран от сервиза:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  4. Под 1 не знае колко точно такива подове, като под 5, са скрити зад сервиза:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Но как точно сервизът разпределя заявките? Изглежда, че се използва балансировка на принципа round-robin? Нека да разберем. 

Балансировка в сервисите на Kubernetes

Сервисите на Kubernetes не съществуват. За сервиза не съществува процес, на който да е назначен IP-адрес и порт.

Можете да се уверите в това, като влезете на всяка нода от кластера и изпълните командата netstat -ntlp.

Вие дори няма да успеете да намерите IP-адрес, назначен на сервиза.

IP-адресът на сервиза е разположен в управленския слой, в контролера, и е записан в базата данни - etcd. Този адрес се използва и от друг компонент - kube-proxy.
Kube-proxy получава списък с IP-адреси за всички услуги и формира набор от правила iptables на всяка нода от кластера.

Тези правила казват: «Ако видим IP-адреса на сервиза, трябва да променим целевия адрес на заявката и да я насочим към един от подовете».

IP-адресът на сервиза се използва само като входна точка и не се обслужва от никакъв процес, който да слуша този IP-адрес и порт.

Нека да разгледаме това

  1. Нека разгледаме клъстера, състоящ се от три ноди. На всяка нода има подове:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  2. Свързаните подове, оцветени в бежов цвят, са част от сервиза. Тъй като сервизът не съществува като процес, той е изобразен в сив цвят:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  3. Първият под отправя заявка към сервиза и трябва да достигне един от свързаните подове:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  4. Но сервизът не съществува, процес няма. Как тогава работи?

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  5. Преди заявката да напусне нодата, тя преминава през правилата на iptables:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  6. Правилата на iptables знаят, че сервиза не съществува и заменят неговия IP-адрес с един от IP-адресите на подовете, свързани с този сервиз:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  7. Заявката получава валиден IP-адрес като целеви адрес и се обработва нормално:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  8. В зависимост от мрежовата топология, заявката в крайна сметка достига до пода:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Могат ли iptables да балансират натоварването?

Не, iptables се използват за филтриране и не са проектирани за балансировка.

Въпреки това има възможност да се напише набор от правила, които да действат като псевдобалансер.

И точно това е реализирано в Kubernetes.

Ако имате три пода, kube-proxy ще напише следните правила:

  1. Да се избере първият под с вероятност 33%, в противен случай да се премине към следващото правило.
  2. Изберете втори под с вероятност 50%, в противен случай преминете към следното правило.
  3. Изберете трети под.

Тази система води до избиране на всеки под с вероятност 33%.

Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Няма никаква гаранция, че под 2 ще бъде избран следващия след под 1.

Забележка: iptables използва статистически модул с произволно разпределение. Така алгоритъмът за балансиране се основава на случайния избор.

Сега, когато разбирате как работят услугите, нека разгледаме по-интересни сценарии.

Дългоживеещи връзки в Kubernetes не се мащабират по подразбиране.

Всеки HTTP-запит от фронтенда към бекенда се обслужва от отделна TCP-връзка, която се отваря и затваря.

Ако фронтендът изпрати 100 запита в секунда към бекенда, ще бъдат отворени и затворени 100 различни TCP-връзки.

Можете да намалите времето за обработка на заявките и да намалите натоварването, като отворите едно TCP-соединение и го използвате за всички последващи HTTP-запити.

В HTTP протокола е заложена възможност, наречена HTTP keep-alive или повторно използване на връзката. В този случай едно TCP-съединение се използва за изпращане и получаване на множество HTTP-запити и отговори:

Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Тази функция не е включена по подразбиране: както сървърът, така и клиентът трябва да бъдат конфигурирани съответно.

Самата настройка е проста и достъпна за повечето програмни езици и среди.

Ето няколко връзки към примери на различни езици:

Какво ще се случи, ако използваме keep-alive в услугата Kubernetes?
Да предположим, че и фронтендът, и бекендът поддържат keep-alive.

Имаме едно копие на фронтенда и три инстанции на бекенда. Фронтендът прави първото запитване и отваря TCP-соединение към бекенда. Запитването достига до услугата, един от подовете на бекенда се избира като целеви адрес. Подът на бекенда изпраща отговор и фронтендът го получава.

В противовес на обичайната ситуация, когато TCP-връзката се затваря след получаване на отговора, в момента тя остава отворена за следващи HTTP-запитвания.

Какво ще се случи, ако фронтендът изпрати още запитвания към бекенда?

За пренасочване на тези запитвания ще бъде използвано вече отвореното TCP-съединение, всички запитвания ще попаднат на същия под на бекенда, на който попадна първото запитване.

Нали ли iptables не трябва да преразпределя трафика?

Не в този случай.

Когато се създава TCP връзка, тя преминава през правилата на iptables, които избират конкретния под на бекенда, към който ще се насочи трафикът.

Тъй като всички следващи заявки преминават по вече отворената TCP връзка, правилата на iptables вече не се извикват.

Нека видим как изглежда това.

  1. Първият под отправя заявка към услугата:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  2. Вие вече знаете какво ще стане след това. Услугата не съществува, но има правила на iptables, които ще обработят заявката:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  3. Един от подовете на бекенда ще бъде избран като адрес на дестинация:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  4. Заявката достига до пода. В този момент ще бъде установена постоянна TCP връзка между двата пода:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  5. Всяка следваща заявка от първия под ще минава по вече установената връзка:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

В резултат на това получавате по-бърз отговор и по-висока пропускна способност, но губите възможността за скалиране на бекенда.

Дори и да имате два пода в бекенда, при постоянна връзка трафикът през цялото време ще попада на един от тях.

Може ли това да бъде поправено?

Тъй като Kubernetes не знае как да балансира постоянни връзки, задачата е възложена на вас.

Услугите са набор от IP адреси и портове, които наричат крайни точки.

Вашето приложение може да получи списък с крайни точки от услугата и да реши как да разпределя заявките между тях. Можете да отворите постоянна връзка с всеки под и да балансирате заявките между тези връзки с помощта на round-robin.

Или да приложите по- сложни алгоритми за балансировка.

Кодът на страната на клиента, отговорен за балансировката, трябва да следва следната логика:

  1. Да получите списък с крайни точки от услугата.
  2. За всяка крайна точка да отворите постоянна връзка.
  3. Когато е необходимо да се направи заявка, да се използва една от отворените връзки.
  4. Редовно да актуализирате списъка с крайни точки, да създавате нови или да затваряте стари постоянни връзки в случай на промяна на списъка.

Ето как ще изглежда това..

  1. Вместо първият под да изпрати заявка към услугата, можете да балансирате заявките от страната на клиента:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  2. Трябва да напишете код, който пита кои подове са част от услугата:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  3. След като получите списъка, запазете го от страната на клиента и го използвайте за свързване с подовете:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

  4. Вие сами отговаряте за алгоритъма за балансиране на натоварването:

    Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Сега възниква въпросът: Отнася ли се този проблем само за HTTP keep-alive?

Балансировка на натоварването от страна на клиента

HTTP не е единственият протокол, който може да използва постоянни TCP съединения.

Ако приложението ви използва база данни, то TCP съединението не се отваря всеки път, когато трябва да извършите запитване или да получите документ от базата данни. 

Вместо това се отваря и използва постоянно TCP съединение към базата данни.

Ако базата данни е развита в Kubernetes и достъпът е предоставен под формата на услуга, ще се сблъскате със същите проблеми, описани в предишния раздел.

Една реплика на базата данни ще бъде натоварена повече от останалите. Kube-proxy и Kubernetes няма да помогнат за балансирането на съединенията. Вие трябва да се погрижите за балансирането на запитванията към базата данни.

В зависимост от това коя библиотека използвате за свързване към БД, може да имате различни опции за решаване на този проблем.

По-долу е приведен пример за достъп до MySQL клъстера от Node.js:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

Съществуват и много други протоколи, използващи постоянни TCP съединения:

  • WebSockets и защитени WebSockets
  • HTTP/2
  • gRPC
  • RSockets
  • AMQP

Вие вече би трябвало да сте запознати с повечето от тези протоколи.

Но ако тези протоколи са толкова популярни, защо няма стандартизирано решение за балансиране? Защо е необходимо да се променя логиката на клиента? Съществува ли нативно решение в Kubernetes?

Kube-proxy и iptables са създадени, за да покрият повечето стандартни сценарии при разгръщане в Kubernetes. Това е направено за удобство.

Ако използвате уеб услуга, която предоставя REST API, имате късмет — в този случай постоянни TCP съединения не се използват, можете да използвате всяка услуга в Kubernetes.

Но веднага щом започнете да използвате постоянни TCP съединения, ще трябва да разберете как да разпределите натоварването равномерно между бекендовете. Kubernetes не предлага готови решения за тази ситуация.

Въпреки това, разбира се, съществуват опции, които могат да помогнат.

Балансировка на дългоживеещи съединения в Kubernetes

В Kubernetes има четири типа услуги:

  1. ClusterIP
  2. NodePort
  3. LoadBalancer
  4. Headless

Първите три услуги работят на базата на виртуален IP адрес, който kube-proxy използва за създаване на правила в iptables. Но основата на всички услуги е headless услугата.

С headless услугата не е свързан никакъв IP адрес, а тя просто предоставя механизъм за получаване на списък с IP адреси и портове, свързани с нейните подове (крайни точки).

Всички услуги са базирани на headless услугата.

Услугата ClusterIP е headless услуга с някои допълнения: 

  1. Управляващият слой назначава IP адрес.
  2. Kube-proxy формира необходимите правила в iptables.

По този начин можете да игнорирате kube-proxy и директно да използвате списъка с крайни точки, получен от headless услугата за натоварване на баланса във вашето приложение.

Но как да добавите подобна логика към всички приложения, разположени в кластера?

Ако вашето приложение вече е разположено, тази задача може да изглежда невъзможна. Въпреки това, има алтернативен вариант.

Service Mesh може да ви помогне.

Вероятно вече сте забелязали, че стратегията за натоварване на баланса от страната на клиента е напълно стандартна.

Когато приложението стартира, то:

  1. Получава списък от IP адреси от услугата.
  2. Отваря и поддържа пул от връзки.
  3. Периодично актуализира пула, добавяйки или премахвайки крайни точки.

Когато приложението иска да направи заявка, то:

  1. Избира налична връзка, използвайки някаква логика (например, round-robin).
  2. Изпълнява заявка.

Тези стъпки работят и за WebSockets, и за gRPC, и за AMQP.

Можете да извлечете тази логика в отделна библиотека и да я използвате в вашите приложения.

Вместо това обаче можете да използвате мрежови услуги, като Istio или Linkerd.

Service Mesh допълва вашето приложение с процес, който:

  1. Автоматично търси IP адреси на услуги.
  2. Проверява връзките, такива като WebSockets и gRPC.
  3. Балансирa заявките, използвайки правилния протокол.

Service Mesh помага за управление на трафика в кластера, но е доста ресурсоемък. Други опции са използването на външни библиотеки, като Netflix Ribbon, или програмирани проксита, като Envoy.

Какво ще се случи, ако игнорираме въпросите за баланса на натоварването?

Можете да не използвате балансиране на натоварването и да не забележите никакви промени. Нека разгледаме няколко сценария на работа.

Ако имате повече клиенти, отколкото сървъри, това не е такава голяма проблема.

Да предположим, че има пет клиенти, които се свързват с два сървъра. Дори и без балансировка, и двата сървъра ще бъдат използвани:

Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Връзките може да бъдат разпределени неравномерно: може би четирима клиенти са се свързали с един и същ сървър, но има добра вероятност и двата сървъра да бъдат използвани.

По-проблематичен е противоположният сценарий.

Ако имате по-малко клиенти и повече сървъри, ресурсите ви може да не се използват достатъчно и може да се появи потенциално тясно място.

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

Останалите сървъри ще останат неактивни:

Балансировка на натоварването и мащабиране на дългосрочни връзки в Kubernetes

Ако тези два сървъра не могат да се справят с обработката на клиентските заявки, хоризонталното мащабиране няма да помогне.

Заключение

Услугите на Kubernetes са създадени да работят в повечето стандартни сценарии на уеб приложения.

Въпреки това, веднага щом започнете да работите с приложни протоколи, които използват постоянни TCP връзки, като бази данни, gRPC или WebSockets, услугите не са вече подходящи. Kubernetes не предоставя вградени механизми за балансировка на постоянни TCP връзки.

Това означава, че трябва да пишете приложенията с предвидената възможност за балансировка от страна на клиента.

Преводът е подготвен от екипа Kubernetes aaS от Mail.ru.

Какво още да прочетете по темата:

  1. Три нива на автоскалиране в Kubernetes и как да ги използвате ефективно
  2. Kubernetes вдъхновен от пиратството с шаблон за внедряване.
  3. Нашият канал в Telegram за цифрова трансформация.

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

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster