Что такое istio openshift
Ликбез по запуску Istio
Istio Service Mesh
Мы в Namely уже год как юзаем Istio. Он тогда только-только вышел. У нас здорово упала производительность в кластере Kubernetes, мы хотели распределенную трассировку и взяли Istio, чтобы запустить Jaeger и разобраться. Service mesh так здорово вписалась в нашу инфраструктуру, что мы решили вложиться в этот инструмент.
Пришлось помучиться, но мы изучили его вдоль и поперек. Это первый пост из серии, где я расскажу, как Istio интегрируется с Kubernetes и что мы узнали о его работе. Иногда будем забредать в технические дебри, но не сильно далеко. Дальше будут еще посты.
Что такое Istio?
Istio — это инструмент конфигурации service mesh. Он читает состояние кластера Kubernetes и делает обновление до прокси L7 (HTTP и gRPC), которые реализуются как sidecar-ы в подах Kubernetes. Эти sidecar-ы — контейнеры Envoy, которые читают конфигурацию из Istio Pilot API (и сервиса gRPC) и маршрутизируют по ней трафик. С мощным прокси L7 под капотом мы можем использовать метрики, трассировки, логику повтора, размыкатель цепи, балансировку нагрузки и канареечные деплои.
Начнем с начала: Kubernetes
В Kubernetes мы создаем под c помощью деплоя или StatefulSet. Или это может быть просто «ванильный» под без контроллера высокого уровня. Затем Kubernetes изо всех сил поддерживает желаемое состояние — создает поды в кластере на ноде, следит, чтобы они запускались и перезапускались. Когда под создается, Kubernetes проходит по жизненному циклу API, убеждается, что каждый шаг будет успешным, и только потом наконец создает под на кластере.
Этапы жизненного цикла API:
Спасибо Banzai Cloud за крутую картинку.
Один из этапов — модифицирующие вебхуки допуска. Это отдельная часть жизненного цикла в Kubernetes, где ресурсы кастомизируются до коммита в хранилище etcd — источнике истины для конфигурации Kubernetes. И здесь Istio творит свою магию.
Модифицирующие вебхуки допуска
Когда под создается (через kubectl или Deployment ), он проходит через этот жизненный цикл, и модифицирующие вебхуки доступа меняют его, прежде чем выпустить в большой мир.
Во время установки Istio добавляется istio-sidecar-injector как ресурс конфигурации модифицирующих вебхуков:
Sidecar-поды
Sidecar-ы — это трюки нашего фокусника Istio. Istio так ловко все проворачивает, что со стороны это прямо магия, если не знать деталей. А знать их полезно, если вдруг надо отладить сетевые запросы.
Init- и прокси-контейнеры
В Kubernetes есть временные одноразовые init-контейнеры, которые можно запускать до основных. Они объединяют ресурсы, переносят базы данных или, как в случае с Istio, настраивают правила сети.
@jimmysongio нарисовал отличную схему связи между правилами iptables и прокси Envoy:
Envoy получает весь входящий и весь исходящий трафик, поэтому весь трафик вообще перемещается внутри Envoy, как на схеме. Прокси Istio — это еще один контейнер, который добавляется во все поды, изменяемые sidecar-инжектором Istio. В этом контейнере запускается процесс Envoy, который получает весь трафик пода (за некоторым исключением, вроде трафика из вашего кластера Kubernetes).
Процесс Envoy обнаруживает все маршруты через Envoy v2 API, который реализует Istio.
Envoy и Pilot
У самого Envoy нет никакой логики, чтобы обнаруживать поды и сервисы в кластере. Это плоскость данных и ей нужна плоскость контроля, чтобы руководить. Параметр конфигурации Envoy запрашивает хост или порт сервиса, чтобы получить эту конфигурацию через gRPC API. Istio, через свой сервис Pilot, выполняет требования для gRPC API. Envoy подключается к этому API на основе sidecar-конфигурации, внедренной через модифицирующий вебхук. В API есть все правила трафика, которые нужны Envoy для обнаружения и маршрутизации для кластера. Это и есть service mesh.
Обмен данными «под Pilot»
Pilot подключается к кластеру Kubernetes, читает состояние кластера и ждет обновлений. Он следит за подами, сервисами и конечными точками в кластере Kubernetes, чтобы потом дать нужную конфигурацию всем sidecar-ам Envoy, подключенным к Pilot. Это мост между Kubernetes и Envoy.
Из Pilot в Kubernetes
Когда в Kubernetes создаются или обновляются поды, сервисы или конечные точки, Pilot узнает об этом и отправляет нужную конфигурацию всем подключенным экземплярам Envoy.
Какая конфигурация отправляется?
Какую конфигурацию получает Envoy от Istio Pilot?
По умолчанию Kubernetes решает ваши сетевые вопросы с помощью sevice (сервис), который управляет endpoint (конечные точки). Список конечных точек можно открыть командой:
Это список всех IP и портов в кластере и их адресатов (обычно это поды, созданные из деплоя). Istio важно это знать, чтобы настраивать и отправлять данные о маршрутах в Envoy.
Сервисы, прослушиватели и маршруты
Когда вы создаете сервис в кластере Kubernetes, вы включаете ярлыки, по которым будут выбраны все подходящие поды. Когда вы отправляете трафик на IP сервиса, Kubernetes выбирает под для этого трафика. Например, команда
Istio и Envoy слегка меняют эту логику. Istio настраивает Envoy на основе сервисов и конечных точек в кластере Kubernetes и использует умные функции маршрутизации и балансировки нагрузки Envoy, чтобы обойти сервис Kubernetes. Вместо проксирования по одному IP Envoy подключается прямо к IP пода. Для этого Istio сопоставляет конфигурацию Kubernetes с конфигурацией Envoy.
Термины Kubernetes, Istio и Envoy немного отличаются, и не сразу понятно, что с чем едят.
Сервисы
Все те же сервисы есть в этом пространстве имен:
Как Istio узнает, какой протокол использует сервис? Настраивает протоколы для манифестов сервисов по полю name в записи порта.
Если использовать kubectl и админскую страницу переадресации портов в Envoy, видно, что конечные точки account-grpc-public реализуются Pilot как кластер в Envoy с протоколом HTTP2. Это подтверждает наши предположения:
Порт 15000 — это админская страница Envoy, доступная в каждом sidecar.
Прослушиватели
Прослушиватели узнают конечные точки Kubernetes, чтобы пропускать трафик в поды. У сервиса проверки адреса здесь одна конечная точка:
Поэтому у пода проверки адреса один прослушиватель на порте 50051:
Маршруты
В Namely мы используем Istio Ingress-Gateway для всего внутреннего GRPC-трафика:
Если переадресовать порт пода Istio-IngressGateway и посмотреть конфигурацию Envoy, мы увидим, что делает VirtualService:
Что мы гуглили, копаясь в Istio
Возникает ошибка 503 или 404
Причины разные, но обычно такие:
Что означает NR/UH/UF в логах прокси Istio?
По поводу высокой доступности с Istio
Почему Cronjob не завершается?
Когда основная рабочая нагрузка выполнена, sidecar-контейнер продолжает работать. Чтобы обойти проблему, отключите sidecar в cronjobs, добавив аннотацию sidecar.istio.io/inject: “false” в PodSpec.
Как установить Istio?
Спасибо Bobby Tables и Michael Hamrah за помощь в написании поста.
Назад к микросервисам вместе с Istio. Часть 1
Прим. перев.: 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 же предлагает специализированное решение, полностью отделённое от сервисов и функционирующее путём вмешательства в сетевое взаимодействие. И таким образом оно реализует:
Архитектура Istio
Istio перехватывает весь сетевой трафик и применяет к нему набор правил, вставляя в каждый pod умный прокси в виде sidecar-контейнера. Прокси, которые активируют все возможности, образуют собой Data Plane, и они могут динамически настраиваться с помощью Control Plane.
Data Plane
Вставляемые в pod’ы прокси позволяют Istio с лёгкостью добиться соответствия нужным нам требованиям. Например, проверим функции повторных попыток и circuit breaker.
Как retries и circuit breaking реализованы в Envoy
Отлично! Теперь вы можете захотеть отправиться в вояж с 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 на практике
После создания кластера и настройки доступа к Kubernetes через консольную утилиту можно установить Istio через пакетный менеджер Helm.
Установка Helm
Установите клиент Helm на своём компьютере, как рассказывают в официальной документации. Его мы будем использовать для генерации шаблонов для установки Istio в следующем разделе.
Установка Istio
Для простоты идентификации ресурсов Istio создайте в кластере K8s пространство имён istio-system :
Завершите установку, перейдя в каталог [istio-resources] и выполнив команду:
Теперь мы готовы продолжить в следующем разделе, где поднимем и запустим приложение.
Архитектура приложения Sentiment Analysis
Воспользуемся примером микросервисного приложения Sentiment Analysis, использованного в уже упомянутой статье-введении в Kubernetes. Оно достаточно сложное, чтобы показать возможности Istio на практике.
Приложение состоит из четырёх микросервисов:
На этой схеме помимо сервисов мы видим также Ingress Controller, который в Kubernetes маршрутизирует входящие запросы на соответствующие сервисы. В Istio используется схожая концепция в рамках Ingress Gateway, подробности о котором последуют.
Запуск приложения с прокси от Istio
Для дальнейших операций, упоминаемых в статье, склонируйте себе репозиторий istio-mastery. В нём содержатся приложение и манифесты для Kubernetes и Istio.
Вставка sidecar’ов
Теперь каждый pod, который будет разворачиваться в пространстве имён по умолчанию ( default ) получит свой sidecar-контейнер. Чтобы убедиться в этом, давайте задеплоим тестовое приложение, перейдя в корневой каталог репозитория [istio-mastery] и выполнив следующую команду:
Визуально это представляется так:
Прокси Envoy в одном из pod’ов
Теперь, когда приложение поднято и функционирует, нам потребуется разрешить входящему трафику приходить в приложение.
Ingress Gateway
Лучшая практика добиться этого (разрешить трафик в кластере) — через Ingress Gateway в Istio, что располагается у «границы» кластера и позволяет включать для входящего трафика такие функции Istio, как маршрутизация, балансировка нагрузки, безопасность и мониторинг.
Компонент Ingress Gateway и сервис, который пробрасывает его вовне, были установлены в кластер во время инсталляции Istio. Чтобы узнать внешний IP-адрес сервиса, выполните:
Мы будем обращаться к приложению по этому IP и дальше (я буду ссылаться на него как EXTERNAL-IP), поэтому для удобства запишем значение в переменную:
Если попробуете сейчас зайти на этот IP через браузер, то получите ошибку Service Unavailable, т.к. по умолчанию Istio блокирует весь входящий трафик, пока не определён Gateway.
Ресурс Gateway
Gateway — это CRD (Custom Resource Definition) в Kubernetes, определяемый после установки Istio в кластере и активирующий возможность указывать порты, протокол и хосты, для которых мы хотим разрешить входящий трафик.
В нашем случае мы хотим разрешить HTTP-трафик на 80-й порт для всех хостов. Задача реализуется следующим определением (http-gateway.yaml):
Конфигурация применяется вызовом следующей команды:
Теперь шлюз разрешает доступ к порту 80, но не имеет представления о том, куда маршрутизировать запросы. Для этого понадобятся Virtual Services.
Ресурс VirtualService
VirtualService указывает Ingress Gateway’ю, как маршрутизировать запросы, которые разрешены внутри кластера.
Запросы к нашему приложению, приходящие через http-gateway, должны быть отправлены в сервисы sa-frontend, sa-web-app и sa-feedback:
Маршруты, которые необходимо настроить с VirtualServices
Рассмотрим запросы, которые должны направляться на SA-Frontend:
Применим VirtualService вызовом:
Примечание: Когда мы применяем ресурсы Istio, Kubernetes API Server создаёт событие, которое получает Istio Control Plane, и уже после этого новая конфигурация применяется к прокси-серверам Envoy каждого pod’а. А контроллер Ingress Gateway представляется очередным Envoy, сконфигурированным в Control Plane. Всё это на схеме выглядит так:
Конфигурация Istio-IngressGateway для маршрутизации запросов
Перед тем, как продолжить, поработайте немного с приложением, чтобы сгенерировать трафик (его наличие необходимо для наглядности в последующих действиях — прим. перев.).
Kiali : наблюдаемость
Чтобы попасть в административный интерфейс Kiali, выполните следующую команду:
… и откройте http://localhost:20001/, залогинившись под admin/admin. Здесь вы найдете множество полезных возможностей, например, для проверки конфигурации компонентов Istio, визуализации сервисов по информации, собранной при перехвате сетевых запросов, получения ответов на вопросы «Кто к кому обращается?», «У какой версии сервиса возникают сбои?» и т.п. В общем, изучите возможности Kiali перед тем, как двигаться дальше — к визуализации метрик с Grafana.
Grafana: визуализация метрик
Собранные в Istio метрики попадают в Prometheus и визуализируются с Grafana. Чтобы попасть в административный интерфейс Grafana, выполните команду ниже, после чего откройте http://localhost:3000/:
Кликнув на меню Home слева сверху и выбрав Istio Service Dashboard в левом верхнем углу, начните с сервиса sa-web-app, чтобы посмотреть на собранные метрики:
Здесь нас ждёт пустое и совершенно скучное представление — руководство никогда такое не одобрит. Давайте же создадим небольшую нагрузку следующей командой:
Вот теперь у нас гораздо более симпатичные графики, а в дополнение к ним — замечательные инструменты Prometheus для мониторинга и Grafana для визуализации метрик, что позволят нам узнать о производительности, состоянии здоровья, улучшениях/деградации в работе сервисов на протяжении времени.
Наконец, посмотрим на трассировку запросов в сервисах.
Jaeger : трассировка
Трассировка нам потребуется, потому что чем больше у нас сервисов, тем сложнее добраться до причины сбоя. Посмотрим на простой случай из картинки ниже:
Типовой пример случайного неудачного запроса
Запрос приходит, падает — в чём же причина? Первый сервис? Или второй? Исключения есть в обоих — давайте посмотрим на логи каждого. Как часто вы ловили себя за таким занятием? Наша работа больше похожа на детективов программного обеспечения, а не разработчиков…
Это широко распространённая проблема в микросервисах и решается она распределёнными системами трассировки, в которых сервисы передают друг другу уникальный заголовок, после чего эта информация перенаправляется в систему трассировки, где она сопоставляется с данными запроса. Вот иллюстрация:
Для идентификации запроса используется TraceId
В Istio используется Jaeger Tracer, который реализует независимый от вендоров фреймворк OpenTracing API. Получить доступ к пользовательского интерфейсу Jaeger можно следующей командой:
Теперь зайдите на http://localhost:16686/ и выберите сервис sa-web-app. Если сервис не показан в выпадающем меню — проявите/сгенерируйте активность на странице и обновите интерфейс. После этого нажмите на кнопку Find Traces, которая покажет самые последние трейсы — выберите любой — покажется детализированная информация по всем трейсам:
Этот трейс показывает:
(A) За проброс заголовков отвечает Istio; (B) За заголовки отвечают сервисы
Istio делает основную работу, т.к. генерирует заголовки для входящих запросов, создаёт новые span’ы в каждом sidecare’е и пробрасывает их. Однако без работы с заголовками внутри сервисов полный путь трассировки запроса будет утерян.
Необходимо учитывать (пробрасывать) следующие заголовки:
Это несложная задача, однако для упрощения её реализации уже существует множество библиотек — например, в сервисе sa-web-app клиент RestTemplate пробрасывает эти заголовки, если просто добавить библиотеки Jaeger и OpenTracing в его зависимости.
Заметьте, что приложение Sentiment Analysis демонстрирует реализации на Flask, Spring и ASP.NET Core.
Теперь, когда стало ясно, что мы получаем из коробки (или почти «из коробки»), рассмотрим вопросы тонко настраиваемой маршрутизации, управления сетевым трафиком, безопасности и т.п.!
Прим. перев.: об этом читайте в следующей части материалов по Istio от Rinor Maloku, переводы которых последуют в нашем блоге в ближайшее время. UPDATE (14 марта): Вторая часть уже опубликована.
Что ждать от внедрения Istio? (обзор и видео доклада)
Istio — частный случай «сервисной сетки» (Service Mesh), понятия, о котором наверняка все слышали, и многие даже знают, что это такое. Мой доклад на Kuber Conf 2021 (мероприятие Yandex.Cloud, которое проходило 24 июня в Москве) посвящен возможным проблемам, к которым надо готовиться при внедрении Istio. Среди прочего я рассказал о том, как Istio влияет на трафик, какие есть возможности для его мониторинга, насколько безопасен mTLS.
Доклад отчасти отражает наш опыт работы с Istio как с одним из компонентов Kubernetes-платформы Deckhouse, отчасти основан на результатах внутренних нагрузочных тестов.
30 минут) и основную выжимку в текстовом виде.
Что такое Service Mesh
Для начала синхронизируем понимание, что такое Service Mesh.
Допустим, у вас есть приложение, оно живет своей жизнью, развивается. Вы нанимаете новых программистов, от бизнеса поступают новые задачи. Нормальная эволюция.
При этом объемы сетевого трафика растут, и в этой ситуации вы просто не можете не столкнуться с задачами по его управлению. Разберем эти задачи подробнее.
Типовая сетевая задача №1
Возьмем небольшой кусочек архитектуры — фронтенд и бэкенд. С точки зрения кластера он выглядит как две группы Pod’ов, которые общаются друг с другом.
Предположим, одному из Pod’ов стало плохо — не терминально, но приложение глючит. Health check’и в Kubernetes этого не замечают: сохраняют Pod в балансировке, и трафик на него продолжает поступать. От этого становится только хуже.
Было бы здорово выявлять эти пограничные состояния, когда вроде бы почти всё окей, но все-таки что-то идет не так. А выявив — дать нашему Pod’у отдохнуть, временно перенаправив трафик на другие Pod’ы. Такой подход называется circuit breaking.
Типовая сетевая задача №2
Вы написали новую версию бэкенда, прошли все тесты, на stage бэкенд показал себя хорошо. Но прежде, чем катить его в production, хорошо бы обкатать на настоящем трафике. То есть выделить группу пользователей для новой версии бэкенда и протестировать его на этой группе.
Если тесты прошли успешно — выкатываем бэкенд в production и обновляем все Pod’ы. Такая стратегия деплоя называется canary deployment. (Подробнее о разных стратегиях деплоя в Kubernetes мы писали здесь.)
Типовая сетевая задача №3
Например, ваш кластер развернут в публичном облаке. Из соображений отказоустойчивости вы «размазали» Pod’ы по разным зонам. При этом фронтенд- и бэкенд-Pod’ы общаются друг с другом беспорядочно, не обращая внимание на зоны. В мире облачных платформ такое беспорядочное общение не будет для вас бесплатным. Например, в AWS это выльется в дополнительные финансовые расходы, в Яндекс.Облаке — в дополнительный latency.
Нужно сделать так, чтобы фронтенды выбирали такие бэкенды, которые находятся в их зоне, а остальные бэкенды оставались бы для них «запасными». Такой способ маршрутизации называется locality load balancing.
Решаем сетевые задачи систематически
Подобные задачи по управлению сетевым трафиком можно перечислять ещё долго и каждый хотя бы раз сталкивался хотя бы с одной из них. Безусловно, у каждой из задач есть решение, которое можно реализовать на уровне приложения. Но проблема в том, что среднестатистическое приложение состоит из множества компонентов, или «шестеренок». И все они разные, к каждой нужен свой подход.
Именно для систематического решения подобных задач и придумали Service Mesh.
Service Mesh дает набор «кирпичей», из которых мы можем собирать собственные паттерны управления сетью. По-другому: Service Mesh — это фреймворк для управления любым TCP-трафиком с собственным декларативном языком. А в качестве бонуса Service Mesh предлагает дополнительные возможности для мониторинга (observability).
Благодаря Service Mesh вам не нужно задумываться о нюансах сетевого взаимодействия на уровне отдельных компонентов.
Вы можете рассматривать ваше приложение просто как дерево компонентов с очень примитивными связями. А все нюансы вынести за скобки и описать их с помощью Service Mesh.
Как это работает
Представим, что мы — большие любители «велосипедов»: мы не ищем готовые решения, потому что любим писать свои. И мы решили написать свой Service Mesh — supermesh.
Предположим, у нас есть приложение. Оно принимает запросы, генерирует новые — вот этим трафиком мы и хотим управлять. Но чтобы им управлять, нам нужно его перехватить. Для этого:
проникаем в сетевое окружение приложения;
внедряем туда наш перехватчик;
DNAT’ом перенаправляем на него входящий и исходящий трафик.
Поскольку мы работаем с Kubernetes, наше приложение «живет» в Pod’e, то есть внутри контейнера. Это значит, что мы для удобства можем «подселить» к приложению наш перехватчик в качестве sidecar-контейнера.
Теперь с этим перехваченным трафиком нужно что-то сделать, как-то его модифицировать. Очевидное решение — использовать прокси (например, nginx, HAProxy или Envoy).
Мы можем написать и свой прокси, но мы не настолько велосипедисты, поэтому остановимся на Envoy. Он хорошо вписывается в эту архитектуру, потому что умеет конфигурироваться удаленно, «на лету», и у него есть множество готовых удобных API.
В итоге мы можем перехватить трафик и влиять на него. Осталось сделать с ним что-то полезное, то есть реализовать какой-то из упомянутых выше паттернов управления — причем не абы какой, а такой, который выберет разработчик нашего приложения.
Мы могли бы передать разработчику «пульт управления» нашими sidecar’ами, чтобы он сам настраивал все нюансы. Но изначально вроде бы не этого хотели: нам нужна систематизация.
Очевидно, что нам не хватает какого-то промежуточного компонента — условного контроллера (supermeshd), который взял бы на себя управление, а разработчику предоставил декларативный язык, с помощью которого тот строил бы свои стратегии.
Теперь мы можем взять от разработчика набор паттернов, которые он хочет, а контроллер будет бегать по sidecar’ам и настраивать их. В мире Service Mesh такой контроллер называется Control Plane, а sidecar’ы с нашим перехватчиком — Data Plane.
Именно по такому принципу — естественно, с большими допущениями — и работает большинство реализаций Service Mesh: Kuma, Istio, Linkerd и пр.
В этом докладе я рассматриваю только Istio и делаю это «без объяснения причин». Кстати, согласно прошлогоднему опросу CNCF, это самое популярное Service Mesh-решение, которое используется в production.
Перед внедрением Istio к нему сразу же возникает ряд вопросов:
Как он повлияет на приложение? А на кластер?
Какие у него возможности по observability?
Надежен ли его Mutual TLS?
Как правильно засетапить Istio? А обновить?
Что, если что-то сломается?
Конечно, вопросов гораздо больше. Постараюсь ответить хотя бы на некоторые из них.
Как Istio влияет на приложение
Раньше, когда не было Istio, всё было просто и прозрачно: пользователь генерирует запрос, фронтенд генерирует дочерний запрос к бэкенду.
Как только появляется Istio, схема усложняется:
По сравнению с чистой инсталляцией «хопов» стало гораздо больше. Понятно, что это не может быть бесплатным. И самая очевидная цена — это latency.
Разработчики Istio обещают, что задержка не будет превышать 2,65 мс. Мне эти цифры показались не очень убедительными, поэтому я решил сделать свои замеры.
Нагрузочное тестирование
Для начала я написал примитивное клиент-серверное приложение. В качестве клиента использовал утилиту для нагрузочного тестирования k6, а в качестве сервера — nginx, который просто отдает статичные файлы. k6 отправляла по 10 тыс. запросов с предварительным прогревом в сторону сервера. Важно: клиент и сервер работали на разных узлах, чтобы не мешать друг другу.
Из этого приложения я собрал несколько вариантов инсталляции:
«чистую» схему, без Istio и всяких sidecar’ов;
сетап с Envoy в качестве sidecar’а;
сетап, в котором просто включен Istio;
еще один сетап с Istio, в котором задействованы 1000 правил авторизации — чтобы понять, влияют ли какие-то дополнительные правила на задержку, и как именно.
Что еще я посчитал важным сделать:
отключил логи на sidecar’ах;
включил сбор метрик sidecar’ах Istio;
использовал разные версии TLS;
включал и выключал HTTP/2;
включал и выключал keepalive на стороне клиента и между sidecar’ами;
использовал JSON-файлы трех размеров: 500Б, 200КБ, 1,7МБ;
экспериментировал с многопоточностью.
В итоге получилось 252 теста. Попробуем оценить собранные метрики.
1. Классический сценарий: примитивный клиент отправляет запросы серверу в один поток и не умеет держать keepalive.
Посмотрим на картину задержек:
Самый первый и очевидный вывод: действительно, перехват трафика не бесплатен. Причем в случае с Envoy в качестве sidecar’а и «тяжелым» файлом мы видим пятикратный рост задержки.
Уровень драматичности снижается, если мы посмотрим на полный round-trip (RTD) запроса. В самом худшем случае мы увидим троекратный оверхэд. А в некоторых случаях он почти не заметен:
Важно учитывать, что в моем случае естественный фон задержки у бэкенда крайне мал — 0,23 мс. В этом случае прирост в 2 мс кажется огромным — 800%. Но если у вас более сложный бэкенд, у которого задержка составляет десятки мс, то эти лишние 2 мс вы не заметите.
Второй вывод: по сравнению с «голым» Envoy’ем Istio все-таки дает небольшие накладные расходы. Возможно, это связано со сбором метрик и это стоит учитывать.
Третий вывод: правила авторизации, как и любые другие дополнительные настройки Istio, влияют на latency. Но это тоже не должно смущать, т. к. в реальной жизни вряд ли кому-то понадобится тысяча дополнительных правил.
Четвертый вывод: рост задержки при росте размера файла. Здесь задержка растет так же предсказуемо, и все пропорции по сравнению с «голой» инсталляцией сохраняются. То есть можно считать, что объем данных, которые мы прогоняем через Istio, на задержку не влияет.
2. Сценарий с шифрованием Mutual TLS
Результаты оказались везде более-менее одинаковыми, поэтому я покажу их на примере одной инсталляции, когда просто включен Istio:
Да, влияние есть. Но, опять же, это «копейки», которые можно не учитывать. Так что шифрование можно смело включать.
3. Любопытные наблюдения
Они не связаны напрямую с Istio, но я подумал, что ими не помешает поделиться.
Я решил: а что, если включить keepalive на клиентах? Не выиграю ли я в задержке? Потому что, во-первых, handshakes. Во-вторых, sidecar’ам надо будет поменьше думать.
Результаты покажу на примере того же сетапа с Istio:
Выигрыш хоть и небольшой, но есть. Мелочь, а приятно!
А что, если еще включить многопоточность. И вот тут я расстроился: при тестах на «средневесе» и «тяжеловесе» задержка выросла.
Разбираясь с этой проблемой, я нашел убедительное объяснение в статье блога Cloudflare, почему keepalive — это не всегда хорошо.
Итак, два главных вывода о влиянии Istio на приложение:
перехват трафика не бесплатен — можно смело закладывать накладные расходы по задержке порядка 2,5 мс;
если ваша естественная задержка измеряется в десятках и сотнях мс, лишние 2,5 мс вы не заметите.
Как Istio влияет на кластер
У нашего Istio, как и у supermeshd, который мы ранее реализовали сами, тоже есть Control Plane. Называется он istiod, и это отдельный контроллер, который работает в кластере.
Также у Istio есть свой декларативный язык, с помощью которого можно строить паттерны по управлению сетью. Технически язык представляет собой набор ресурсов Kubernetes, за которыми istiod пристально следит. Еще он следит за набором системных ресурсов: Namespace’ами, Pod’ами, узлами и т. д. В этом смысле контроллер Istio ничем не отличается от любого другого контроллера или оператора. И на практике никаких проблем с ним мы не выявили. Можем идти дальше.
Observability
У Istio есть интеграция со сторонними инструментами для мониторинга. Например, с дашбордом Kiali, с помощью которого можно визуализировать приложение в виде графа, рассмотреть отдельные связи компонентов, как они устроены, что у них «болит» и т. д.
Есть хороший набор дашбордов для графиков Grafana.
Istio также предоставляет базовые возможности для внедрения трассировки на основе Jaeger.
К сожалению, ничего из этого у вас не заработает «из коробки». Для Kiali и Grafana нужно будет установить Prometheus (и саму Grafana). В случае с трассировкой придется, как минимум, установить где-то Jaeger, а как максимум — научить ваше приложение грамотно обрабатывать HTTP-заголовки со служебными данными от трассировки.
Безопасность (Mutual TLS)
Напомню, протокол Mutual TLS (mTLS) — это взаимная, или двусторонняя, аутентификация клиента и сервера. Он нужен, когда мы хотим, чтобы наши клиент и сервер достоверно знали друг друга, не общались со всякими «незнакомцами». Также mTLS нужен, когда мы хотим зашифровать трафик между нашими приложениями. Технически это достигается с помощью обычных SSL-сертификатов.
В мире Istio у каждого Pod’а есть сертификат, который подтверждает подлинность этого Pod’а, а именно — подлинность его идентификатора (ID). В терминологии Istio идентификатор — это principal. Он состоит из трех частей:
ID кластера (Trust Domain),
ServiceAccount’а, под которым работает Pod.
За выпуск сертификатов отвечает Control Plane, на основе собственного корневого сертификата. У каждой инсталляции Istio есть собственный корневой сертификат — root CA (его не стоит путать с корневым сертификатом самого кластера). На основе корневого сертификата выпускаются индивидуальные.
Давайте разберём весь жизненный цикл индивидуального сертификата.
Envoy в Istio не общается напрямую с Control Plane: он это делает через посредника, который называется Istio agent — утилита, написанная на Go, которая живет в одном контейнере с Envoy. Этот агент отвечает за ротацию сертификатов.
Istio-agent генерирует CSR с заявкой на ID (principal), который полагается нашему Pod’у. Но сначала этот ID нужно как-то сгенерировать. С Trust Domain всё просто: агент знает, в каком кластере работает Pod (переменная в ENV). С Namespace’ом и ServiceAccount’ом чуть сложнее…
Но вообще: что такое ServiceAccount? Остановимся на этом чуть подробнее.
ServiceAccount
У Kubernetes есть свой API, который подразумевает, что с ним никто анонимно общаться не будет. С другой стороны, API подразумевает, что к нему будут общаться Pod’ы. Рядовым приложениям это, как правило, не нужно: обычно с API Kubernetes общаются контроллеры, операторы и т. п. И чтобы решить этот вопрос, придумали специальные учетные записи, которые называются ServiceAccount.
ServiceAccount — это примитивный ресурс в K8s, который в момент создания никакой информации не несет. Он просто существует в каком-то Namespace’е, с каким-то именем. Как только такой ресурс появляется в кластере, Kubernetes на это реагирует и выпускает JWT-токен. Этот токен целиком описывает появившийся ServiceAccount. То есть Kubernetes подтверждает: «в моем кластере, в таком-то Namespace’е, с таким-то именем есть ServiceAccount» — и выдает специальный токен, который складывает в Secret ( mysa-token-123 ).
Secret привязывается к ServiceAccount’у, который тем самым становится чем-то вроде ресурса со свидетельством о рождении. Данный ServiceAccount можно «привязать» к любому Pod’у, после чего, в ФС Pod’а будет автоматически примонтирован Secret с токеном, причем по заранее известному пути ( /run/secret/kubernetes.io/serviceaccount/token ). Собственно, данный токен и пригодится нам для того, чтобы узнать Namespace, в котором мы работаем, и ServiceAccount, от имени которого мы работаем.
Мы сгенерировали principal — теперь можем положить его в CSR.
Полученный CSR мы можем отправить в istiod. Но чтобы istiod поверил, что мы — это мы, в дополнение отправляем токен. Далее istiod проверяет этот токен через API K8s ( TokenReview ). Если K8s говорит, что всё хорошо, istiod подписывает CSR и возвращает в sidecar.
Для каждого Pod’а ротация сертификатов происходит раз в сутки.
Чтобы что-то взломать в этой системе, можно:
украсть сертификат (он будет действовать не более суток);
украсть токен ServiceAccount’а Pod’а, чтобы заказывать сертификаты от имени этого Pod’а;
взломать API Kubernetes — тогда можно заказать сколько угодно токенов, от имени какого угодно Pod’а;
украсть корневой сертификат Control Plane — самый примитивный и опасный взлом.
Всё это на самом деле довольно сложно реализовать. Поэтому можем считать, что mTLS в Istio безопасен.
Другие вопросы к Istio
К сожалению, осталось еще много вопросов, не раскрытых в докладе. И по каждому из этих вопросов достаточно много нюансов. Порой эти нюансы либо очень плохо описаны в документации, либо не описаны вовсе — приходится ковыряться в коде и обращаться к разработчикам Istio.
Множество из этих нюансов мы учли в модуле Istio для нашей платформы Deckhouse. С ее помощью вы можете за 8 минут развернуть готовый к использованию Kubernetes-кластер на любой инфраструктуре: в облаке, на «голом железе», в OpenStack и т. д.
Подробнее познакомиться с функциями, которые решает Istio в рамках Deckhouse, можно в нашей документации.