ADG Оставить заявку
Блог DevOps 5 мин чтения

Istio 1.1 в production: mTLS между сервисами через CRD и трассировка Jaeger без правок кода

Развернули Istio 1.1 в промышленном кластере: включили mTLS через PeerAuthentication, Jaeger показал узкие места в цепочке вызовов без единой правки в приложениях.

Контекст момента

Istio 1.1 GA - улучшенное управление трафиком, мультикластерность и производительность sidecar

Istio 1.1 вышел в середине марта. Команда проекта назвала его крупнейшим релизом с момента 1.0 - новая модель конфигурации, заметные улучшения производительности sidecar, нормальная поддержка мультикластерных топологий. Мы взяли его в один из managed-проектов и прогнали через нормальный production: не тестовый стенд, а живой кластер с трафиком.

Результат - неоднозначный, но в хорошем смысле. Что-то сработало лучше ожиданий, что-то потребовало времени.

Контекст: зачем вообще Istio

У проекта несколько сервисов на Go и Python, общающихся по HTTP/gRPC. Проблем с базовой связностью нет, но было две задачи, которые висели давно:

  • Взаимная аутентификация между сервисами. TLS-сертификаты раздавать вручную никто не хотел, а без mTLS запрос от условного billing-service к order-service ничем не отличается от запроса с ноды злоумышленника.
  • Сквозная трассировка. Jaeger у нас уже стоит, но инструментировать каждый сервис руками - задача, которая всегда откладывается.

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

Установка: что изменилось в 1.1

В 1.0 установка через Helm была, мягко говоря, монструозной - один чарт с сотнями параметров, всё вместе. В 1.1 появился istioctl с профилями установки: default, demo, minimal. Мы взяли default и точечно включили нужные компоненты:

istioctl manifest apply --set profile=default \
  --set values.tracing.enabled=true \
  --set values.tracing.provider=jaeger \
  --set values.kiali.enabled=true

Kiali - это UI для визуализации service mesh. На пилоте он оказался полезнее, чем мы ожидали - граф зависимостей между сервисами сразу даёт картину того, кто с кем говорит и с какими кодами ответа.

Sidecar-инжекция включается на уровне namespace:

kubectl label namespace production istio-injection=enabled

Дальше поды пересоздаются с istio-proxy контейнером автоматически при следующем деплое. В нашем случае - через ArgoCD, так что промкатка прошла штатно.

mTLS: включили одним CRD

Вот здесь 1.1 действительно сделал шаг вперёд. В предыдущих версиях управление mTLS было через MeshPolicy и DestinationRule - два отдельных объекта, которые легко рассинхронизировать. В 1.1 появился PeerAuthentication - единый CRD, описывающий политику аутентификации для namespace или отдельного сервиса.

Для включения mTLS в режиме STRICT для всего namespace:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT

Применили - и всё. Трафик между сервисами в namespace production теперь зашифрован и взаимно аутентифицирован. Сертификаты выдаёт Citadel (компонент Istio), ротация автоматическая. Ни одной правки в коде приложений.

Первые несколько минут после применения были нервными - один из сервисов начал отдавать 503. Оказалось, что legacy-клиент (внешний скрипт на bash с curl) ходил напрямую, без sidecar, и после перехода в STRICT mode естественно получил отказ. Добавили для этого клиента отдельный PeerAuthentication с mode: PERMISSIVE на конкретный порт - ситуация разрешилась.

Трассировка: Jaeger без единой строки в приложениях

Это, пожалуй, самая впечатляющая часть для команды разработки. Istio-proxy автоматически генерирует spans для каждого входящего и исходящего запроса через sidecar. Заголовки x-b3-traceid, x-b3-spanid и компания - Envoy проставляет их сам и передаёт дальше.

Нюанс один: для того чтобы цепочка span-ов была сквозной, приложение должно прокидывать эти заголовки при исходящих запросах. То есть если api-gateway получил запрос с x-b3-traceid и делает запрос к order-service - оно должно передать тот же traceid дальше. Это единственное, что требует правки кода - и то минимальной: просто копировать заголовки из входящего запроса в исходящий.

В Go это буквально несколько строк в HTTP-клиенте или middleware. В Python - то же самое. Это не инструментирование в полном смысле - это просто проброс заголовков.

После этого Jaeger показал полную картину:

api-gateway [12ms]
  └─ order-service [8ms]
       ├─ postgres (через [pgbouncer](/blog/terms/pgbouncer/)) [5ms]
       └─ billing-service [2ms]
            └─ external-payment-api [1ms]

Узкое место нашли сразу - запрос к order-service делал несколько последовательных запросов к базе там, где можно было один batch. Это было видно без какого-либо изменения в самих сервисах, только благодаря трассировке через Istio.

Производительность sidecar

Один из главных претензий к Istio 1.0 был overhead от Envoy sidecar. В 1.1 команда проделала работу по оптимизации: xDS push стал инкрементальным - Pilot теперь не пересылает всю конфигурацию при каждом изменении, а только дельту.

На нашем кластере (около 30 подов) субъективно разница есть - время запуска нового пода с sidecar сократилось. Но утверждать что-то с конкретными цифрами мы не будем - нагрузочного тестирования отдельно под это не проводили.

Latency overhead от mTLS-рукопожатия на warm connection незначителен - в пределах погрешности наших p99-метрик. На холодных соединениях заметнее, но в сервисах с connection pooling это не проблема.

Что не понравилось

Документация отстаёт от кода. Часть примеров в официальной документации всё ещё написана под старый API (MeshPolicy/ServiceRoleBinding), которые в 1.1 объявлены deprecated в пользу нового security API. Натыкаться на это при отладке неприятно.

CRD-ов много. После установки Istio в кластере появляется несколько десятков custom resource definitions. kubectl get crd | wc -l даёт результат, который с непривычки пугает. Понять что из этого нужно тебе прямо сейчас - отдельная работа.

Envoy memory. Каждый sidecar держит в памяти полную карту сервисов mesh. На большом кластере это заметно. В 1.1 есть механизм Sidecar CRD для ограничения видимости - но настраивать его руками на каждый namespace утомительно.

Где сейчас

mTLS включён в production, трассировка работает. На неделе после включения команда разработки самостоятельно нашла и поправила два медленных запроса к базе - просто открыли Jaeger и посмотрели. Это и есть главный результат.

Мультикластерность и traffic management (VirtualService, DestinationRule для canary) - следующий этап, сейчас изучаем. Istio умеет много, но вводить всё сразу - верный способ получить сложность ради сложности.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.