[Перевод] Назад к микросервисам вместе с Istio. Часть 1

bjj4oyjxqshrbjf5eks9sgsvbeg.png

Прим. перев.: Service mesh’и определённо стали актуальным решением в современной инфраструктуре для приложений, следующих микросервисной архитектуре. Хотя Istio может быть на слуху у многих DevOps-инженеров, это довольно новый продукт, который, будучи комплексным в смысле предоставляемых возможностей, может потребовать значительного времени для знакомства. Немецкий инженер Rinor Maloku, отвечающий за облачные вычисления для крупных клиентов в телекоммуникационной компании Orange Networks, написал замечательный цикл материалов, что позволяют достаточно быстро и глубоко погрузиться в Istio. Начинает же он свой рассказ с того, что вообще умеет Istio и как на это можно быстро посмотреть собственными глазами.

Istio — Open Source-проект, разработанный при сотрудничестве команд из Google, IBM и Lyft. Он решает сложности, возникающие в приложениях, основанных на микросервисах, например, такие как:

  • Управление трафиком: таймауты, повторные попытки, балансировка нагрузки;
  • Безопасность: аутентификация и авторизация конечного пользователя;
  • Наблюдаемость: трассировка, мониторинг, логирование.


Все они могут быть решены на уровне приложения, однако после этого ваши сервисы перестанут быть «микро». Все дополнительные усилия по решению этих проблем — лишний расход ресурсов компании, которые могли бы использоваться непосредственно для бизнес-ценностей. Рассмотрим пример:

Менеджер проектов: Как долго добавлять возможность обратной связи?
Разработчик: Два спринта.

МП: Что?… Это ведь всего лишь CRUD!
Р: Сделать CRUD — простая часть задачи, но нам ещё потребуется аутентифицировать и авторизовывать пользователей и сервисы. Поскольку сеть ненадёжна, понадобится реализовать повторные запросы, а также паттерн circuit breaker в клиентах. Ещё, чтобы убедиться, что вся система не упала, понадобятся таймауты и bulkheads (подробнее об обоих упомянутых паттернах см. дальше в статье — прим. перев.), а для того, чтобы обнаруживать проблемы, потребуется мониторинг, трассировка, […]

МП: Ох, давайте тогда просто вставим эту фичу в сервис Product.

Думаю, идея понятна: объём шагов и усилий, которые требуются для добавления одного сервиса, огромен. В этой статье мы рассмотрим, как Istio устраняет все упомянутые выше сложности (не являющиеся целевыми для бизнес-логики) из сервисов.

l4js7omne2rslxitn0zr_lgusee.png

Примечание: Статья предполагает, что у вас есть практические знания по Kubernetes. В ином случае рекомендую прочитать моё введение в Kubernetes и только после этого продолжить чтение данного материала.

Идея Istio


В мире без Istio один сервис делает прямые запросы к другому, а в случае сбоя сервис должен сам обработать его: предпринять новую попытку, предусмотреть таймаут, открыть circuit breaker и т.п.

ovuvg5sepxqvj7wduhnl3uiixyi.png
Сетевой трафик в Kubernetes

Istio же предлагает специализированное решение, полностью отделённое от сервисов и функционирующее путём вмешательства в сетевое взаимодействие. И таким образом оно реализует:

  • Отказоустойчивость: опираясь на код статуса в ответе, оно понимает, произошёл ли сбой в запросе, и выполняет его повторно.
  • Канареечные выкаты: перенаправляет на новую версию сервиса лишь фиксированное процентом число запросов.
  • Мониторинг и метрики: за какое время сервис ответил?
  • Трассировка и наблюдаемость: добавляет специальные заголовки в каждый запрос и выполняет их трассировку в кластере.
  • Безопасность: извлекает JWT-токен, аутентифицирует и авторизует пользователей.


Это лишь некоторые из возможностей (действительно лишь некоторые!), чтобы заинтриговать вас. А теперь давайте погрузимся в технические подробности!

Архитектура Istio


Istio перехватывает весь сетевой трафик и применяет к нему набор правил, вставляя в каждый pod умный прокси в виде sidecar-контейнера. Прокси, которые активируют все возможности, образуют собой Data Plane, и они могут динамически настраиваться с помощью Control Plane.

Data Plane


Вставляемые в pod’ы прокси позволяют Istio с лёгкостью добиться соответствия нужным нам требованиям. Например, проверим функции повторных попыток и circuit breaker.

8t7xxntkiuakkk5sbn41b4rualg.gif
Как retries и circuit breaking реализованы в Envoy

Подытожим:

  1. Envoy (речь про прокси, находящийся в sidecar-контейнере, который распространяется и как отдельный продукт — прим. перев.) отправляет запрос первому экземпляру сервиса B и происходит сбой.
  2. Envoy Sidecar предпринимает повторную попытку (retry). (1)
  3. Запрос со сбоем возвращается вызвавшему его прокси.
  4. Так открывается Circuit Breaker и происходит вызов следующего сервиса для последующих запросов. (2)


Это означает, что вам не придётся использовать очередную библиотеку Retry, не придётся делать свою реализацию Circuit Breaking и Service Discovery на языке программирования X, Y или Z. Всё это и многое другое доступно из коробки в Istio и не требует никаких изменений в коде.

Отлично! Теперь вы можете захотеть отправиться в вояж с Istio, но всё ещё есть какие-то сомнения, открытые вопросы. Если это универсальное решение на все случаи в жизни, то у вас возникает закономерное подозрение: ведь все такие решения в действительности оказываются не подходящими ни для какого случая.

И вот наконец вы спросите: «Оно настраивается?»

Теперь вы готовы к морскому путешествию — и давайте же познакомимся с Control Plane.

Control Plane


Он состоит из трёх компонентов: Pilot, Mixer и Citadel, — которые совместными усилиями настраивают Envoy’и для маршрутизации трафика, применяют политики и собирают телеметрические данные. Схематично всё это выглядит так:

dwp6hh6y5g41oinfxccixuutkyo.png
Взаимодействие Control Plane с Data Plane

Envoy’и (т.е. data plane) сконфигурированы с помощью Kubernetes CRD (Custom Resource Definitions), определёнными Istio и специально предназначенными для этой цели. Для вас это означает, что они представляются очередным ресурсом в Kubernetes со знакомым синтаксисом. После создания этот ресурс будет подобран control plane’ом и применён к Envoy’ям.

Отношение сервисов к Istio


Мы описали отношение Istio к сервисам, но не обратное: как же сервисы относятся к Istio?

Честно говоря, о присутствии Istio сервисам известно так же хорошо, как рыбам — о воде, когда они спрашивают себя: «Что вообще такое вода?».

reoiffespub6tknffwssilkgu7c.jpeg
Иллюстрация 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 на практике.

Приложение состоит из четырёх микросервисов:

  1. Сервис SA-Frontend, который обслуживает фронтенд приложения на Reactjs;
  2. Сервис SA-WebApp, который обслуживает запросы Sentiment Analysis;
  3. Сервис SA-Logic, который выполняет сам сентимент-анализ;
  4. Сервис SA-Feedback, который получает от пользователей обратную связь о точности проведённого анализа.


hijvoc4y7ercttsbopo4jzbo4w4.png

На этой схеме помимо сервисов мы видим также 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


Визуально это представляется так:

x9ktluxxjopen7rn9tchuyvvioi.png
Прокси 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:

ziil7_-9gfy5ejkhkzzn1wpxkxe.png
Маршруты, которые необходимо настроить с 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


Важные моменты:

  1. Этот VirtualService относится к запросам, приходящим через http-gateway;
  2. В 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. Всё это на схеме выглядит так:

dznz4bh_wpzcv7tx8jkykkiad9q.png
Конфигурация 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.

ixpdwrppiekv0dbk3xcbceasft8.png

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, чтобы посмотреть на собранные метрики:

wbvz4tkgi3qgoitxhhwhppk9pu8.png

Здесь нас ждёт пустое и совершенно скучное представление — руководство никогда такое не одобрит. Давайте же создадим небольшую нагрузку следующей командой:

$ 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 : трассировка


Трассировка нам потребуется, потому что чем больше у нас сервисов, тем сложнее добраться до причины сбоя. Посмотрим на простой случай из картинки ниже:

dv8wivmvfn20fvmaebzk8wl6h68.png
Типовой пример случайного неудачного запроса

Запрос приходит, падает — в чём же причина? Первый сервис? Или второй? Исключения есть в обоих — давайте посмотрим на логи каждого. Как часто вы ловили себя за таким занятием? Наша работа больше похожа на детективов программного обеспечения, а не разработчиков…

Это широко распространённая проблема в микросервисах и решается она распределёнными системами трассировки, в которых сервисы передают друг другу уникальный заголовок, после чего эта информация перенаправляется в систему трассировки, где она сопоставляется с данными запроса. Вот иллюстрация:

an_htzqnc3b4xxhgkzdqi5my-ki.png
Для идентификации запроса используется 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, которая покажет самые последние трейсы — выберите любой — покажется детализированная информация по всем трейсам:

r8zx1qjxc2a316-4a803l4vmzv4.png

Этот трейс показывает:

  1. Запрос приходит в istio-ingressgateway (это первое взаимодействие с одним из сервисов, и для запроса генерируется Trace ID), после чего шлюз направляет запрос в сервис sa-web-app.
  2. В сервисе sa-web-app запрос подхватывается Envoy sidecar’ом, создаётся «ребёнок» в span’е (поэтому мы видим его в трейсах) и перенаправляется в контейнер sa-web-app. (Span — логическая единица работы в Jaeger, имеющая название, время начало операции и её продолжительность. Span’ы могут быть вложенными и упорядоченными. Ориентированный ациклический граф из span’ов образует trace. — прим. перев.)
  3. Здесь запрос обрабатывается методом sentimentAnalysis. Эти трейсы уже сгенерированы приложением, т.е. для них потребовались изменения в коде.
  4. С этого момента инициируется POST-запрос в sa-logic. Trace ID должен быть проброшен из sa-web-app.


Примечание: На 4 шаге приложение должно увидеть заголовки, сгенерированные Istio, и передать их в последующие запросы, как показано на изображении ниже:

qga9aibwkynfogbcxfo2cnwqxfm.png
(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. от переводчика


Читайте также в нашем блоге:

© Habrahabr.ru