Istio Service Mesh в production: опыт внедрения для ПлатёжСервис

Зачем финтеху Service Mesh

Финтех-компания ПлатёжСервис — процессинговый центр, обрабатывающий платежи для 340 интернет-магазинов — обратилась к нам с комплексной задачей. Их платформа из 87 микросервисов на Kubernetes обрабатывала ~15 000 транзакций в минуту, но имела три критические проблемы:

  • Безопасность: требование PCI DSS Level 1 — шифрование всего межсервисного трафика. Текущее решение (TLS на уровне приложений) покрывало только 60% сервисов
  • Надёжность: каскадные сбои при деградации одного сервиса. Отсутствие circuit breakers и retry policies
  • Наблюдаемость: отсутствие единой картины трафика между сервисами. 87 сервисов × ~300 сетевых путей — невозможно понять, кто с кем общается

Мы предложили внедрение Istio Service Mesh как единое решение для всех трёх проблем. Проект занял 10 недель: 4 недели подготовки, 4 недели миграции и 2 недели стабилизации.

Команда проекта: 3 наших DevOps-инженера, 1 сетевой специалист и 2 SRE-инженера клиента.

Архитектура Istio: как это работает под капотом

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

Control Plane и Data Plane

Istio состоит из двух плоскостей:

  • Control Plane (istiod) — управляющий компонент. Получает конфигурацию из Kubernetes API, транслирует её в правила для Envoy и распространяет на все sidecar-прокси
  • Data Plane (Envoy sidecars) — прокси-контейнеры, инжектируемые в каждый Pod. Перехватывают весь входящий и исходящий трафик

В каждом Pod рядом с Envoy работает istio-agent, который поддерживает соединение с Control Plane и транслирует состояние кластера в Envoy API.

Перехват трафика: как работает DNAT

Istio использует iptables правила для перенаправления трафика через sidecar:

# Правила iptables, создаваемые istio-init контейнером:
# Исходящий трафик → порт 15001 (Envoy outbound listener)
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 15001

# Входящий трафик → порт 15006 (Envoy inbound listener)
iptables -t nat -A PREROUTING -p tcp -j REDIRECT --to-port 15006

Envoy использует флаг use_original_dst для определения реального адреса назначения до DNAT. Это позволяет ему применить правильные политики маршрутизации и безопасности.

Путь запроса: DNAT перехват → выбор listener по original_dst → обработка filter chain (TLS, авторизация, метрики) → выбор cluster → балансировка по endpoint → установка соединения.

Этап 1: mTLS — шифрование всего трафика

Первоочередная задача — выполнение требования PCI DSS по шифрованию межсервисного трафика. Istio делает это через mutual TLS (mTLS).

Поэтапное включение mTLS

Мы начали с PERMISSIVE mode, который принимает и шифрованный, и нешифрованный трафик — это позволило внедрять sidecar-ы постепенно, не ломая связность:

# Этап 1: PERMISSIVE mode (принимает оба варианта)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system  # Применяется ко всему mesh
spec:
  mtls:
    mode: PERMISSIVE

После того как все 87 сервисов получили sidecar-ы, мы переключились на STRICT mode:

# Этап 2: STRICT mode (только шифрованный трафик)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: mtls-strict
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

Критическая ошибка, которую мы предотвратили: PERMISSIVE mode некорректно работает с протоколами, где сервер отправляет первый пакет (FTP, SMTP, MySQL native protocol). Envoy ожидает пакет от клиента для определения типа шифрования, а сервер ожидает подключения клиента — deadlock. У ПлатёжСервис был MySQL, подключённый напрямую — мы вынесли его за mesh через exclusion annotation:

# Исключение MySQL Pod из mesh
apiVersion: v1
kind: Pod
metadata:
  annotations:
    sidecar.istio.io/inject: "false"
  labels:
    app: mysql-billing

Управление сертификатами

Istio автоматически генерирует и ротирует X.509 сертификаты для каждого Pod. Время жизни сертификата — 24 часа по умолчанию (мы оставили). Ротация прозрачна для приложений.

Для аудита PCI DSS мы настроили DestinationRule с контролем outbound TLS:

# Управление исходящим TLS
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: payment-processor-tls
spec:
  host: payment-processor
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL  # Использовать сертификаты Istio
    portLevelSettings:
      - port:
          number: 8443
        tls:
          mode: ISTIO_MUTUAL

Этап 2: AuthorizationPolicy — микросегментация

mTLS шифрует трафик, но не контролирует, кто может обращаться к какому сервису. Для этого мы внедрили AuthorizationPolicy — правила авторизации на каждом sidecar.

# Политика для payment-processor: доступ только от gateway и reconciliation
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: payment-processor-access
  namespace: payments
spec:
  selector:
    matchLabels:
      app: payment-processor
  action: ALLOW
  rules:
    - from:
        - source:
            principals:
              - "cluster.local/ns/payments/sa/payment-gateway"
              - "cluster.local/ns/payments/sa/reconciliation-svc"
      to:
        - operation:
            methods: ["POST"]
            paths: ["/api/v1/process", "/api/v1/refund"]
    - from:
        - source:
            principals:
              - "cluster.local/ns/monitoring/sa/prometheus"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/metrics"]

Ключевые особенности:

  • Identity-based: авторизация по ServiceAccount, а не по IP-адресу. Это работает правильно при масштабировании и перезапусках Pod-ов
  • Defense-in-depth: политика проверяется на каждом sidecar на пути запроса. Даже при компрометации одного сервиса, он не получит доступ к другим
  • Гранулярность: можно ограничить не только источник, но и HTTP-метод, путь, заголовки и даже JWT-токены

Политика по умолчанию: deny all

Мы установили политику «запрещено всё, что не разрешено явно» на уровне namespace:

# Deny-all по умолчанию для namespace payments
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: payments
spec:
  {}
  # Пустой spec без rules = deny all

После этого каждый сервис получил явную AuthorizationPolicy. Это трудоёмко (мы написали 87 политик), но обеспечивает полную микросегментацию — требование PCI DSS.

Этап 3: Traffic Management и Circuit Breakers

Вторая ключевая задача — устойчивость к каскадным сбоям. Мы настроили DestinationRule с outlier detection и connection pooling для каждого критичного сервиса.

# Circuit breaker для payment-processor
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: payment-processor-resilience
  namespace: payments
spec:
  host: payment-processor
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 50
        http2MaxRequests: 200
        maxRequestsPerConnection: 10
        maxRetries: 3
    outlierDetection:
      consecutive5xxErrors: 3
      interval: 30s
      baseEjectionTime: 60s
      maxEjectionPercent: 50

Как работает outlier detection

Outlier detection — это пассивный health check. Envoy отслеживает ответы каждого endpoint и временно исключает «нездоровые»:

  • consecutive5xxErrors: 3 — после 3 последовательных 5xx ответов endpoint исключается
  • interval: 30s — проверка каждые 30 секунд
  • baseEjectionTime: 60s — минимальное время исключения (увеличивается с каждым повторным исключением)
  • maxEjectionPercent: 50 — не более 50% endpoint-ов может быть исключено одновременно

Важное ограничение: Istio не поддерживает активные health check-и. Endpoint считается здоровым, пока не начнёт отвечать ошибками. Для критичных сервисов мы дополнили outlier detection Kubernetes readiness probes.

Нюанс с maxConnections

Параметр maxConnections задаёт лимит на один sidecar, а не на весь кластер. При 20 инстансах клиентского сервиса, каждый с лимитом 100 — суммарно до 2000 соединений к серверу. Мы столкнулись с этим, когда payment-gateway при автоскейлинге до 40 реплик создал 4000 соединений к payment-processor. Решение — пересчитать лимиты с учётом максимального количества реплик.

Этап 4: Canary Deployments через VirtualService

Istio VirtualService позволяет управлять маршрутизацией трафика на уровне mesh. Мы использовали его для canary-деплоев:

# VirtualService для canary payment-gateway
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: payment-gateway
  namespace: payments
spec:
  hosts:
    - payment-gateway
  http:
    # Canary: 10% трафика на новую версию
    - route:
        - destination:
            host: payment-gateway
          weight: 90
        - destination:
            host: payment-gateway-canary
          weight: 10
      retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: 5xx,reset,connect-failure

Два подхода к canary: какой выбрать

Istio предлагает два способа реализации canary:

ПодходПлюсыМинусы
Отдельный Service + VirtualServiceЧистое разделение, независимый мониторингДублирование манифестов
Subsets в DestinationRuleОдин Service, разделение по labelsСложнее мониторинг, неочевидная конфигурация

Мы выбрали первый подход — отдельный Service для каждой canary-версии. Это проще в эксплуатации и позволяет видеть метрики каждой версии отдельно в Grafana.

Header-based маршрутизация для admin-панели

Для внутренних инструментов мы использовали маршрутизацию по заголовкам:

# Маршрутизация /admin на отдельный сервис
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: api-router
  namespace: payments
spec:
  hosts:
    - api-gateway
  http:
    - match:
        - uri:
            prefix: "/admin"
      route:
        - destination:
            host: admin-service
    - route:
        - destination:
            host: api-gateway

Важный нюанс: VirtualService оперирует на стороне клиента. Правила маршрутизации не применяются к входящим запросам на сервер — только к исходящим от клиента. Это часто вызывает путаницу при настройке.

Производительность: overhead Istio в цифрах

Service mesh добавляет латентность — каждый запрос проходит через два sidecar (на клиенте и на сервере). Мы замерили overhead на кластере ПлатёжСервис:

МетрикаБез IstioС IstioOverhead
Латентность p50 (1 hop)2.1 мс4.5 мс+2.4 мс
Латентность p99 (1 hop)8 мс12 мс+4 мс
Цепочка 4 сервиса12 мс22 мс+10 мс
RAM на Pod (sidecar)060-120 МБ
CPU на Pod (sidecar)050-150m

~2.5 мс на hop — приемлемая цена за mTLS, авторизацию и observability. Для уменьшения overhead в длинных цепочках мы заменили часть синхронных вызовов на асинхронные через Kafka.

Оптимизация Control Plane

С 87 сервисами и ~400 Pod-ами мы столкнулись с высокой нагрузкой на Control Plane — istiod генерировал до 500 Mbps трафика при обновлении конфигурации, рассылая полное состояние кластера всем sidecar-ам.

Решение — Sidecar resource для ограничения видимости каждого namespace:

# Ограничение видимости для namespace payments
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: default
  namespace: payments
spec:
  egress:
    - hosts:
        - "payments/*"       # Свой namespace
        - "istio-system/*"   # Istio infrastructure
        - "kafka/*"          # Kafka namespace
        - "monitoring/*"     # Prometheus, Grafana

Это сократило объём конфигурации, передаваемой каждому sidecar, на 70% и снизило CPU istiod с 4 ядер до 1.5.

Отказоустойчивость mesh: что происходит при сбоях

Мы провели серию экспериментов по отказоустойчивости — критически важно для финтеха:

Сценарий 1: падение sidecar

При crash-е sidecar Envoy — Pod уходит в crash loop. Трафик перенаправляется на другие реплики через Kubernetes Service. Влияние — потеря только текущих запросов этого Pod (единицы).

Сценарий 2: отказ Control Plane (istiod)

Это самый критичный сценарий. Результаты тестирования:

  • Существующие sidecar-ы продолжают работать на закэшированной конфигурации. Трафик не прерывается
  • Новые Pod-ы не получат sidecar — остаются в состоянии Pending (если настроен webhook на always inject). Мы настроили failurePolicy: Ignore для webhook, чтобы Pod-ы запускались без sidecar, а не зависали
  • Удалённые Pod-ы не убираются из endpoint-ов — outlier detection берёт на себя роль health check
  • Новые политики не применяются до восстановления Control Plane
# Настройка webhook для graceful degradation при отказе istiod
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: istio-sidecar-injector
webhooks:
  - name: sidecar-injector.istio.io
    failurePolicy: Ignore  # Не блокировать запуск Pod при отказе istiod
    timeoutSeconds: 5

Сценарий 3: network partition между ДЦ

При потере связности между площадками каждый сегмент mesh продолжает работать автономно. Запросы к сервисам в недоступном сегменте обрабатываются outlier detection и retry policy.

Результаты проекта

После 10 недель внедрения и 3 месяцев эксплуатации результаты проекта для ПлатёжСервис:

МетрикаДо IstioПосле
mTLS покрытие60%100%
Время обнаружения каскадного сбоя5-15 минут30 секунд (автоматически)
Сервисов с circuit breaker12 (ручная реализация)87 (все)
Видимость сетевых путейЧастичная100% (Kiali dashboard)
Время развёртывания canary2-4 часа (ручной процесс)15 минут (VirtualService)
Инцидентов PCI DSS/квартал3-50

Главный результат: ПлатёжСервис прошёл аудит PCI DSS Level 1 с первой попытки — впервые за историю компании. Шифрование трафика, микросегментация и аудит-логи Istio полностью удовлетворили требования аудиторов.

Подробнее о внедрении Service Mesh для финтех-проектов — на itfresh.ru.

Чего Istio не умеет: честный обзор ограничений

Было бы нечестно умолчать об ограничениях, с которыми мы столкнулись:

  • Нет активных health check-ов: только пассивный outlier detection по 5xx ответам. Сервис, который висит и не отвечает — не будет исключён, пока не начнёт возвращать ошибки
  • Не решает задачу canary-деплоя целиком: Istio управляет маршрутизацией трафика, но не создаёт Deployment — вам нужен свой инструмент для создания canary-версий (Argo Rollouts, Flagger)
  • Resource overhead: sidecar потребляет 60-120 МБ RAM и 50-150m CPU на каждый Pod. Для кластера с 400 Pod-ами это ~40 ГБ RAM и ~40 ядер только на sidecar-ы
  • PERMISSIVE mode и server-first протоколы: MySQL, SMTP, FTP требуют исключения из mesh
  • Full state sync: Control Plane рассылает полную конфигурацию при любом изменении. Experimental delta xDS решает проблему, но ещё не готов для production

Несмотря на ограничения, для финтеха с требованиями PCI DSS Istio оказался единственным решением, покрывающим все три задачи (безопасность, устойчивость, наблюдаемость) в одной плоскости управления.

Часто задаваемые вопросы

Да, именно так мы и поступили. Sidecar инжектируется при пересоздании Pod — достаточно добавить label istio-injection=enabled на namespace и выполнить rolling restart. PERMISSIVE mode mTLS позволяет Pod-ам с sidecar и без него общаться без проблем. Переключение на STRICT mode выполняется после инжекции sidecar во все Pod-ы.
В наших замерах overhead составил ~2.5 мс на один hop (через два sidecar — клиентский и серверный). Для цепочки из 4 сервисов это +10 мс. Overhead складывается из: DNAT перехвата (~0.1 мс), TLS handshake (при первом запросе, далее переиспользуется), обработки filter chain (метрики, авторизация), и проксирования запроса. Для большинства приложений это незаметно, но для ultra-low-latency систем стоит оценить заранее.
Все существующие sidecar-ы продолжат работать на закэшированной конфигурации — трафик не прервётся. Однако новые Pod-ы не получат sidecar (или зависнут, если webhook в режиме Fail), изменения политик не применятся, и удалённые endpoint-ы не будут убраны из балансировки. Мы рекомендуем запускать istiod с 2-3 репликами и настроить failurePolicy: Ignore на webhook.
По умолчанию Istio пропускает трафик к внешним IP-адресам через PassthroughCluster. Для контроля можно создать ServiceEntry, описывающий внешний сервис, и применить к нему те же политики (mTLS, retry, circuit breaker). Мы создали ServiceEntry для каждого внешнего платёжного провайдера с настроенными таймаутами и retry.
Для 5-10 сервисов overhead Istio (операционная сложность, ресурсы на sidecar, кривая обучения) обычно не оправдан. Рекомендуем рассмотреть более лёгкие альтернативы: Linkerd (меньше ресурсов, проще в эксплуатации) или решение на уровне приложений (библиотеки retry/circuit breaker + cert-manager для TLS). Istio становится оправданным при 30+ сервисах или при жёстких compliance-требованиях (PCI DSS, SOC 2).

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#istio service mesh#envoy proxy#mTLS kubernetes#istio traffic management#VirtualService DestinationRule#AuthorizationPolicy istio#canary deployment istio#service mesh production
Комментарии 0

Оставить комментарий

загрузка...