Что ждать от внедрения Istio? (обзор и видео доклада)

Istio — частный случай «сервисной сетки» (Service Mesh), понятия, о котором наверняка все слышали, и многие даже знают, что это такое. Мой доклад на Kuber Conf 2021 (мероприятие Yandex.Cloud, которое проходило 24 июня в Москве) посвящен возможным проблемам, к которым надо готовиться при внедрении Istio. Среди прочего я рассказал о том, как Istio влияет на трафик, какие есть возможности для его мониторинга, насколько безопасен mTLS.

Доклад отчасти отражает наш опыт работы с Istio как с одним из компонентов Kubernetes-платформы Deckhouse, отчасти основан на результатах внутренних нагрузочных тестов.

55d51acc58397fcf32f189c6dce930d5.jpg

Представляю видео с докладом (~30 минут) и основную выжимку в текстовом виде.

Что такое Service Mesh

Для начала синхронизируем понимание, что такое Service Mesh.

Допустим, у вас есть приложение, оно живет своей жизнью, развивается. Вы нанимаете новых программистов, от бизнеса поступают новые задачи. Нормальная эволюция.

image-loader.svg

При этом объемы сетевого трафика растут, и в этой ситуации вы просто не можете не столкнуться с задачами по его управлению. Разберем эти задачи подробнее.

Типовая сетевая задача №1

Возьмем небольшой кусочек архитектуры — фронтенд и бэкенд. С точки зрения кластера он выглядит как две группы Pod«ов, которые общаются друг с другом.

Предположим, одному из Pod«ов стало плохо — не терминально, но приложение глючит. Health check’и в Kubernetes этого не замечают: сохраняют Pod в балансировке, и трафик на него продолжает поступать. От этого становится только хуже.

image-loader.svg

Было бы здорово выявлять эти пограничные состояния, когда вроде бы почти всё окей, но все-таки что-то идет не так. А выявив — дать нашему Pod«у отдохнуть, временно перенаправив трафик на другие Pod«ы. Такой подход называется circuit breaking.

image-loader.svg

Типовая сетевая задача №2

Вы написали новую версию бэкенда, прошли все тесты, на stage бэкенд показал себя хорошо. Но прежде, чем катить его в production, хорошо бы обкатать на настоящем трафике. То есть выделить группу пользователей для новой версии бэкенда и протестировать его на этой группе.

image-loader.svg

Если тесты прошли успешно — выкатываем бэкенд в production и обновляем все Pod«ы. Такая стратегия деплоя называется canary deployment. (Подробнее о разных стратегиях деплоя в Kubernetes мы писали здесь.)

Типовая сетевая задача №3

Например, ваш кластер развернут в публичном облаке. Из соображений отказоустойчивости вы «размазали» Pod«ы по разным зонам. При этом фронтенд- и бэкенд-Pod«ы общаются друг с другом беспорядочно, не обращая внимание на зоны. В мире облачных платформ такое беспорядочное общение не будет для вас бесплатным. Например, в AWS это выльется в дополнительные финансовые расходы, в Яндекс.Облаке — в дополнительный latency.

image-loader.svg

Нужно сделать так, чтобы фронтенды выбирали такие бэкенды, которые находятся в их зоне, а остальные бэкенды оставались бы для них «запасными». Такой способ маршрутизации называется locality load balancing.

Решаем сетевые задачи систематически

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

Именно для систематического решения подобных задач и придумали Service Mesh.

image-loader.svg

Service Mesh дает набор «кирпичей», из которых мы можем собирать собственные паттерны управления сетью. По-другому: Service Mesh — это фреймворк для управления любым TCP-трафиком с собственным декларативном языком. А в качестве бонуса Service Mesh предлагает дополнительные возможности для мониторинга (observability).

Благодаря Service Mesh вам не нужно задумываться о нюансах сетевого взаимодействия на уровне отдельных компонентов.

image-loader.svg

Вы можете рассматривать ваше приложение просто как дерево компонентов с очень примитивными связями. А все нюансы вынести за скобки и описать их с помощью Service Mesh.

image-loader.svg

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

Представим, что мы — большие любители «велосипедов»: мы не ищем готовые решения, потому что любим писать свои. И мы решили написать свой Service Mesh — supermesh.

Предположим, у нас есть приложение. Оно принимает запросы, генерирует новые — вот этим трафиком мы и хотим управлять. Но чтобы им управлять, нам нужно его перехватить. Для этого:

  • проникаем в сетевое окружение приложения;

  • внедряем туда наш перехватчик;

  • DNAT«ом перенаправляем на него входящий и исходящий трафик.

image-loader.svg

Поскольку мы работаем с Kubernetes, наше приложение «живет» в Pod«e, то есть внутри контейнера. Это значит, что мы для удобства можем «подселить» к приложению наш перехватчик в качестве sidecar-контейнера.

image-loader.svg

Теперь с этим перехваченным трафиком нужно что-то сделать, как-то его модифицировать. Очевидное решение — использовать прокси (например, nginx, HAProxy или Envoy).

Мы можем написать и свой прокси, но мы не настолько велосипедисты, поэтому остановимся на Envoy. Он хорошо вписывается в эту архитектуру, потому что умеет конфигурироваться удаленно, «на лету», и у него есть множество готовых удобных API.

image-loader.svg

В итоге мы можем перехватить трафик и влиять на него. Осталось сделать с ним что-то полезное, то есть реализовать какой-то из упомянутых выше паттернов управления — причем не абы какой, а такой, который выберет разработчик нашего приложения.

Мы могли бы передать разработчику «пульт управления» нашими sidecar«ами, чтобы он сам настраивал все нюансы. Но изначально вроде бы не этого хотели: нам нужна систематизация.

Очевидно, что нам не хватает какого-то промежуточного компонента — условного контроллера (supermeshd), который взял бы на себя управление, а разработчику предоставил декларативный язык, с помощью которого тот строил бы свои стратегии.

image-loader.svg

Теперь мы можем взять от разработчика набор паттернов, которые он хочет, а контроллер будет бегать по sidecar«ам и настраивать их. В мире Service Mesh такой контроллер называется Control Plane, а sidecar«ы с нашим перехватчиком — Data Plane.

Именно по такому принципу — естественно, с большими допущениями — и работает большинство реализаций Service Mesh: Kuma, Istio, Linkerd и пр.

В этом докладе я рассматриваю только Istio и делаю это «без объяснения причин». Кстати, согласно прошлогоднему опросу CNCF, это самое популярное Service Mesh-решение, которое используется в production.

image-loader.svg

Перед внедрением Istio к нему сразу же возникает ряд вопросов:

  • Как он повлияет на приложение? А на кластер?

  • Какие у него возможности по observability?

  • Надежен ли его Mutual TLS?

  • Что с федерацией?

  • Как правильно засетапить Istio? А обновить?

  • Что, если что-то сломается?

Конечно, вопросов гораздо больше. Постараюсь ответить хотя бы на некоторые из них.

Как Istio влияет на приложение

Раньше, когда не было Istio, всё было просто и прозрачно: пользователь генерирует запрос, фронтенд генерирует дочерний запрос к бэкенду.

image-loader.svg

Как только появляется Istio, схема усложняется:

image-loader.svg

По сравнению с чистой инсталляцией «хопов» стало гораздо больше. Понятно, что это не может быть бесплатным. И самая очевидная цена — это latency.

Разработчики Istio обещают, что задержка не будет превышать 2,65 мс. Мне эти цифры показались не очень убедительными, поэтому я решил сделать свои замеры.

Нагрузочное тестирование

Для начала я написал примитивное клиент-серверное приложение. В качестве клиента использовал утилиту для нагрузочного тестирования k6, а в качестве сервера — nginx, который просто отдает статичные файлы. k6 отправляла по 10 тыс. запросов с предварительным прогревом в сторону сервера. Важно: клиент и сервер работали на разных узлах, чтобы не мешать друг другу.

Из этого приложения я собрал несколько вариантов инсталляции:

  • «чистую» схему, без Istio и всяких sidecar«ов;

  • сетап с Envoy в качестве sidecar«а;

  • сетап, в котором просто включен Istio;

  • еще один сетап с Istio, в котором задействованы 1000 правил авторизации — чтобы понять, влияют ли какие-то дополнительные правила на задержку, и как именно.

image-loader.svg

Что еще я посчитал важным сделать:

  • отключил логи на sidecar«ах;

  • включил сбор метрик sidecar«ах Istio;

  • использовал разные версии TLS;

  • включал и выключал HTTP/2;

  • включал и выключал keepalive на стороне клиента и между sidecar«ами;

  • использовал JSON-файлы трех размеров: 500Б, 200КБ, 1,7МБ;

  • экспериментировал с многопоточностью.

В итоге получилось 252 теста. Попробуем оценить собранные метрики.

1. Классический сценарий: примитивный клиент отправляет запросы серверу в один поток и не умеет держать keepalive.

Посмотрим на картину задержек:

image-loader.svg

Самый первый и очевидный вывод: действительно, перехват трафика не бесплатен. Причем в случае с Envoy в качестве sidecar«а и «тяжелым» файлом мы видим пятикратный рост задержки.

Уровень драматичности снижается, если мы посмотрим на полный round-trip (RTD) запроса. В самом худшем случае мы увидим троекратный оверхэд. А в некоторых случаях он почти не заметен:

image-loader.svg

Важно учитывать, что в моем случае естественный фон задержки у бэкенда крайне мал — 0,23 мс. В этом случае прирост в 2 мс кажется огромным — 800%. Но если у вас более сложный бэкенд, у которого задержка составляет десятки мс, то эти лишние 2 мс вы не заметите.

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

Третий вывод: правила авторизации, как и любые другие дополнительные настройки Istio, влияют на latency. Но это тоже не должно смущать, т. к. в реальной жизни вряд ли кому-то понадобится тысяча дополнительных правил.

Четвертый вывод: рост задержки при росте размера файла. Здесь задержка растет так же предсказуемо, и все пропорции по сравнению с «голой» инсталляцией сохраняются. То есть можно считать, что объем данных, которые мы прогоняем через Istio, на задержку не влияет.

image-loader.svg

2. Сценарий с шифрованием Mutual TLS

Результаты оказались везде более-менее одинаковыми, поэтому я покажу их на примере одной инсталляции, когда просто включен Istio:

image-loader.svg

Да, влияние есть. Но, опять же, это «копейки», которые можно не учитывать. Так что шифрование можно смело включать.

3. Любопытные наблюдения

Они не связаны напрямую с Istio, но я подумал, что ими не помешает поделиться.

Я решил:, а что, если включить keepalive на клиентах? Не выиграю ли я в задержке? Потому что, во-первых, handshakes. Во-вторых, sidecar«ам надо будет поменьше думать.

Результаты покажу на примере того же сетапа с Istio:

image-loader.svg

Выигрыш хоть и небольшой, но есть. Мелочь, а приятно!

А что, если еще включить многопоточность?… И вот тут я расстроился: при тестах на «средневесе» и «тяжеловесе» задержка выросла.

image-loader.svg

Разбираясь с этой проблемой, я нашел убедительное объяснение в статье блога 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),

  • Namespace«а приложения,

  • ServiceAccount«а, под которым работает Pod.

image-loader.svg

За выпуск сертификатов отвечает Control Plane, на основе собственного корневого сертификата. У каждой инсталляции Istio есть собственный корневой сертификат — root CA (его не стоит путать с корневым сертификатом самого кластера). На основе корневого сертификата выпускаются индивидуальные.

image-loader.svg

Давайте разберём весь жизненный цикл индивидуального сертификата.

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, от имени которого мы работаем.

image-loader.svg

Мы сгенерировали principal — теперь можем положить его в CSR.

Полученный CSR мы можем отправить в istiod. Но чтобы istiod поверил, что мы — это мы, в дополнение отправляем токен. Далее istiod проверяет этот токен через API K8s (TokenReview). Если K8s говорит, что всё хорошо, istiod подписывает CSR и возвращает в sidecar.

image-loader.svg

Для каждого Pod«а ротация сертификатов происходит раз в сутки.

Чтобы что-то взломать в этой системе, можно:

  • украсть сертификат (он будет действовать не более суток);

  • украсть токен ServiceAccount«а Pod«а, чтобы заказывать сертификаты от имени этого Pod«а;

  • взломать API Kubernetes — тогда можно заказать сколько угодно токенов, от имени какого угодно Pod«а;

  • украсть корневой сертификат Control Plane — самый примитивный и опасный взлом.

Всё это на самом деле довольно сложно реализовать. Поэтому можем считать, что mTLS в Istio безопасен.

Другие вопросы к Istio

К сожалению, осталось еще много вопросов, не раскрытых в докладе. И по каждому из этих вопросов достаточно много нюансов. Порой эти нюансы либо очень плохо описаны в документации, либо не описаны вовсе — приходится ковыряться в коде и обращаться к разработчикам Istio.

Множество из этих нюансов мы учли в модуле Istio для нашей платформы Deckhouse. С ее помощью вы можете за 8 минут развернуть готовый к использованию Kubernetes-кластер на любой инфраструктуре: в облаке, на «голом железе», в OpenStack и т. д.

Подробнее познакомиться с функциями, которые решает Istio в рамках Deckhouse, можно в нашей документации.

Видео и слайды

Видео с выступления (~31 минута):

Презентация доклада:

P.S.

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

© Habrahabr.ru