Контейнери, микросервизи и сервис мрежи

В интернет множество статии по- сервизни мрежи (service mesh), и ето още едно. Ура! Но защо? Защото искам да споделя мнението си, че беше по-добре сервизните мрежи да се появят преди 10 години, преди контейнерните платформи като Docker и Kubernetes. Не твърдя, че гледната ми точка е по-добра или по-лоша от другите, но тъй като сервизните мрежи са доста сложни същества, множеството гледни точки ще помогне за тяхното по-добро разбиране.

Ще разкажа за платформата dotCloud, която беше изградена на основанието на повече от сто микросервиза и поддържаше хиляди приложения в контейнери. Ще обясня проблемите, с които се сблъскахме при нейното разработване и стартиране, и как сервизните мрежи можеха да помогнат (или не).

История на dotCloud

Вече съм писал за историята на dotCloud и избора на архитектура за тази платформа, но малко разказах за мрежовото ниво. Ако не искате да се задълбочавате в четенето на предишната статия за dotCloud, накратко ето същността: това е платформа като услуга PaaS, позволяваща на клиентите да стартират широка гама от приложения (Java, PHP, Python…), с поддръжка на множество бази данни (MongoDB, MySQL, Redis…) и работния процес като при Heroku: качвате кода си на платформата, тя изгражда образи на контейнери и ги разгръща.

Ще разкажа как се насочваше трафикът към платформата dotCloud. Не защото беше нещо особено (въпреки че за времето си системата работеше доста добре!), а най-вече защото с помощта на съвременни инструменти такъв дизайн лесно може да бъде реализиран за кратко време от скромен екип, ако им е нужен начин за маршрутизиране на трафика между множество микросервиза или множество приложения. Така можем да сравним опциите: какво се получава, ако разработим всичко сами или използваме съществуваща сервизна мрежа. Стандартният избор: да направим сами или да купим.

Маршрутизиране на трафика за хоствани приложения

Приложенията на dotCloud могат да предоставят HTTP и TCP крайни точки.

HTTP крайни точки се добавят динамично към конфигурацията на кластерите на балансировките на натоварването Hipache. Това е подобно на това, което днес правят ресурсите Ingress в Kubernetes и балансировката на натоварването, подобно на Traefik.

Клиентите се свързват с HTTP крайните точки чрез съответните домейни при условие, че домейнът сочи към балансировките на натоварването на dotCloud. Нищо особено.

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

Клиентите могат да се свързват с TCP крайни точки, използвайки съответното име на хоста (нещо като gateway-X.dotcloud.com) и номер на порта.

Това име на хоста се разрешава до клъстера от сървъри “nats“ (няма отношение към NATS), които ще маршрутизират входящите TCP връзки към правилния контейнер (или, в случай на услуги с натоварваща балансировка, към правилните контейнери).

Ако сте запознати с Kubernetes, вероятно ще ви напомни за услугите NodePort.

На платформата dotCloud нямаше еквивалент на услугите ClusterIP: за простота, достъпът до услугите беше еднакъв както от вътре, така и от вън от платформата.

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

Обширен рефакторинг на съществуващия код не беше необходим. По-специално, 12-factor приложенията могат директно да използват адреса, получен чрез променливи на средата.

С какво това се различава от съвременната service mesh?

Ограничена обзорност. Ние въобще нямахме никакви метрики за маршрутизация TCP. Що се отнася до маршрутизацията на HTTP, в по-късните версии се появиха подробни HTTP метрики с кодове на грешки и време за отговор, но съвременните meshes за услуги отиват още по-далеч, осигурявайки интеграция с системи за събиране на метрики, като Prometheus, например.

Обзорността е важна не само от оперативна гледна точка (за да помага при разрешаване на проблеми), но и при пускането на нови функции. Става въпрос за безопасен син-зелен деплой и канарен деплой.

Ефективност на маршрутизацията също е ограничена. В маршрутизиращата мрежа dotCloud целият трафик трябваше да преминава през клъстера на специализираните маршрутизатори. Това означаваше потенциално пресичане на няколко граници на AZ (зони на наличност) и значително увеличение на закъснението. Помня как отстранявах проблеми с код, който правеше над сто SQL заявки на страница и за всяка заявка отваряше нова връзка с SQL сървъра. При локален старт страницата се зарежда мигновено, но в dotCloud зареждането отнема няколко секунди, защото за всяко TCP свързване (и последваща SQL заявка) са необходими десетки милисекунди. В този конкретен случай проблемът бе решен със постоянни връзки.

Съвременните сервис-решения се справят по-добре с такива проблеми. Първо, те проверяват, че връзките се маршрутизират в източника. Логичният поток е същият: клиент → мрежа → услуга, но сега мрежата работи локално, а не на отдалечени възли, така че връзката клиент → мрежа е локална и много бърза (микросекунди вместо милисекунди).

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

Сигурност също е по-добре. Мрежата за маршрутизиране на dotCloud работеше изцяло на EC2 Classic и не криптираше трафика (предполагаемо, че ако на някого е успяло да му сложи шпионин на мрежовия трафик EC2, вече имате сериозни проблеми). Съвременните сервис-решения прозрачно защитават целия наш трафик, например с взаимна TLS аутентификация и последващо криптиране.

Маршрутизиране на трафика за услуги на платформата

Добре, обсъдихме трафика между приложенията, но какво ще кажем за самата платформа dotCloud?

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

Много висококачествени услуги могат да използват мрежата за маршрутизация, описана по-горе. Всъщност, много от повече от сто микросервиза на dotCloud бяха разположени като обикновени приложения на самата платформа dotCloud. Но малко количество нискоуровневни услуги (по-специално, които реализират тази мрежа за маршрутизация) се нуждаеха от нещо по-просто, с по-малко зависимости (тъй като не можеха да зависят от самите себе си — старата добра зависимост на кокошката и яйцето).

Тези нискоуровневни, важни услуги бяха разположени чрез стартиране на контейнери директно на няколко ключови възли. При това не бяха използвани стандартните услуги на платформата: събирач, планировчик и runner. Ако искате да сравните с модерните контейнерни платформи, това е нещо като стартиране на контролен панел с docker run директно на възлите, вместо да се делегират задачите на Kubernetes. Това е доста сходно с концепцията за статични модули (пода), които използва kubeadm или bootkube при зареждане на автономен кластер.

Тези услуги бяха експонирани по прост и груб начин: в YAML файла бяха изброени имената и адресите им; а всеки клиент трябваше да вземе копие на този YAML файл за разполагане.

От една страна, това е изключително надеждно, защото не изисква поддръжка на външно хранилище от ключ-стойност, като Zookeeper (не забравяйте, по това време още не съществуваше etcd или Consul). От друга страна, това затрудняваше преместването на услугите. Всеки път при преместване всички клиенти трябваше да получат актуализиран YAML файл (и потенциално да се презаредят). Не е много удобно!

Впоследствие започнахме да въвеждаме нова схема, при която всеки клиент се свързваше с локален прокси-сървър. Вместо адреса и порта, му беше достатъчно да знае само номера на порта на услугата и да се свърже чрез localhost. Локалният прокси-сървър обработва това свързване и го насочва към реалния сървър. Сега при преместване на бекенда на друга машина или мащабиране, вместо да актуализираме всички клиенти, трябва да актуализираме само всички тези локални проксита; и презареждането вече не е необходимо.

(Също така се планираше да се инкапсулира трафикът в TLS връзки и да се постави още един прокси сървър на приемащата страна, както и да се проверяват TLS сертификати без участието на приемащата услуга, която е настроена да приема връзки само на localhost. За това по-късно).

Това много прилича на SmartStack от Airbnb, но съществената разлика е, че SmartStack е реализиран и разположен в продукция, докато вътрешната система за маршрутизиране на dotCloud беше прибрана в чекмеджето, когато dotCloud се превърна в Docker.

Лично аз считам SmartStack за един от предшествениците на такива системи като Istio, Linkerd и Consul Connect, защото всички те следват един шаблон:

  • Стартиране на прокси на всеки възел.
  • Клиентите се свързват с проксито.
  • Управляващата плоскост обновява конфигурацията на прокси сървъра при промяна на бекендите.
  • … Профит!

Съвременна реализация на сервис мрежа

Ако трябва да реализираме подобна мрежа днес, можем да използваме аналогични принципи. Например, да настроим вътрешна DNS зона, срастваща имена на услуги с адреси в пространство 127.0.0.0/8. След това да стартираме HAProxy на всеки възел в клъстера, приемаща връзки на всеки адрес на услуга (в тази подсетка 127.0.0.0/8) и пренасочвайки/балансирайки натоварването на съответните бекенди. Конфигурацията на HAProxy може да се управлява confd, позволявайки да се съхранява информация за бекенда в etcd или Consul и автоматично да се пуска обновената конфигурация на HAProxy, когато е необходимо.

Така работи Istio! Но с някои разлики:

  • Използва Envoy Proxy вместо HAProxy.
  • Запазва конфигурацията на бекенда през Kubernetes API вместо etcd или Consul.
  • На услугите се разпределят адреси във вътрешната подсетка (адреси Kubernetes ClusterIP) вместо 127.0.0.0/8.
  • Има допълнителен компонент (Citadel) за добавяне на взаимна проверка на идентичност TLS между клиента и сървърите.
  • Поддържа нови функции, като счупване на веригата (circuit breaking), разпределено трасиране, разгръщане на канарки и др.

Нека накратко разгледаме някои разлики.

Envoy Proxy

Envoy Proxy беше написан от компания Lyft [конкурент на Uber на пазара на таксита - бел. ред.]. Той е по същество подобен на други проксита (например, HAProxy, Nginx, Traefik…), но Lyft създадоха своя, защото им бяха нужни функции, които отсъстват в другите проксита, и по-разумно изглеждало да направят нов, отколкото да разширяват съществуващия.

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

Но Envoy също може да работи като данъчна площ (data plane) за service mesh. Това означава, че сега за този service mesh Envoy се настройва управляваща площ (control plane).

Управляваща площ

В управляващата площ Istio разчита на Kubernetes API. Това не се различава особено от използването на confd, който разчита на etcd или Consul за преглед на набор от ключове в хранилище от данни. Istio преглежда набор от ресурси Kubernetes чрез Kubernetes API.

Междувременно: на мен ми се стори полезно това описание на Kubernetes API, което гласи:

Сървърът на Kubernetes API е "глупав сървър", който предлага съхранение, управление на версии, проверка, актуализация и семантика на ресурсите на API.

Istio е проектирано да работи с Kubernetes; и ако искате да го използвате извън Kubernetes, трябва да стартирате инстанция на сървър Kubernetes API (и помощна услуга etcd).

Адреси на услуги

Istio разчита на адреси ClusterIP, които предоставя Kubernetes, така че услугите на Istio получават вътрешен адрес (не в диапазона 127.0.0.0/8).

Трафикът към адреса ClusterIP за конкретна услуга в клъстера Kubernetes без Istio се прихваща от kube-proxy и се изпраща на сървърната част на този прокси. Ако ви интересуват техническите детайли, kube-proxy установява правила iptables (или IPVS балансировачи на натоварването, в зависимост от начина, по който е конфигуриран), за да препише IP адресите на дестинацията на съединенията, които минават по адрес ClusterIP.

След инсталирането на Istio в клъстера Kubernetes нищо не се променя, докато не бъде ясно включено за конкретен потребител или дори цялото пространство за именуване, като се въведе контейнерът sidecar в персонализираните подове. Този контейнер ще стартира инстанция на Envoy и ще установи набор от правила iptables за прихващане на трафика, идващ към други услуги, и пренасочване на този трафик към Envoy.

При интеграция с Kubernetes DNS това означава, че нашият код може да се свързва по име на услуга, а всичко "просто работи". С други думи, нашият код прави запитвания от типа http://api/v1/users/4242, тогава api резолвира запитването на 10.97.105.48, правилата на iptables прихващат свързванията с 10.97.105.48 и ги пренасочват към локален прокси Envoy, а този локален прокси изпраща запитването към действителния бекенд API. Ха!

Допълнителни функции

Istio също така осигурява крайно шифроване и аутентификация чрез mTLS (взаимно TLS). За това отговаря компонент, наречен Citadel.

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

И, разбира се, споменахме наблюдаемостта: Envoy събира огромно количество метрики, осигурявайки разпределено проследяване. В архитектурата на микросервизите, ако едно API запитване трябва да премине през микросервизите A, B, C и D, то при влизане в системата разпределеното проследяване добавя уникален идентификатор към запитването и запазва този идентификатор през подзапитванията към всичките тези микросервизи, позволявайки фиксиране на всички свързани извиквания, тяхната латентност и т.н.

Разработвам или купувам

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

Ако имаме скромни нужди (не се нуждаем от наблюдаемост, прекъсвач на верига и други нюанси), идват мисли за разработка на собствен инструмент. Но ако използваме Kubernetes, той може дори да не е необходим, защото Kubernetes вече предлага основни инструменти за откриване на услуги и балансиране на натоварването.

Но ако имаме напреднали изисквания, тогава "купуването" на сервис мрежа изглежда много по-добра опция. (Не винаги става дума за "покупка", тъй като Istio се предлага с отворен код, но все пак трябва да инвестираме инженерно време, за да разберем как работи, да го разгърнем и управляваме).

Какво да изберем: Istio, Linkerd или Consul Connect?

Досега говорихме само за Istio, но това не е единствената услуга за свързване. Популярна алтернатива е Linkerd, а има и Consul Connect.

Какво да изберем?

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

Един от обещаващите подходи е да се използва инструмент като SuperGloo. Той реализира слой на абстракция, за да опрости и унифицира API-тата, предоставяни от сервис-мешовете. Вместо да изследваме специфичните (и, според мен, относително сложни) API на различните сервис-мешове, можем да използваме по-прости конструкции на SuperGloo и лесно да преминаваме от един на друг, сякаш имаме междинен формат на конфигурация, който описва HTTP интерфейси и бекенди, способен да генерира действителна конфигурация за Nginx, HAProxy, Traefik, Apache…

Поигравал съм малко с Istio и SuperGloo, а в следващата статия искам да покажа как да добавя Istio или Linkerd в съществуващ кластер с помощта на SuperGloo и колко добре последният се справя със задачата си, т.е. позволява да превключваме от един сервис-меш на друг без презаписване на конфигурации.

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

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