Istio Service Mesh Мы в Namely уже год как юзаем Istio. Он тогда только-только вышел. У нас здорово упала производительность в кластере Kubernetes, мы хотели распределенную трассировку и взяли Istio, чтобы запустить Jaeger и разобраться. Service mesh так здорово вписалась в нашу инфраструктуру, что мы решили вложиться в этот инструмент. Пришлось помучиться, но мы изучили его вдоль и поперек. Это первый пост из серии, где я расскажу, как Istio интегрируется с Kubernetes и что мы узнали о его работе. Иногда будем забредать в технические дебри, но не сильно далеко. Дальше будут еще посты. Istio — это инструмент конфигурации service mesh. Он читает состояние кластера Kubernetes и делает обновление до прокси L7 (HTTP и gRPC), которые реализуются как sidecar-ы в подах Kubernetes. Эти sidecar-ы — контейнеры Envoy, которые читают конфигурацию из Istio Pilot API (и сервиса gRPC) и маршрутизируют по ней трафик. С мощным прокси L7 под капотом мы можем использовать метрики, трассировки, логику повтора, размыкатель цепи, балансировку нагрузки и канареечные деплои. В Kubernetes мы создаем под c помощью деплоя или StatefulSet. Или это может быть просто «ванильный» под без контроллера высокого уровня. Затем Kubernetes изо всех сил поддерживает желаемое состояние — создает поды в кластере на ноде, следит, чтобы они запускались и перезапускались. Когда под создается, Kubernetes проходит по жизненному циклу API, убеждается, что каждый шаг будет успешным, и только потом наконец создает под на кластере. Этапы жизненного цикла API: 
Спасибо Banzai Cloud за крутую картинку. Один из этапов — модифицирующие вебхуки допуска. Это отдельная часть жизненного цикла в Kubernetes, где ресурсы кастомизируются до коммита в хранилище etcd — источнике истины для конфигурации Kubernetes. И здесь Istio творит свою магию. Когда под создается (через kubectl или Deployment), он проходит через этот жизненный цикл, и модифицирующие вебхуки доступа меняют его, прежде чем выпустить в большой мир. Во время установки Istio добавляется istio-sidecar-injector как ресурс конфигурации модифицирующих вебхуков: И конфигурация: Тут написано, что Kubernetes должен отправлять все события создания подов в сервис istio-sidecar-injector в неймспейс istio-system, если в неймспейсе есть ярлык istio-injection=enabled. Инжектор включает в PodSpec еще два контейнера: один временный для настройки правил прокси и один — собственно для проксирования. Sidecar-инжектор вставляет эти контейнеры по шаблону из карты конфигурации istio-sidecar-injector. Этот процесс еще называется sidecaring. Sidecar-ы — это трюки нашего фокусника Istio. Istio так ловко все проворачивает, что со стороны это прямо магия, если не знать деталей. А знать их полезно, если вдруг надо отладить сетевые запросы. В Kubernetes есть временные одноразовые init-контейнеры, которые можно запускать до основных. Они объединяют ресурсы, переносят базы данных или, как в случае с Istio, настраивают правила сети. Istio использует Envoy для проксирования всех запросов к подам по нужным маршрутам. Для этого Istio создает правила iptables, и они отправляют входящий и исходящий трафик прямо в Envoy, а тот аккуратно проксирует трафик в пункт назначения. Трафик делает небольшой крюк, зато у вас есть распределенная трассировка, метрики запросов и соблюдение политик. В этом файле из репозитория Istio видно, как Istio создает правила iptables. @jimmysongio нарисовал отличную схему связи между правилами iptables и прокси Envoy: 
Трафик Envoy–Envoy Envoy получает весь входящий и весь исходящий трафик, поэтому весь трафик вообще перемещается внутри Envoy, как на схеме. Прокси Istio — это еще один контейнер, который добавляется во все поды, изменяемые sidecar-инжектором Istio. В этом контейнере запускается процесс Envoy, который получает весь трафик пода (за некоторым исключением, вроде трафика из вашего кластера Kubernetes). Процесс Envoy обнаруживает все маршруты через Envoy v2 API, который реализует Istio. У самого 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 выбирает под для этого трафика. Например, команда сначала найдет виртуальный IP, назначенный сервису my-service в неймспейсе default, и этот IP перешлет трафик в под, который соответствует ярлыку сервиса. Istio и Envoy слегка меняют эту логику. Istio настраивает Envoy на основе сервисов и конечных точек в кластере Kubernetes и использует умные функции маршрутизации и балансировки нагрузки Envoy, чтобы обойти сервис Kubernetes. Вместо проксирования по одному IP Envoy подключается прямо к IP пода. Для этого Istio сопоставляет конфигурацию Kubernetes с конфигурацией Envoy. Термины Kubernetes, Istio и Envoy немного отличаются, и не сразу понятно, что с чем едят. Сервис в Kubernetes сопоставляется с кластером в Envoy. Кластер Envoy содержит список конечных точек, то есть IP (или имена хостов) экземпляров для обработки запросов. Чтобы увидеть список кластеров, настроенных в sidecar-поде Istio, запустите istioctl proxy-config cluster <имя пода>. Эта команда показывает текущее положение дел с точки зрения пода. Вот пример из одной нашей среды: Все те же сервисы есть в этом пространстве имен: Как Istio узнает, какой протокол использует сервис? Настраивает протоколы для манифестов сервисов по полю name в записи порта. Если там grpc или префикс grpc-, Istio настроит для сервиса протокол HTTP2. Мы на горьком опыте узнали, как Istio использует имя порта, когда запороли конфиги прокси, потому что не указали префиксы http или grpc… Если использовать kubectl и админскую страницу переадресации портов в Envoy, видно, что конечные точки account-grpc-public реализуются Pilot как кластер в Envoy с протоколом HTTP2. Это подтверждает наши предположения: Порт 15000 — это админская страница Envoy, доступная в каждом sidecar. Прослушиватели узнают конечные точки Kubernetes, чтобы пропускать трафик в поды. У сервиса проверки адреса здесь одна конечная точка: Поэтому у пода проверки адреса один прослушиватель на порте 50051: В Istio вместо стандартного объекта Kubernetes Ingress берется более абстрактный и эффективный кастомный ресурс — VirtualService. VirtualService сопоставляет маршруты с апстрим-кластерами, привязывая их к шлюзу. Это как использовать Kubernetes Ingress с Ingress-контроллером. В Namely мы используем Istio Ingress-Gateway для всего внутреннего GRPC-трафика: На первый взгляд в примере ничего не разберешь. Тут не видно, но деплой Istio-IngressGateway записывает, какие конечные точки нужны, на основе селектора istio: ingressgateway. В этом примере IngressGateway направляет трафик для всех доменов через порт 80 по протоколу HTTP2. VirtualService реализует маршруты для этого шлюза, сопоставляет по префиксу /namely.address_validator.AddressValidator и передает в апстрим-сервис addressvalidator через порт 50051 с правилом повтора через две секунды. Если переадресовать порт пода Istio-IngressGateway и посмотреть конфигурацию Envoy, мы увидим, что делает VirtualService: Возникает ошибка 503 или 404 Причины разные, но обычно такие: Что означает NR/UH/UF в логах прокси Istio? Подробности читайте на сайте Envoy. По поводу высокой доступности с Istio Почему Cronjob не завершается? Когда основная рабочая нагрузка выполнена, sidecar-контейнер продолжает работать. Чтобы обойти проблему, отключите sidecar в cronjobs, добавив аннотацию sidecar.istio.io/inject: “false” в PodSpec. Как установить Istio? Мы используем Spinnaker для деплоев, но обычно берем последние Helm-чарты, колдуем над ними, используем helm template -f values.yml и коммитим файлы в Github, чтобы посмотреть изменения, прежде чем применить их через kubectl apply -f -. Это для того, чтобы нечаянно не изменить CRD или API в разных версиях. Спасибо Bobby Tables и Michael Hamrah за помощь в написании поста.