Часть IV: Распределённые системы
Глава 18

Service Mesh и инфраструктура

Проблема

Проблема: Микросервисов стало 50+. Каждый пишет retry, таймауты, метрики и TLS самостоятельно. Команды используют разные языки, библиотеки — и всё это надо как-то заставить работать вместе. Service mesh обещает вынести эти заботы в инфраструктурный слой. Но цена — дикая сложность. Когда оно того стоит?


#Что вообще такое service mesh

Представьте, что у вас не 3 микросервиса, а 50. Или 200. Или 1000 (как у Netflix).

Каждый из них должен:

  • Общаться с другими по HTTPS (с проверкой сертификатов)
  • Ретраить запросы при таймаутах
  • Собирать метрики (latency, error rate, request count)
  • Прокидывать trace context
  • Вырезать медленные сервисы (circuit breaker)
  • Понимать, куда вообще стучаться (service discovery)

В мире monoculture (все на Java со Spring Boot или на Go со своим HTTP-клиентом) это решается библиотеками. Одна библиотека — и все микросервисы получают retry, метрики, трассировку.

Но в реальном мире у вас, скорее всего, зоопарк: Python для ML-сервисов, Go для высоконагруженных, Node.js для BFF, Java для core-домена, а может и Rust для чего-то критичного. И каждая команда реализует retry/mTLS/tracing по-своему. Или не реализует.

Вот здесь и появляется service mesh.

Service mesh

Service mesh — это выделенный инфраструктурный слой для межсервисной коммуникации. Он берёт на себя всё, что связано с передачей данных между сервисами, и делает это прозрачно для приложения.

Звучит как магия. По сути — да, магия. Сложная, ресурсоёмкая, но магия.


#Sidecar proxy — базовая единица service mesh

Как работает service mesh технически?

Рядом с каждым экземпляром вашего сервиса запускается sidecar proxy — легковесный прокси-контейнер. Весь входящий и исходящий трафик проходит через этот прокси. Ваше приложение даже не знает, что трафик идёт через посредника — перехват происходит на уровне сети (iptables rules или eBPF).

┌───────────────────────┐
│         Pod           │
│  ┌───────┐ ┌───────┐  │
│  │ App   │ │ Sidecar │ │
│  │ :8080 │◄┼►│ :15001 │ │
│  └───────┘ └───────┘  │
│       ▲        ▲       │
│       │ iptables       │
│       └────────┘       │
└───────────────────────┘

На схеме выше:

  1. Ваше приложение слушает порт :8080
  2. Sidecar (Envoy) слушает порт :15001
  3. iptables rules перенаправляют весь трафик из контейнера через sidecar
  4. Sidecar применяет правила: mTLS, retry, таймауты, маршрутизацию
  5. Трафик уходит к sidecar соседнего сервиса

Ваше приложение ничего не знает про service mesh. Оно просто открывает http://service-b:8080 — а дальше магия.

Паттерн sidecar не нов. По сути, это тот же подход, что и с reverse proxy перед веб-сервером (nginx перед uwsgi). Только теперь прокси перед каждым экземпляром каждого сервиса.

#Data plane и Control plane

Любой service mesh состоит из двух логических частей:

  • Data plane — все sidecar proxy, которые обрабатывают трафик. Они быстрые, тупые и делают только то, что им сказали.
  • Control plane — центральный компонент, который управляет правилами для всех sidecar. Он умный, но медленный — и это нормально, потому что он не участвует в обработке запросов.

Control plane рассылает конфигурацию всем sidecar. Sidecar применяют её и обрабатывают трафик. Если control plane упал — трафик продолжает ходить по последней известной конфигурации. Service mesh деградирует, но не падает.


#Istio vs Linkerd vs Consul Connect

Три главных игрока на рынке service mesh. У каждого — своя философия и цена.

#Istio — самый мощный, самый тяжёлый

Istio — это «швейцарский нож» service mesh'ей. Делает всё. И даже больше.

Data plane: Envoy Proxy (C++)\ Control plane: istiod (Go) — объединяет Pilot, Citadel, Galley в один компонент\ Сложность: высокая\ Ресурсы: ~150-300 MB RAM на sidecar, ~0.5-1 vCPU в покое

Что умеет:

  • mTLS с автоматической ротацией сертификатов
  • Traffic splitting (канарейки, A/B, mirroring)
  • Circuit breaking, retry, timeout — все паттерны resilience
  • Fault injection (chaos testing «из коробки»)
  • Traffic mirroring (shadow трафик на новую версию)
  • Встроенные метрики (Prometheus), трассировка (Jaeger/Zipkin), observability (Kiali)
  • Authorization policies (RBAC на уровне mesh)
  • Внешний ingress/egress gateway

Пример конфигурации VirtualService для канареечного деплоя:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: payments-vs
spec:
  hosts:
  - payments
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: payments
        subset: v2
  - route:
    - destination:
        host: payments
        subset: v1
      weight: 90
    - destination:
        host: payments
        subset: v2
      weight: 10

Этот конфиг говорит:

  • Если в запросе есть заголовок X-Canary: true — направляй на v2 (A/B тест)
  • Иначе — 10% трафика на v2 (канарейка), 90% на v1

Без service mesh вам пришлось бы реализовывать эту логику в каждом сервисе. С Istio — это просто yaml-файл.

Проблема Istio: он монструозен. Установка — это десятки CRD (Custom Resource Definitions), куча сервисов, сложный control plane. Istio — это проект, который требует выделенного инженера (или команды) для поддержки.

#Linkerd — «Istio на минималках»

Linkerd родился из идеи: «А давайте сделаем service mesh, который не требует недели на установку и не ест половину кластера?».

Data plane: linkerd2-proxy (Rust)\ Control plane: Go\ Сложность: средняя\ Ресурсы: ~10-20 MB RAM на sidecar, ~0.1 vCPU

Linkerd принципиально проще Istio:

  • Меньше CRD, меньше абстракций
  • Data plane на Rust — намного легче и быстрее Envoy
  • Не поддерживает Envoy — использует свой прокси
  • Нет встроенного ingress gateway
  • Меньше возможностей для тонкой настройки

Что вы получаете взамен за «меньше»? Простоту. Linkerd можно установить за 5 минут (если у вас уже есть Kubernetes). Istio — за час, если всё идёт по плану, и за день, если что-то пошло не так.

# Установка Linkerd
linkerd install | kubectl apply -f -

# Проверка
linkerd check

# Добавление sidecar к сервису
kubectl get deploy/webapp -o yaml | linkerd inject - | kubectl apply -f -

Linkerd — хороший выбор, если вам нужно 80% возможностей Istio с 20% сложности. Особенно если у вас нет выделенного SRE для service mesh.

#Consul Connect — если вы уже на Consul

Consul от HashiCorp изначально был service discovery. Но со временем они добавили service mesh.

Data plane: Envoy (опционально, можно без proxy — built-in proxy)\ Control plane: Consul Server\ Сложность: средняя (если Consul уже есть)\ Ресурсы: зависит от data plane

Фишка Consul Connect в том, что он не привязан к Kubernetes. Он работает с номад-кластерами, виртуалками, bare metal. Если у вас гибридная инфраструктура — Consul Connect ваш выбор.

Минус: вы привязываетесь к экосистеме HashiCorp. Consul, Vault, Nomad — это мощно, но это замкнутый мир.

#Decision matrix

Критерий Istio Linkerd Consul Connect
Сложность установки 🔴 Высокая 🟢 Низкая 🟡 Средняя
Потребление ресурсов 🔴 ~300 MB 🟢 ~20 MB 🟡 ~100 MB
Гибкость конфигурации 🟢 Полная 🟡 Средняя 🟡 Средняя
Traffic management 🟢 VirtualService, Mirroring 🟡 Weighted routing 🟡 Service router
mTLS 🟢 Auto + rotation 🟢 Auto + rotation 🟢 Auto + rotation
Observability 🟢 Metrics + Tracing + Kiali 🟢 Metrics + Tap 🟡 Metrics
Kubernetes-native 🟢 Да 🟢 Да 🟡 Не только
Multi-cluster 🟢 Да (Primary-Remote) 🟢 Да 🟢 Да (WAN Federation)
Сообщество 🟢 CNCF, Google 🟢 CNCF, Buoyant 🟡 HashiCorp
Learning curve 🔴 Крутая 🟡 Умеренная 🟡 Умеренная

Вывод: Istio — для больших, сложных систем с SRE-командой. Linkerd — если хотите service mesh без головной боли. Consul Connect — если у вас уже HashiCorp-стек или не-Kubernetes инфраструктура.


#Когда service mesh — overkill

Я видел проекты, где service mesh внедряли «на вырост». На 3 микросервиса в Kubernetes. Ребята, серьёзно?

Вот признаки того, что service mesh вам не нужен:

1. Меньше 10-20 сервисов

Да, число условное. Но если у вас меньше десяти сервисов, complexity budget (см. Главу 5 «Балансируем сложность») лучше потратить на что-то другое. Retry и таймауты вы напишете в одном HTTP-клиенте за день.

2. Все сервисы на одном языке с одной библиотекой

Если у вас всё на Java со Spring Boot/WebFlux — используйте Resilience4j, Spring Cloud и OpenFeign. Это даст 80% функциональности service mesh без операционной нагрузки.

Go с net/http и middleware-цепочками даст то же самое.

3. Нет требований к mTLS

Многие проекты живут внутри VPN/VPC и не шифруют трафик между сервисами. Это ок для внутренних сетей (хоть и не best practice). Если compliance не требует mTLS, service mesh становится менее нужным.

4. Маленькая команда, нет SRE

Service mesh не администрируется сам. Кто-то должен:

  • Обновлять control plane
  • Мониторить health sidecar
  • Отлаживать проблемы с трафиком («почему запрос идёт не туда?»)
  • Чинить конфиги после обновления Kubernetes

Если этот «кто-то» — вы, и у вас ещё есть фичи, которые надо писать — подумайте дважды.

5. Простота важнее гибкости

Service решает problems at scale, но добавляет complexity. Если ваша система простая (или вы хотите, чтобы она была простой) — service mesh будет минусом, а не плюсом.

Мой совет

Мой совет: Не внедряйте service mesh «на вырост». Внедряйте, когда почувствуете боль. Когда retry в библиотеке перестал справляться. Когда настройка mTLS для каждого сервиса стала рутиной на неделю. Когда канареечные деплои превратились в ад из костылей.


#Service mesh и API Gateway: не одно и то же

Одна из самых частых ошибок — путать service mesh и API Gateway. Это разные вещи, которые решают разные проблемы.

API Gateway (Kong, Ambassador, AWS API Gateway, Zuul) — это точка входа из внешнего мира. Он обрабатывает трафик север-юг (north-south): от клиента к сервисам. API Gateway занимается аутентификацией, rate limiting, трансформацией запросов, агрегацией.

Service mesh — это слой для внутреннего трафика восток-запад (east-west): между сервисами. Он занимается mTLS, retry, observability внутри системы.

        Интернет
           │
    ┌──────▼──────┐
    │ API Gateway │  ← north-south (внешний трафик)
    └──────┬──────┘
           │
    ┌──────▼──────┐
    │  Service Mesh│  ← east-west (внутренний трафик)
    └──────┬──────┘
           │
    ┌──▼──┐ ┌──▼──┐ ┌──▼──┐
    │Svc A│ │Svc B│ │Svc C│
    └─────┘ └─────┘ └─────┘

Часто API Gateway сам является частью service mesh (как, например, Istio Ingress Gateway) — то есть он тоже использует sidecar proxy, но стоит на границе кластера.

Компромисс: Можно ли заменить API Gateway service mesh? Нет, потому что API Gateway решает прикладные задачи (аутентификация, трансформация), а service mesh — инфраструктурные. Можно ли заменить service mesh API Gateway? Тоже нет.

На практике они отлично дополняют друг друга:

  • API Gateway аутентифицирует пользователя и проксирует запрос к сервису
  • Service mesh обеспечивает безопасную и надёжную доставку запроса до нужного экземпляра сервиса

#mTLS — шифрование без боли

Одна из самых недооценённых фич service mesh — mutual TLS (взаимный TLS).

В обычном TLS только клиент проверяет сертификат сервера. В mTLS — обе стороны проверяют сертификаты друг друга. Сервис A не просто убеждается, что сервис B — это настоящий сервис B. Сервис B тоже проверяет, что сервис A — это настоящий сервис A.

Без service mesh mTLS выглядит так:

  1. Сгенерировать CA-сертификат
  2. Для каждого сервиса — сгенерировать client-cert и server-cert
  3. Настроить ротацию сертификатов (раз в N дней)
  4. Включить mTLS в HTTP-сервере и HTTP-клиенте каждого сервиса
  5. Настроить distribution сертификатов (Vault? Secret Manager? GitOps?)
  6. Не забыть обновить пайплайн при добавлении нового сервиса
  7. Повторить для каждого окружения (dev, staging, prod)

На 50 сервисах это превращается в проект на месяц.

С service mesh:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: payments
spec:
  mtls:
    mode: STRICT  # STRICT — только mTLS, PERMISSIVE — mTLS или plaintext

Один ресурс — и весь namespace payments общается через mTLS. Сертификаты генерируются, распределяются и ротируются автоматически. Каждый sidecar получает сертификат от Certificate Authority в control plane.

#Как это работает внутри

  1. Sidecar сервиса A хочет отправить запрос к сервису B
  2. Sidecar A устанавливает mTLS-соединение с sidecar B
  3. Оба предъявляют сертификаты, подписанные общим CA
  4. После handshake'а трафик шифруется
  5. Сертификаты автоматически ротируются (обычно каждые 24 часа)

Ваше приложение об этом ничего не знает. Для него это обычный HTTP-запрос (или gRPC). Шифрование происходит на уровне sidecar'ов, не на уровне приложения.

#Strict vs Permissive

  • STRICT mode — весь трафик между сервисами должен быть mTLS. Если кто-то шлёт plain HTTP — соединение отклоняется.
  • PERMISSIVE mode — service mesh принимает и mTLS, и plain HTTP. Нужен для постепенной миграции, когда не все сервисы ещё в mesh.

Начинайте с PERMISSIVE, постепенно переводите на STRICT по мере включения сервисов в mesh.


#Traffic management — канарейки, A/B, mirroring

Одна из killer-фич service mesh — управление трафиком без изменения кода приложения.

#Weighted routing (канареечные деплои)

Классический сценарий: выкатили новую версию сервиса, хотим пустить на неё 5% трафика, убедиться что всё ок, и раскатить на всех.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: payments
spec:
  hosts:
  - payments
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: payments
        subset: v2
  - route:
    - destination:
        host: payments
        subset: v1
      weight: 95
    - destination:
        host: payments
        subset: v2
      weight: 5

Что здесь происходит:

  • 5% трафика идёт на v2 (канарейка)
  • Если есть заголовок X-Canary: true — 100% на v2 (для внутреннего тестирования)
  • Остальное — на v1 (стабильная версия)

Постепенно увеличиваем вес v2: 5% → 25% → 50% → 100%. Без передеплоя, без изменения кода.

#Header-based routing (A/B тесты)

Хотите показать новую платёжную форму только пользователям из Chrome и посмотреть на конверсию?

- match:
    - headers:
        user-agent:
          regex: '.*Chrome.*'
  route:
    - destination:
        host: payments
        subset: v2-experiment

Service mesh направит трафик от Chrome на экспериментальную версию. Остальные браузеры — на стабильную. Для A/B теста нужно лишь добавить заголовок или cookie.

#Traffic mirroring (shadow трафик)

Самый крутой (и опасный) режим — mirroring. Запрос отправляется на обе версии, но ответ клиенту возвращается только от v1. v2 получает копию трафика «втёмную». Проверяете, как новая версия обрабатывает запросы, не рискуя продакшеном.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: payments-mirror
spec:
  hosts:
  - payments
  http:
  - route:
    - destination:
        host: payments
        subset: v1
    mirror:
      host: payments
      subset: v2-mirror
    mirrorPercent: 10

10% запросов дублируются на v2. v1 возвращает ответ как обычно. v2 обрабатывает запрос, но никто не ждёт от неё ответа.

Внимание: mirroring удваивает нагрузку. Если v1 обрабатывает 1000 RPS, v2 получит ещё 100 RPS. Убедитесь, что v2 выдержит.


#Анти-паттерны: как убить систему service mesh'ем

#1. Service mesh ради service mesh

Самый популярный анти-паттерн. Внедрить Istio «потому что все внедряют» — верный способ потратить месяц на установку и ещё месяц на отладку, не получив никакой бизнес-ценности.

Симптомы:

  • «Нам нужен service mesh» — «Зачем?» — «Ну, это современно»
  • После внедрения никто не использует mTLS, канарейки или observability
  • Команда боится трогать конфиги service mesh

Лечение: Начните с проблемы, а не с инструмента. Если нет боли — не лечите.

#2. Доверие к service mesh по умолчанию

Service mesh — это софт. В нём есть баги. И эти баги могут положить всю вашу коммуникацию.

Реальная история: В 2019 году Istio выпустил релиз 1.1 с багом, который при определённых условиях вызывал циклический редирект — трафик ходил по кругу между sidecar'ами, пока не упирался в таймаут. Несколько крупных продакшенов легли, потому что все обновились «потому что новая версия».

Что делать:

  • Не включайте STRICT mTLS с первого дня
  • Тестируйте обновления service mesh на staging
  • Держите документацию, как откатить service mesh или отключить sidecar
  • Имейте rollback plan

#3. Настройка через «copy-paste из документации»

Service mesh — сложная система. Каждый проект — уникальный. Copy-paste конфигов без понимания того, что они делают, — верный способ получить неделю debugging без сна.

Пример из жизни: Команда скопировала конфиг VirtualService с mirroring'ом 100% трафика на обе версии. Случайно. Нагрузка на сервис выросла вдвое. База не выдержала. Production упал.

Что делать: Каждый конфиг должен сопровождаться пониманием: «что этот параметр делает, зачем он здесь, что будет если его убрать».

#4. Service mesh без observability

Вы установили Istio, потратили две недели на настройку, но не настроили метрики и дашборды. Трафик идёт через sidecar, но вы не видите, как.

Service mesh без observability — это чёрный ящик. Вы знаете, что что-то идёт через прокси, но не знаете, с какими ошибками, задержками и потерями пакетов.

Обязательный минимум:

  • Prometheus метрики с sidecar (request count, latency, error rate)
  • Grafana дашборды для сервисов и mesh
  • Распределённая трассировка (Jaeger, Zipkin)
  • Kiali (для Istio) — визуализация графа сервисов

#5. Непоследовательный rollout

Включить mTLS сразу на весь кластер в STRICT mode — хороший способ положить всю коммуникацию, если хотя бы один сервис не в mesh.

Как надо:

  1. Начните с PERMISSIVE mode
  2. Подключайте сервисы постепенно
  3. Убедитесь, что все сервисы в mesh
  4. Переключитесь на STRICT

#Кейс: как одна компания внедряла Istio и пожалела

[Anecdotal] Я работал с командой, которая решила «сделать красиво». У них было ~30 микросервисов, Kubernetes, Go + Python стек. Они прочитали про Istio, вдохновились и решили внедрить.

Что пошло не так:

  • Установка заняла 3 недели вместо запланированных 2 дней. Istio на тот момент (версия 1.0) требовал сложной настройки cert-manager, webhook'ов, RBAC. Документация была скудной, много dead-end туториалов.
  • Потребление ресурсов. Они не закладывали ресурсы на sidecar. Envoy жрал по 256 MB на под. На 30 подах — это 7.5 GB RAM только на прокси. Пришлось срочно увеличивать кластер.
  • Проблемы с совместимостью. Их самописный service discovery конфликтовал с DNS-резолвингом Istio. Два дня ушло на отладку: «почему запросы уходят не туда».
  • Никто не знал, как это дебажить. При проблемах с коммуникацией никто не понимал, смотреть ли в логи приложения, sidecar'а или control plane. Добавилась прослойка, которая скрыла ошибки.
  • Не использовали 80% фич. Через полгода они использовали только mTLS и базовые метрики. Канарейки не настраивали — не было бизнес-потребности. Circuit breaker оказался лишним — ретраев на уровне библиотеки хватало.

Вывод команды: «Если бы мы делали это снова — мы бы не ставили Istio. Мы бы взяли Linkerd или вообще обошлись библиотеками. Мы переплатили операционной сложностью за фичи, которые нам не нужны».


#Pro tip: Service mesh — это сложность. Убедитесь, что она оправдана

Я не хочу сказать, что service mesh — это зло. Это мощный инструмент, который решает реальные проблемы. Но он решает их на определённом масштабе.

Подумайте о service mesh как о вертолёте:

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

Service mesh — это вертолёт. Не покупайте его для поездки в магазин за хлебом.

Вот когда он оправдан:

  • 50+ микросервисов на разных языках — каждая команда не пишет retry/mTLS/tracing заново
  • Строгие требования к безопасности (fintech, healthcare) — mTLS для каждого соединения без изменения кода
  • Высокая частота деплоев — канареечные деплои и A/B тесты как стандартный процесс без костылей
  • Выделенная SRE-команда — кто-то должен администрировать этот зоопарк
  • Разнородный стек — Java, Go, Python, Node.js — и каждому нужны retry, mTLS и метрики

Вот когда не оправдан:

  • Меньше 15-20 сервисов
  • Единый язык с богатой библиотекой (Spring Cloud, Go kit, Finagle)
  • Нет SRE-команды
  • Нет compliance-требований к mTLS
  • Простота операций важнее всех остальных качеств

#Чек-лист для самопроверки

  • У нас есть реальная боль в межсервисной коммуникации (не «на вырост»)?
  • Мы попробовали решить проблемы библиотеками (Resilience4j, Go middleware)?
  • Мы понимаем, какую конкретно проблему решает service mesh в нашем контексте?
  • У нас есть человек/команда, которая будет администрировать service mesh?
  • Мы выбрали решение под наши потребности (Linkerd если просто, Istio если мощно, Consul если не Kubernetes)?
  • Мы заложили ресурсы на sidecar (CPU, RAM, latency overhead)?
  • У нас настроен мониторинг и дашборды для service mesh?
  • У нас есть rollback plan (как отключить service mesh, если что-то пошло не так)?
  • Мы используем service mesh не для всего, а для проблем, которые он решает?
  • Мы настроили mTLS постепенно (PERMISSIVE → STRICT), не одним днём?

#Ключевые выводы

  1. Service mesh выносит межсервисную коммуникацию из библиотек в инфраструктуру. Это гениально для разнородных стеков и убийственно для маленьких систем.

  2. Sidecar proxy — основа service mesh. Прозрачный перехват трафика через iptables/eBPF, без изменения кода приложения.

  3. Istio — мощный и тяжёлый, Linkerd — лёгкий и простой, Consul Connect — для не-Kubernetes. Выбор определяется вашим контекстом.

  4. Service mesh не заменяет API Gateway. Они решают разные проблемы: mesh — east-west (внутренний трафик), gateway — north-south (внешний).

  5. mTLS, traffic management, observability — killer-фичи service mesh. Но они не бесплатны: каждая фича добавляет сложность, latency и потребление ресурсов.

  6. Не внедряйте service mesh «на вырост». Внедряйте, когда почувствуете боль. Боль от retry, от mTLS, от канареек без костылей. Complexity budget — конечный ресурс, тратьте его с умом.


#Ссылки на другие главы

  • Глава 5: «Балансируем сложность» — о complexity budget и когда усложнение оправдано
  • Глава 10: «Observability» — о метриках, трассировке и логировании
  • Глава 14: «Дорога в распределённый мир» — когда вам вообще понадобятся микросервисы
  • Глава 15: «Микросервисы — правильный способ» — о паттернах межсервисной коммуникации
  • Глава 24: «Resilience и Chaos Engineering» — circuit breaker, bulkhead и другие паттерны устойчивости
  • Глава 27: «Developer Platform и IDP» — как service mesh вписывается в платформенный подход