Прим. перев.: Service mesh'и определённо стали актуальным решением в современной инфраструктуре для приложений, следующих микросервисной архитектуре. Хотя Istio может быть на слуху у многих DevOps-инженеров, это довольно новый продукт, который, будучи комплексным в смысле предоставляемых возможностей, может потребовать значительного времени для знакомства. Немецкий инженер Rinor Maloku, отвечающий за облачные вычисления для крупных клиентов в телекоммуникационной компании Orange Networks, написал замечательный цикл материалов, что позволяют достаточно быстро и глубоко погрузиться в Istio. Начинает же он свой рассказ с того, что вообще умеет Istio и как на это можно быстро посмотреть собственными глазами.
Istio — Open Source-проект, разработанный при сотрудничестве команд из Google, IBM и Lyft. Он решает сложности, возникающие в приложениях, основанных на микросервисах, например, такие как:
- Управление трафиком: таймауты, повторные попытки, балансировка нагрузки;
- Безопасность: аутентификация и авторизация конечного пользователя;
- Наблюдаемость: трассировка, мониторинг, логирование.
Все они могут быть решены на уровне приложения, однако после этого ваши сервисы перестанут быть «микро». Все дополнительные усилия по решению этих проблем — лишний расход ресурсов компании, которые могли бы использоваться непосредственно для бизнес-ценностей. Рассмотрим пример:
Менеджер проектов: Как долго добавлять возможность обратной связи?
Разработчик: Два спринта.МП: Что?.. Это ведь всего лишь CRUD!
Р: Сделать CRUD — простая часть задачи, но нам ещё потребуется аутентифицировать и авторизовывать пользователей и сервисы. Поскольку сеть ненадёжна, понадобится реализовать повторные запросы, а также паттерн circuit breaker в клиентах. Ещё, чтобы убедиться, что вся система не упала, понадобятся таймауты и bulkheads (подробнее об обоих упомянутых паттернах см. дальше в статье — прим. перев.), а для того, чтобы обнаруживать проблемы, потребуется мониторинг, трассировка, […]МП: Ох, давайте тогда просто вставим эту фичу в сервис Product.
Думаю, идея понятна: объём шагов и усилий, которые требуются для добавления одного сервиса, огромен. В этой статье мы рассмотрим, как Istio устраняет все упомянутые выше сложности (не являющиеся целевыми для бизнес-логики) из сервисов.
Примечание: Статья предполагает, что у вас есть практические знания по Kubernetes. В ином случае рекомендую прочитать моё введение в Kubernetes и только после этого продолжить чтение данного материала.
Идея Istio
В мире без Istio один сервис делает прямые запросы к другому, а в случае сбоя сервис должен сам обработать его: предпринять новую попытку, предусмотреть таймаут, открыть circuit breaker и т.п.
Сетевой трафик в Kubernetes
Istio же предлагает специализированное решение, полностью отделённое от сервисов и функционирующее путём вмешательства в сетевое взаимодействие. И таким образом оно реализует:
- Отказоустойчивость: опираясь на код статуса в ответе, оно понимает, произошёл ли сбой в запросе, и выполняет его повторно.
- Канареечные выкаты: перенаправляет на новую версию сервиса лишь фиксированное процентом число запросов.
- Мониторинг и метрики: за какое время сервис ответил?
- Трассировка и наблюдаемость: добавляет специальные заголовки в каждый запрос и выполняет их трассировку в кластере.
- Безопасность: извлекает JWT-токен, аутентифицирует и авторизует пользователей.
Это лишь некоторые из возможностей (действительно лишь некоторые!), чтобы заинтриговать вас. А теперь давайте погрузимся в технические подробности!
Архитектура Istio
Istio перехватывает весь сетевой трафик и применяет к нему набор правил, вставляя в каждый pod умный прокси в виде sidecar-контейнера. Прокси, которые активируют все возможности, образуют собой Data Plane, и они могут динамически настраиваться с помощью Control Plane.
Data Plane
Вставляемые в pod'ы прокси позволяют Istio с лёгкостью добиться соответствия нужным нам требованиям. Например, проверим функции повторных попыток и circuit breaker.
Как retries и circuit breaking реализованы в Envoy
Подытожим:
- Envoy (речь про прокси, находящийся в sidecar-контейнере, который распространяется и как отдельный продукт — прим. перев.) отправляет запрос первому экземпляру сервиса B и происходит сбой.
- Envoy Sidecar предпринимает повторную попытку (retry). (1)
- Запрос со сбоем возвращается вызвавшему его прокси.
- Так открывается Circuit Breaker и происходит вызов следующего сервиса для последующих запросов. (2)
Это означает, что вам не придётся использовать очередную библиотеку Retry, не придётся делать свою реализацию Circuit Breaking и Service Discovery на языке программирования X, Y или Z. Всё это и многое другое доступно из коробки в Istio и не требует никаких изменений в коде.
Отлично! Теперь вы можете захотеть отправиться в вояж с Istio, но всё ещё есть какие-то сомнения, открытые вопросы. Если это универсальное решение на все случаи в жизни, то у вас возникает закономерное подозрение: ведь все такие решения в действительности оказываются не подходящими ни для какого случая.
И вот наконец вы спросите: «Оно настраивается?»
Теперь вы готовы к морскому путешествию — и давайте же познакомимся с Control Plane.
Control Plane
Он состоит из трёх компонентов: Pilot, Mixer и Citadel, — которые совместными усилиями настраивают Envoy'и для маршрутизации трафика, применяют политики и собирают телеметрические данные. Схематично всё это выглядит так:
Взаимодействие Control Plane с Data Plane
Envoy'и (т.е. data plane) сконфигурированы с помощью Kubernetes CRD (Custom Resource Definitions), определёнными Istio и специально предназначенными для этой цели. Для вас это означает, что они представляются очередным ресурсом в Kubernetes со знакомым синтаксисом. После создания этот ресурс будет подобран control plane'ом и применён к Envoy'ям.
Отношение сервисов к Istio
Мы описали отношение Istio к сервисам, но не обратное: как же сервисы относятся к Istio?
Честно говоря, о присутствии Istio сервисам известно так же хорошо, как рыбам — о воде, когда они спрашивают себя: «Что вообще такое вода?».
Иллюстрация Victoria Dimitrakopoulos: — Как вам вода? — Что вообще такое вода?
Таким образом, вы можете взять рабочий кластер и после деплоя компонентов Istio сервисы, находящиеся в нём, продолжат работать, а после устранения этих компонентов — снова всё будет хорошо. Понятное дело, что при этом вы потеряете возможности, предоставляемые Istio.
Достаточно теории — давайте перенесём это знание в практику!
Istio на практике
Istio требует кластера Kubernetes, в котором как минимум доступны 4 vCPU и 8 Гб RAM. Чтобы быстро поднять кластер и следовать инструкциям из статьи, рекомендую воспользоваться Google Cloud Platform, которая предлагает новым пользователям бесплатные $300.
После создания кластера и настройки доступа к Kubernetes через консольную утилиту можно установить Istio через пакетный менеджер Helm.
Установка Helm
Установите клиент Helm на своём компьютере, как рассказывают в официальной документации. Его мы будем использовать для генерации шаблонов для установки Istio в следующем разделе.
Установка Istio
Скачайте ресурсы Istio из последнего релиза (оригинальная авторская ссылка на версию 1.0.5 изменена на актуальную, т.е. 1.0.6 — прим. перев.), извлеките содержимое в одну директорию, которую я буду в дальнейшем называть [istio-resources]
.
Для простоты идентификации ресурсов Istio создайте в кластере K8s пространство имён istio-system
:
$ kubectl create namespace istio-system
Завершите установку, перейдя в каталог [istio-resources]
и выполнив команду:
$ helm template install/kubernetes/helm/istio
--set global.mtls.enabled=false
--set tracing.enabled=true
--set kiali.enabled=true
--set grafana.enabled=true
--namespace istio-system > istio.yaml
Эта команда выведет ключевые компоненты Istio в файл istio.yaml
. Мы изменили стандартный шаблон под себя, указав следующие параметры:
-
global.mtls.enabled
установлено вfalse
(т.е. mTLS-аутентификация отключена — прим перев.), чтобы упростить наш процесс знакомства; -
tracing.enabled
включает трассировку запросов с помощью Jaeger; -
kiali.enabled
устанавливает Kiali в кластер для визуализации сервисов и трафика; -
grafana.enabled
устанавливает Grafana для визуализации собранных метрик.
Применим сгенерированные ресурсы командой:
$ kubectl apply -f istio.yaml
Установка Istio в кластер завершена! Дождитесь, пока все pod'ы в пространстве имён istio-system
окажутся в состоянии Running
или Completed
, выполнив команду ниже:
$ kubectl get pods -n istio-system
Теперь мы готовы продолжить в следующем разделе, где поднимем и запустим приложение.
Архитектура приложения Sentiment Analysis
Воспользуемся примером микросервисного приложения Sentiment Analysis, использованного в уже упомянутой статье-введении в Kubernetes. Оно достаточно сложное, чтобы показать возможности Istio на практике.
Приложение состоит из четырёх микросервисов:
- Сервис SA-Frontend, который обслуживает фронтенд приложения на Reactjs;
- Сервис SA-WebApp, который обслуживает запросы Sentiment Analysis;
- Сервис SA-Logic, который выполняет сам сентимент-анализ;
- Сервис SA-Feedback, который получает от пользователей обратную связь о точности проведённого анализа.
На этой схеме помимо сервисов мы видим также Ingress Controller, который в Kubernetes маршрутизирует входящие запросы на соответствующие сервисы. В Istio используется схожая концепция в рамках Ingress Gateway, подробности о котором последуют.
Запуск приложения с прокси от Istio
Для дальнейших операций, упоминаемых в статье, склонируйте себе репозиторий istio-mastery. В нём содержатся приложение и манифесты для Kubernetes и Istio.
Вставка sidecar'ов
Вставка может быть произведена автоматически или вручную. Для автоматической вставки sidecar-контейнеров потребуется выставить пространству имён лейбл istio-injection=enabled
, что делается следующей командой:
$ kubectl label namespace default istio-injection=enabled
namespace/default labeled
Теперь каждый pod, который будет разворачиваться в пространстве имён по умолчанию (default
) получит свой sidecar-контейнер. Чтобы убедиться в этом, давайте задеплоим тестовое приложение, перейдя в корневой каталог репозитория [istio-mastery]
и выполнив следующую команду:
$ kubectl apply -f resource-manifests/kube
persistentvolumeclaim/sqlite-pvc created
deployment.extensions/sa-feedback created
service/sa-feedback created
deployment.extensions/sa-frontend created
service/sa-frontend created
deployment.extensions/sa-logic created
service/sa-logic created
deployment.extensions/sa-web-app created
service/sa-web-app created
Развернув сервисы, проверим, что у pod'ов по два контейнера (с самим сервисом и его sidecar'ом), выполнив команду kubectl get pods
и убедившись, что под столбцом READY
указано значение 2/2
, символизирующее, что оба контейнера запущены:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sa-feedback-55f5dc4d9c-c9wfv 2/2 Running 0 12m
sa-frontend-558f8986-hhkj9 2/2 Running 0 12m
sa-logic-568498cb4d-2sjwj 2/2 Running 0 12m
sa-logic-568498cb4d-p4f8c 2/2 Running 0 12m
sa-web-app-599cf47c7c-s7cvd 2/2 Running 0 12m
Визуально это представляется так:
Прокси Envoy в одном из pod'ов
Теперь, когда приложение поднято и функционирует, нам потребуется разрешить входящему трафику приходить в приложение.
Ingress Gateway
Лучшая практика добиться этого (разрешить трафик в кластере) — через Ingress Gateway в Istio, что располагается у «границы» кластера и позволяет включать для входящего трафика такие функции Istio, как маршрутизация, балансировка нагрузки, безопасность и мониторинг.
Компонент Ingress Gateway и сервис, который пробрасывает его вовне, были установлены в кластер во время инсталляции Istio. Чтобы узнать внешний IP-адрес сервиса, выполните:
$ kubectl get svc -n istio-system -l istio=ingressgateway
NAME TYPE CLUSTER-IP EXTERNAL-IP
istio-ingressgateway LoadBalancer 10.0.132.127 13.93.30.120
Мы будем обращаться к приложению по этому IP и дальше (я буду ссылаться на него как EXTERNAL-IP), поэтому для удобства запишем значение в переменную:
$ EXTERNAL_IP=$(kubectl get svc -n istio-system
-l app=istio-ingressgateway
-o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')
Если попробуете сейчас зайти на этот IP через браузер, то получите ошибку Service Unavailable, т.к. по умолчанию Istio блокирует весь входящий трафик, пока не определён Gateway.
Ресурс Gateway
Gateway — это CRD (Custom Resource Definition) в Kubernetes, определяемый после установки Istio в кластере и активирующий возможность указывать порты, протокол и хосты, для которых мы хотим разрешить входящий трафик.
В нашем случае мы хотим разрешить HTTP-трафик на 80-й порт для всех хостов. Задача реализуется следующим определением (http-gateway.yaml):
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: http-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
Такая конфигурация не нуждается в объяснениях за исключением селектора istio: ingressgateway
. С помощью этого селектора мы можем указать, к какому Ingress Gateway применить конфигурацию. В нашем случае таковым является контроллер Ingress Gateway, что был по умолчанию установлен в Istio.
Конфигурация применяется вызовом следующей команды:
$ kubectl apply -f resource-manifests/istio/http-gateway.yaml gateway.networking.istio.io/http-gateway created
Теперь шлюз разрешает доступ к порту 80, но не имеет представления о том, куда маршрутизировать запросы. Для этого понадобятся Virtual Services.
Ресурс VirtualService
VirtualService указывает Ingress Gateway'ю, как маршрутизировать запросы, которые разрешены внутри кластера.
Запросы к нашему приложению, приходящие через http-gateway, должны быть отправлены в сервисы sa-frontend, sa-web-app и sa-feedback:
Маршруты, которые необходимо настроить с VirtualServices
Рассмотрим запросы, которые должны направляться на SA-Frontend:
- Точное совпадение по пути
/
должно отправляться в SA-Frontend для получения index.html; - Пути с префиксом
/static/*
должны отправляться в SA-Frontend для получения статических файлов, используемых во фронтенде, таких как CSS и JavaScript; - Пути, попадающие под регулярное выражение
'^.*.(ico|png|jpg)$'
, должны отправляться в SA-Frontend, т.к. это картинки, отображаемые на странице.
Реализация достигается следующей конфигурацией (sa-virtualservice-external.yaml):
kind: VirtualService
metadata:
name: sa-external-services
spec:
hosts:
- "*"
gateways:
- http-gateway # 1
http:
- match:
- uri:
exact: /
- uri:
exact: /callback
- uri:
prefix: /static
- uri:
regex: '^.*.(ico|png|jpg)$'
route:
- destination:
host: sa-frontend # 2
port:
number: 80
Важные моменты:
- Этот VirtualService относится к запросам, приходящим через http-gateway;
- В
destination
определяется сервис, куда отправляются запросы.
Примечание: Конфигурация выше хранится в файле sa-virtualservice-external.yaml
, который также содержит настройки для маршрутизации в SA-WebApp и SA-Feedback, но был сокращён здесь в статье для лаконичности.
Применим VirtualService вызовом:
$ kubectl apply -f resource-manifests/istio/sa-virtualservice-external.yaml
virtualservice.networking.istio.io/sa-external-services created
Примечание: Когда мы применяем ресурсы Istio, Kubernetes API Server создаёт событие, которое получает Istio Control Plane, и уже после этого новая конфигурация применяется к прокси-серверам Envoy каждого pod'а. А контроллер Ingress Gateway представляется очередным Envoy, сконфигурированным в Control Plane. Всё это на схеме выглядит так:
Конфигурация Istio-IngressGateway для маршрутизации запросов
Приложение Sentiment Analysis стало доступным по http://{EXTERNAL-IP}/
. Не переживайте, если вы получаете статус Not Found: иногда требуется чуть больше времени для того, чтобы конфигурация вступила в силу и кэши Envoy обновились.
Перед тем, как продолжить, поработайте немного с приложением, чтобы сгенерировать трафик (его наличие необходимо для наглядности в последующих действиях — прим. перев.).
Kiali : наблюдаемость
Чтобы попасть в административный интерфейс Kiali, выполните следующую команду:
$ kubectl port-forward
$(kubectl get pod -n istio-system -l app=kiali
-o jsonpath='{.items[0].metadata.name}')
-n istio-system 20001
… и откройте http://localhost:20001/, залогинившись под admin/admin. Здесь вы найдете множество полезных возможностей, например, для проверки конфигурации компонентов Istio, визуализации сервисов по информации, собранной при перехвате сетевых запросов, получения ответов на вопросы «Кто к кому обращается?», «У какой версии сервиса возникают сбои?» и т.п. В общем, изучите возможности Kiali перед тем, как двигаться дальше — к визуализации метрик с Grafana.
Grafana: визуализация метрик
Собранные в Istio метрики попадают в Prometheus и визуализируются с Grafana. Чтобы попасть в административный интерфейс Grafana, выполните команду ниже, после чего откройте http://localhost:3000/:
$ kubectl -n istio-system port-forward
$(kubectl -n istio-system get pod -l app=grafana
-o jsonpath={.items[0].metadata.name}) 3000
Кликнув на меню Home слева сверху и выбрав Istio Service Dashboard в левом верхнем углу, начните с сервиса sa-web-app, чтобы посмотреть на собранные метрики:
Здесь нас ждёт пустое и совершенно скучное представление — руководство никогда такое не одобрит. Давайте же создадим небольшую нагрузку следующей командой:
$ while true; do
curl -i http://$EXTERNAL_IP/sentiment
-H "Content-type: application/json"
-d '{"sentence": "I love yogobella"}';
sleep .8; done
Вот теперь у нас гораздо более симпатичные графики, а в дополнение к ним — замечательные инструменты Prometheus для мониторинга и Grafana для визуализации метрик, что позволят нам узнать о производительности, состоянии здоровья, улучшениях/деградации в работе сервисов на протяжении времени.
Наконец, посмотрим на трассировку запросов в сервисах.
Jaeger : трассировка
Трассировка нам потребуется, потому что чем больше у нас сервисов, тем сложнее добраться до причины сбоя. Посмотрим на простой случай из картинки ниже:
Типовой пример случайного неудачного запроса
Запрос приходит, падает — в чём же причина? Первый сервис? Или второй? Исключения есть в обоих — давайте посмотрим на логи каждого. Как часто вы ловили себя за таким занятием? Наша работа больше похожа на детективов программного обеспечения, а не разработчиков…
Это широко распространённая проблема в микросервисах и решается она распределёнными системами трассировки, в которых сервисы передают друг другу уникальный заголовок, после чего эта информация перенаправляется в систему трассировки, где она сопоставляется с данными запроса. Вот иллюстрация:
Для идентификации запроса используется TraceId
В Istio используется Jaeger Tracer, который реализует независимый от вендоров фреймворк OpenTracing API. Получить доступ к пользовательского интерфейсу Jaeger можно следующей командой:
$ kubectl port-forward -n istio-system
$(kubectl get pod -n istio-system -l app=jaeger
-o jsonpath='{.items[0].metadata.name}') 16686
Теперь зайдите на http://localhost:16686/ и выберите сервис sa-web-app. Если сервис не показан в выпадающем меню — проявите/сгенерируйте активность на странице и обновите интерфейс. После этого нажмите на кнопку Find Traces, которая покажет самые последние трейсы — выберите любой — покажется детализированная информация по всем трейсам:
Этот трейс показывает:
- Запрос приходит в istio-ingressgateway (это первое взаимодействие с одним из сервисов, и для запроса генерируется Trace ID), после чего шлюз направляет запрос в сервис sa-web-app.
- В сервисе sa-web-app запрос подхватывается Envoy sidecar'ом, создаётся «ребёнок» в span'е (поэтому мы видим его в трейсах) и перенаправляется в контейнер sa-web-app. (Span — логическая единица работы в Jaeger, имеющая название, время начало операции и её продолжительность. Span'ы могут быть вложенными и упорядоченными. Ориентированный ациклический граф из span'ов образует trace. — прим. перев.)
- Здесь запрос обрабатывается методом sentimentAnalysis. Эти трейсы уже сгенерированы приложением, т.е. для них потребовались изменения в коде.
- С этого момента инициируется POST-запрос в sa-logic. Trace ID должен быть проброшен из sa-web-app.
- …
Примечание: На 4 шаге приложение должно увидеть заголовки, сгенерированные Istio, и передать их в последующие запросы, как показано на изображении ниже:
(A) За проброс заголовков отвечает Istio; (B) За заголовки отвечают сервисы
Istio делает основную работу, т.к. генерирует заголовки для входящих запросов, создаёт новые span'ы в каждом sidecare'е и пробрасывает их. Однако без работы с заголовками внутри сервисов полный путь трассировки запроса будет утерян.
Необходимо учитывать (пробрасывать) следующие заголовки:
x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
x-ot-span-context
Это несложная задача, однако для упрощения её реализации уже существует множество библиотек — например, в сервисе sa-web-app клиент RestTemplate пробрасывает эти заголовки, если просто добавить библиотеки Jaeger и OpenTracing в его зависимости.
Заметьте, что приложение Sentiment Analysis демонстрирует реализации на Flask, Spring и ASP.NET Core.
Теперь, когда стало ясно, что мы получаем из коробки (или почти «из коробки»), рассмотрим вопросы тонко настраиваемой маршрутизации, управления сетевым трафиком, безопасности и т.п.!
Прим. перев.: об этом читайте в следующей части материалов по Istio от Rinor Maloku, переводы которых последуют в нашем блоге в ближайшее время.
P.S. от переводчика
Читайте также в нашем блоге:
- «Conduit — легковесный service mesh для Kubernetes»;
- «Что такое service mesh и почему он мне нужен [для облачного приложения с микросервисами]?»;
- «Иллюстрированное руководство по устройству сети в Kubernetes. Части 1 и 2»;
- «Как этот sidecar-контейнер оказался здесь [в Kubernetes]?».
Автор: Андрей Сидоров