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

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

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

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

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

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

Да видим как изглежда.

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

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

  2. Устройството за балансиране на натоварването се нарича услуга и му се присвоява 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. Трафикът преминава от Pod 1 до Pod 5, избран от услугата:

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

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

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

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

Балансиране в услугите на Kubernetes

Услугите на Kubernetes не съществуват. Няма процес за услугата, на която са присвоени IP адрес и порт.

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

Дори няма да можете да намерите IP адреса, определен за услугата.

IP адресът на услугата се намира в контролния слой, в контролера и е записан в базата данни - и т.н. Същият адрес се използва от друг компонент - 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.

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

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

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

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

И няма гаранция, че Pod 2 ще бъде избран следващия след Pod 1.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Да видим как изглежда.

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

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

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

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

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

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

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

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

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

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

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

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

Може ли това да се поправи?

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

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

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

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

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

  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. без глава

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

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

Всички услуги се основават на услугата без глава.

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

  1. Слоят за управление му присвоява IP адрес.
  2. Kube-прокси генерира необходимите правила за iptables.

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

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

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

Service Mesh ще ви помогне

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

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

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

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

  1. Избира налична връзка с помощта на някаква логика (напр. кръгова система).
  2. Изпълнява заявката.

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

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

Въпреки това можете да използвате сервизни мрежи като Istio или Linkerd вместо това.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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

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

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

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

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

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

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

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