Играем в service mesh

Намедни я высматривал, с чем бы таким новым поиграться, и случайно наткнулся на штуку под названием service mesh. Непонятно, правда, как нормально перевести её на русский. Сервисная сеть? Сервисный меш? Служебный мышь? Но даже с учётом того, что концепция мешей вряд ли принесёт мир всему миру, задумка действительно интересная. Давайте смотреть.

Итак, что такое service mesh?

Чтобы среднестатистические микросервисное приложение могло работать, ему нужен способ найти свои сервисы и сеть, чтобы с ними общаться. И если поиск сервисов мы более или менее контролируем (через тот же Consul, например), сеть… она почему-то просто есть и работает. Если же такие приложение переселить в Docker или Kubernetes, то начинается полная магия. Кто-то, не мы, и поиск сервисов нам делает, и балансировку нагрузки между ними, и сеть устраивает, и мы в этот процесс как-то не вовлечены и ничего особо не контролируем.

Воображаемое распределённое приложение, в котором сервисы общаются по сети напрямую
Воображаемое распределённое приложение, в котором сервисы общаются по сети напрямую

Ну вот а если я хочу перенаправить трафик от сервиса А, например, к сервису Б, который почти как А, просто новее? А если я хочу ещё замерить задержки в сети? Её ошибки? Обнаружить сервисы, которые перегружают сеть? А потому задумаюсь, стоит ли переотправлять упавшие запросы? Это ведь сеть, запросы действительно иногда обламываются. А если да, то столько раз стоит переотправлять? Все эти вопросы случаются в реальной жизни, и «прелесть» неявной, неуправляемой сети в том, что на некоторые из них ответить так сразу и не получится. А другие и вовсе придётся внедрять руками в каждый из сервисов.

И вот тут подключается сервисная сеть. Она берёт общение между сервисами на себя (вроде как надсетевой слой), и так как теперь это управляемый компонент, то в нём можно и статистику посмотреть, и дискавери сервисов настроить, и на лету маршруты запросов поменять, балансировку нагрузки сделать, отследить упавшие сервисы, отключить абьюзеров от сети, — в общем, много, много чего теперь можно сделать из единого центра и даже через UI.

Как вообще это работает

Сначала я подумал, чтобы сделать такую штуку, ребята, видимо, переизобрели собственную сеть. Но реальность оказалась проще. Уже существующие меши вроде Linkerd  или Conduit работают как обычные прокси, которые находят нужные сервисы по HTTP заголовкам, вроде «Host: myservice». Для того, чтобы приложение начало отправлять запросы именно через прокси, его даже не придётся перекомпилировать или как-то перенастраивать. Есть переменная окружения http_proxy, которая, как оказалось, работает практически везде, и, завидев которую, весь HTTP трафик приложения пойдёт в нужную сторону.

Это всё ещё может звучать как-то непонятно и абстрактно, так что давайте посмотрим на живые service mesh, и тогда всё станет по крайней мере не абстрактным.

Запускаем service mesh прямо в OS

Для начала начнём с Linkerd — с мешем, который работает как в контейнерах, так и прямо в операционке. Linkerd очень любит Java8, чего не скажешь обо мне, так что вместо того, чтобы устанавливать детище Sun/Oracle на своей машине, я запущу всё это кощунство в Docker буду колдовать уже там.

Я не буду вдаваться в детали того, как Linkerd настраивается, большей частью потому, что сам не знаю. Но из того, что я всё-таки знаю — сервис дискавери у Linkerd работает через файлы, и поэтому чтобы зарегистрировать, например, сервис search, мне нужно создать файл с именем search в папке disco и положить туда IP адрес с портом в придачу:

172.217... — это гугл, между прочим. Теперь, чтобы отправить запрос сервису search, мне нужно отправить его Linkerd и через HTTP заголовки показать, что за сервис мне нужен:

Та-даам! Гугл, конечно, ответил с 404, но это видимо потому, что ему подсунули незнакомый Host заголовок с search  внутри — Linkerd же всё ему отдал. Кстати, из-за того, что Linkerd понимает семантику HTTP(S), он наверняка положил этот запрос в список неуспешных и всё из-за статус кода 404. Я даже думаю, что если бы были другие search сервисы и они отвечали нормально, то в какой-то момент Linkerd удалил бы этого неудачника из своей базы за перманентный пессимизм.

Воображаемое распределённое приложение общается через прокси
Воображаемое распределённое приложение общается через прокси

Запускаем сервис mesh в Kubernetes

Примеры для Kubernetes всегда интересней смотреть в первую очередь из-за природы контейнерных приложений. Всё-таки в одну команду можно запустить что-то очень огромное. Чтобы развернуть локальный Kubernetes кластер я буду использовать minikube. Как он устанавливается где-то уже писалось, так что будем считать, что локальный k8s уже есть и можно переходить сразу к десерту.

Linkerd на k8s работает в виде набора DaemonSet — то есть один pod на хост кластера. Как и многое в Kubernetes, его можно установить в одну команду и для этого нам понадобится kubectl:

service mesh на хост
Воображаемое распределённое приложение с прокси на каждом хосте

Смотрим админку и статистику запросов

При нехорошем интернете пройдёт несколько минут, прежде чем Linkerd запустится, но потом можно прямиком идти и таращится в красивые сетевые админские страницы:

linkerd админка

Сюда даже можно адаптированную Grafana постевить и наслаждаться её эстетической пустотой. Данных-то нет ещё.

grafana-viz

Добавляем демо-сервисы

Оказывается, в мире распределённых приложений есть  свой «hello-world». Даже в Kubernetes. Он выглядит как два сервиса hello и world. Когда в hello приходит запрос, то он самостоятельно делает запрос в world, и уже вместе они отвечают «hello world».

Чтобы они начали общаться через Linkerd всего-то нужно сделать одну вещь — положить адрес Linkerd в переменную окружения http_proxyhello-world.yml, который и есть hello-world для Kubernetes, делает именно это — задаёт http_proxy  для пода hello и прозаическое приложение внезапно становится proof of concept для сервис мешей:

Правда, в самом примере есть проблема. Точнее, в запуске его под minikube, Засада в том, что в http_proxy идёт имя хоста, которое в миникубе будет — сюрприз — minikube, и которое для находящихся в нём подов не имеет ровно никакого смысла. Вот если бы там была айпишка, было бы интереснее. И ведь можно её туда засунуть. Достаточно в hello-world.yml  заменить  spec.nodeName на  status.hostIP, и всё станет работать. Эти значения Kubernetes берёт из Downward API, и про него можно почитать отдельно.

Итак, пингуем hello-world через Linkerd:

И мало того, что есть ответ, так Linkerd ещё и заметил сетевую активность. Работает, чтоб его.

linkerd-admin-data

Альтернативный подход к mesh

Есть ещё один service mesh, который дизайнился специально под Kubernetes, и имя ему — Conduit. Кажется, он ещё до сих пор в альфе, поэтому совать его в продакшен так же рисково, как и руку в крокодилий заповедник. Но посмотреть на задумку интересно.

Так вот, вместо добавления DaemonSet на каждый кластерный хост, Conduit добавляет меш-контейнер к каждому поду. В каком-то смысле теперь мы явно подключаем сервисы приложения к сервис mesh. Красиво и по одному.

Воображаемое распределённое приложение подключённое к service mesh
Воображаемое распределённое приложение, подключённое к service mesh

А в остальном Conduit — mesh как mesh. Есть UI, есть командная строка для настроек и запроса базовой статистики и даже HTTP логов в реальном времени. Но так как это пост уже разросся до неимоверных размеров, я, думаю, посмотрю на него подробнее в следующий раз.

Заключение

Вот такие они, сервис меши. Моя картина мира, конечно, не пошатнулось из-за того, они в нём есть. Всё-таки многие куски их функциональности уже были сделаны в том или ином виде. nginx годами работал в качестве обратного прокси (+lua расширения для динамической маршрутизации), Consul прекрасно справлялся с обнаружением сервисов и проверкой их здоровья, и т.п. Но мне всё-таки нравится их идея: выделить что-то настолько привычное и неявное в отдельный, ограниченный и управляемый компонент.

Он, кстати, может даже пригодиться нам на работе. Подвернулась недавно задача, где сервисам нужно устроить динамическую маршрутизацию. Среди вариантов мы рассматривали и nginx+lua, и HA Proxy, или даже написать что-нибудь своё. А с service mesh появляется если не готовое решение, то точно ещё один вариант, и это здорово.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *