Преходът на Tinder към Kubernetes

Забележка. превод: Служители на световноизвестната услуга Tinder наскоро споделиха някои технически подробности за мигрирането на тяхната инфраструктура към Kubernetes. Процесът отне почти две години и доведе до стартирането на много мащабна платформа на K8s, състояща се от 200 услуги, хоствани в 48 хиляди контейнера. Какви интересни трудности срещнаха инженерите на Tinder и до какви резултати стигнаха? Прочетете този превод.

Преходът на Tinder към Kubernetes

Защо?

Преди почти две години Tinder реши да премести своята платформа в Kubernetes. Kubernetes ще позволи на екипа на Tinder да контейнеризира и премине към производство с минимални усилия чрез неизменно внедряване (неизменно внедряване). В този случай сглобяването на приложения, тяхното внедряване и самата инфраструктура биха били уникално дефинирани от код.

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

Процесът се оказа труден. По време на нашата миграция в началото на 2019 г. клъстерът Kubernetes достигна критична маса и започнахме да срещаме различни проблеми поради обема на трафика, размера на клъстера и DNS. По пътя решихме много интересни проблеми, свързани с мигрирането на 200 услуги и поддържането на клъстер Kubernetes, състоящ се от 1000 възли, 15000 48000 подове и XNUMX XNUMX работещи контейнера.

Как?

От януари 2018 г. преминахме през различни етапи на миграция. Започнахме с контейнеризиране на всички наши услуги и внедряването им в тестови облачни среди на Kubernetes. От октомври започнахме методично да мигрираме всички съществуващи услуги към Kubernetes. До март на следващата година завършихме миграцията и сега платформата Tinder работи изключително на Kubernetes.

Изграждане на изображения за Kubernetes

Имаме над 30 хранилища за изходен код за микроуслуги, работещи на клъстер на Kubernetes. Кодът в тези хранилища е написан на различни езици (например Node.js, Java, Scala, Go) с множество среди за изпълнение за един и същи език.

Системата за изграждане е проектирана да предоставя напълно адаптивен „контекст за изграждане“ за всяка микроуслуга. Обикновено се състои от Dockerfile и списък с команди на shell. Тяхното съдържание е напълно персонализирано и в същото време всички тези контексти на изграждане са написани в съответствие със стандартизиран формат. Стандартизирането на контекстите на изграждане позволява една единствена система за изграждане да обработва всички микроуслуги.

Преходът на Tinder към Kubernetes
Фигура 1-1. Стандартизиран процес на изграждане чрез контейнер на Builder

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

Неговата реализация на контейнери изискваше усъвършенствани Docker техники. Builder наследява локалния потребителски идентификатор и тайни (като SSH ключ, идентификационни данни за AWS и т.н.), необходими за достъп до частни хранилища на Tinder. Той монтира локални директории, съдържащи източници, за естествено съхраняване на компилирани артефакти. Този подход подобрява производителността, тъй като елиминира необходимостта от копиране на артефакти за изграждане между контейнера на Builder и хоста. Съхранените компилационни артефакти могат да се използват повторно без допълнителна конфигурация.

За някои услуги трябваше да създадем друг контейнер, за да картографираме средата за компилиране към средата за изпълнение (например библиотеката Node.js bcrypt генерира специфични за платформата бинарни артефакти по време на инсталацията). По време на процеса на компилиране изискванията може да варират между услугите и окончателният Dockerfile се компилира в движение.

Клъстерна архитектура и миграция на Kubernetes

Управление на размера на клъстера

Решихме да използваме kube-aws за автоматизирано внедряване на клъстер в инстанси на Amazon EC2. В самото начало всичко работеше в един общ пул от възли. Бързо осъзнахме необходимостта от разделяне на работните натоварвания по размер и тип екземпляр, за да използваме по-ефективно ресурсите. Логиката беше, че стартирането на няколко заредени многонишкови подове се оказа по-предвидимо по отношение на производителността, отколкото съвместното им съществуване с голям брой еднонишкови подове.

В крайна сметка се спряхме на:

  • m5.4xголям — за наблюдение (Prometheus);
  • c5.4xголям - за натоварване на Node.js (еднопоточно натоварване);
  • c5.2xголям - за Java и Go (многопоточно натоварване);
  • c5.4xголям — за контролния панел (3 възела).

миграция

Една от подготвителните стъпки за мигриране от старата инфраструктура към Kubernetes беше пренасочването на съществуващата директна комуникация между услугите към новите балансьори на натоварването (Elastic Load Balancers (ELB). Те са създадени в конкретна подмрежа на виртуален частен облак (VPC). Тази подмрежа беше свързана с Kubernetes VPC. Това ни позволи да мигрираме модулите постепенно, без да вземаме предвид специфичния ред на зависимостите на услугата.

Тези крайни точки са създадени с помощта на претеглени набори от DNS записи, които имат CNAME, сочещи към всеки нов ELB. За да превключим, добавихме нов запис, сочещ към новия ELB на услугата Kubernetes с тегло 0. След това зададохме времето за живот (TTL) на набора запис на 0. След това старите и новите тегла бяха бавно се коригира и в крайна сметка 100% от натоварването беше изпратено към нов сървър. След като превключването приключи, TTL стойността се върна на по-адекватно ниво.

Java модулите, които имахме, можеха да се справят с нисък TTL DNS, но приложенията на Node не можеха. Един от инженерите пренаписа част от кода на пула за връзка и го уви в мениджър, който актуализира пуловете на всеки 60 секунди. Избраният подход работи много добре и без забележимо влошаване на производителността.

Уроците

Ограниченията на мрежовата структура

Рано сутринта на 8 януари 2019 г. платформата Tinder неочаквано се срина. В отговор на несвързано увеличение на латентността на платформата по-рано същата сутрин, броят на подовете и възлите в клъстера се увеличи. Това доведе до изчерпване на ARP кеша на всички наши възли.

Има три Linux опции, свързани с ARP кеша:

Преходът на Tinder към Kubernetes
(източник)

gc_thresh3 - това е твърда граница. Появата на записи за „препълване на съседна таблица“ в дневника означаваше, че дори след синхронно събиране на отпадъци (GC), няма достатъчно място в ARP кеша за съхраняване на съседния запис. В този случай ядрото просто е изхвърлило пакета напълно.

Ние използваме бархет като мрежова тъкан в Kubernetes. Пакетите се предават през VXLAN. VXLAN е L2 тунел, издигнат върху L3 мрежа. Технологията използва MAC-in-UDP (MAC Address-in-User Datagram Protocol) капсулиране и позволява разширяване на Layer 2 мрежови сегменти. Транспортният протокол във физическата мрежа на центъра за данни е IP плюс UDP.

Преходът на Tinder към Kubernetes
Фигура 2–1. Диаграма на фланела (източник)

Преходът на Tinder към Kubernetes
Фигура 2-2. VXLAN пакет (източник)

Всеки работен възел на Kubernetes разпределя виртуално адресно пространство с маска /24 от по-голям блок /9. За всеки възел това е средства един запис в таблицата за маршрутизиране, един запис в таблицата ARP (на интерфейса flannel.1) и един запис в таблицата за превключване (FDB). Те се добавят при първото стартиране на работен възел или при всяко откриване на нов възел.

Освен това комуникацията между възел и под (или под-под) в крайна сметка минава през интерфейса eth0 (както е показано на диаграмата на Flannel по-горе). Това води до допълнителен запис в ARP таблицата за всеки съответен източник и целеви хост.

В нашата среда този тип комуникация е много разпространена. За обслужващи обекти в Kubernetes се създава ELB и Kubernetes регистрира всеки възел с ELB. ELB не знае нищо за pods и избраният възел може да не е крайната дестинация на пакета. Въпросът е, че когато възел получи пакет от ELB, той го разглежда, като вземе предвид правилата IPTABLES за конкретна услуга и произволно избира под на друг възел.

По време на повредата в клъстера имаше 605 възела. Поради изложените по-горе причини това беше достатъчно, за да се преодолее значимостта gc_thresh3, което е по подразбиране. Когато това се случи, не само пакетите започват да се изпускат, но и цялото виртуално адресно пространство на Flannel с /24 маска изчезва от ARP таблицата. Комуникацията на Node-pod и DNS заявките са прекъснати (DNS се хоства в клъстер; прочетете по-късно в тази статия за подробности).

За да разрешите този проблем, трябва да увеличите стойностите gc_thresh1, gc_thresh2 и gc_thresh3 и рестартирайте Flannel, за да регистрирате отново липсващите мрежи.

Неочаквано DNS мащабиране

По време на процеса на миграция ние активно използвахме DNS за управление на трафика и постепенно прехвърляне на услуги от старата инфраструктура към Kubernetes. Ние задаваме относително ниски TTL стойности за свързаните RecordSets в Route53. Когато старата инфраструктура работеше на екземпляри EC2, нашата конфигурация на резолвер сочеше към Amazon DNS. Приехме това за даденост и въздействието на ниския TTL върху нашите услуги и услугите на Amazon (като DynamoDB) остана до голяма степен незабелязано.

Докато мигрирахме услугите към Kubernetes, открихме, че DNS обработва 250 хиляди заявки в секунда. В резултат на това приложенията започнаха да изпитват постоянни и сериозни изчаквания за DNS заявки. Това се случи въпреки невероятните усилия за оптимизиране и превключване на DNS доставчика към CoreDNS (който при пиково натоварване достигна 1000 подове, работещи на 120 ядра).

Докато проучвахме други възможни причини и решения, открихме Статия, описващи условия на състезание, засягащи рамката за филтриране на пакети Netfilter в Linux. Изчакванията, които наблюдавахме, съчетани с нарастващ брояч вмъкване_неуспешно в интерфейса Flannel са в съответствие с констатациите в статията.

Проблемът възниква на етапа на транслация на мрежов адрес на източник и дестинация (SNAT и DNAT) и последващо въвеждане в таблицата conntrack. Едно от заобиколните решения, обсъдени вътрешно и предложени от общността, беше преместването на DNS към самия работен възел. В такъв случай:

  • SNAT не е необходим, защото трафикът остава вътре в възела. Не е необходимо да се маршрутизира през интерфейса eth0.
  • DNAT не е необходим, тъй като целевият IP е локален за възела, а не произволно избран под според правилата IPTABLES.

Решихме да се придържаме към този подход. CoreDNS беше внедрен като DaemonSet в Kubernetes и внедрихме DNS сървър на локален възел в resolve.conf всяка капсула чрез задаване на флаг --cluster-dns команди кубелет . Това решение се оказа ефективно за изчакване на DNS.

Все пак видяхме загуба на пакети и увеличение на брояча вмъкване_неуспешно в интерфейса Flannel. Това продължи и след прилагането на заобиколното решение, тъй като успяхме да елиминираме SNAT и/или DNAT само за DNS трафик. Състезателните условия бяха запазени за други видове трафик. За щастие повечето от нашите пакети са TCP и ако възникне проблем, те просто се препредават. Все още се опитваме да намерим подходящо решение за всички видове трафик.

Използване на Envoy за по-добро балансиране на натоварването

Докато мигрирахме бекенд услуги към Kubernetes, започнахме да страдаме от небалансирано натоварване между подовете. Установихме, че HTTP Keepalive е причинил ELB връзки да висят на първите готови модули на всяко внедряване. По този начин по-голямата част от трафика премина през малък процент от наличните подс. Първото решение, което тествахме, беше настройката на MaxSurge на 100% при нови внедрявания за най-лошия случай. Ефектът се оказа незначителен и необещаващ от гледна точка на по-големи внедрявания.

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

Ние отдавна искахме да оценим напълно пратеник. Настоящата ситуация ни позволи да го разположим по много ограничен начин и да получим незабавни резултати. Envoy е високоефективен, с отворен код, слой-XNUMX прокси, предназначен за големи SOA приложения. Той може да прилага усъвършенствани техники за балансиране на натоварването, включително автоматични повторни опити, прекъсвачи и глобално ограничаване на скоростта. (Забележка. превод: Можете да прочетете повече за това в тази статия относно Istio, който е базиран на Envoy.)

Измислихме следната конфигурация: имайте кош на Envoy за всяка капсула и единичен маршрут и свържете клъстера към контейнера локално чрез порт. За да сведем до минимум потенциалното каскадиране и да поддържаме малък радиус на попадение, ние използвахме набор от модули Envoy front-proxy, по един на зона на наличност (AZ) за всяка услуга. Те разчитаха на проста машина за откриване на услуги, написана от един от нашите инженери, която просто връщаше списък с подове във всяка AZ за дадена услуга.

След това Service Front-Envoys използва този механизъм за откриване на услуги с един възходящ клъстер и маршрут. Зададохме адекватни изчаквания, увеличихме всички настройки на прекъсвачите и добавихме конфигурация за минимален повторен опит, за да помогнем при единични повреди и да осигурим плавно внедряване. Поставихме TCP ELB пред всеки от тези сервизни предни посланици. Дори ако поддържането на активност от основния ни прокси слой беше заседнало на някои Envoy pods, те все пак успяха да се справят с натоварването много по-добре и бяха конфигурирани да балансират чрез least_request в бекенда.

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

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

Резултатите бяха незабавни и очевидни. Започнахме с най-небалансираните услуги, а в момента тя работи пред 12-те най-важни услуги в клъстера. Тази година планираме преход към мрежа с пълна услуга с по-усъвършенствано откриване на услуги, прекъсване на веригата, откриване на отклонения, ограничаване на скоростта и проследяване.

Преходът на Tinder към Kubernetes
Фигура 3–1. CPU конвергенция на една услуга по време на прехода към Envoy

Преходът на Tinder към Kubernetes

Преходът на Tinder към Kubernetes

Краен резултат

Чрез този опит и допълнителни изследвания ние изградихме силен инфраструктурен екип със силни умения в проектирането, внедряването и работата с големи Kubernetes клъстери. Всички инженери на Tinder вече имат знанията и опита да пакетират контейнери и да внедряват приложения в Kubernetes.

Когато възникна необходимостта от допълнителен капацитет на старата инфраструктура, трябваше да изчакаме няколко минути за стартиране на нови инстанции на EC2. Сега контейнерите започват да работят и започват да обработват трафик в рамките на секунди вместо минути. Планирането на множество контейнери на един екземпляр на EC2 също осигурява подобрена хоризонтална концентрация. В резултат на това прогнозираме значително намаляване на разходите за EC2019 през 2 г. в сравнение с миналата година.

Миграцията отне почти две години, но я завършихме през март 2019 г. В момента платформата Tinder работи изключително на клъстер Kubernetes, състоящ се от 200 услуги, 1000 възли, 15 000 подове и 48 000 работещи контейнера. Инфраструктурата вече не е единствената област на оперативните екипи. Всички наши инженери споделят тази отговорност и контролират процеса на изграждане и внедряване на своите приложения само с помощта на код.

PS от преводача

Прочетете също поредица от статии в нашия блог:

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

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