Статья Service Mesh: ошибки конфигурации

1770981328045.webp
Знаешь, когда я слышу фразу «Мы поставили Istio, и у нас всё стало работать быстрее», я чувствую ту же боль, что и при фразе «Я перешёл на макбук и наконец-то начал писать чистый код». Или когда мне говорят: «Service Mesh решит все наши проблемы с сетью». В этот момент где-то плачет маленький сетевой инженер, потому что он знает: проблемы с сетью никогда не решаются добавлением ещё одного слоя абстракции. Они решаются пониманием того, как работают пакеты, TCP-стеки и приложения. Но маркетинг облачных технологий сделал своё чёрное дело - теперь каждый второй стартап считает, что без Istio они не enterprise, а без Linkerd их микросервисы умрут от одиночества.

Откуда ноги растут

Помнишь времена, когда мы просто поднимали nginx на хосте, проксировали запросы на несколько бэкендов и жили счастливо? Тогда единственным «service mesh» был iptables, который мы ненавидели, но уважали. Потом пришли Docker и Kubernetes, и вдруг оказалось, что подов много, они умирают и рождаются, а IP-адреса меняются быстрее, чем девушка у тимлида после демо. Люди запаниковали и придумали sidecar-прокси. Мол, пусть каждый под имеет своего маленького помощника, который будет балансировать трафик, собирать метрики и делать mTLS. Звучало как спасение.

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

Почему mesh - это налог, а не благословение

Я хочу, чтобы ты понял одну важную вещь. Service Mesh - это не функция, которую можно включить и забыть. Это распределённая система, которая живёт своей жизнью. Каждый sidecar - это маленький сервер, который принимает и отправляет трафик, поддерживает соединения, применяет политики. Если у тебя 100 подов, то у тебя 100 прокси, которые общаются друг с другом, с контрол-плейном и с API Kubernetes. Это 100 точек отказа, 100 источников задержки, 100 потребителей памяти.

И вся эта сложность оправдана только в одном случае: когда ты действительно не можешь жить без mTLS и тонкой маршрутизации. Для 90% проектов достаточно обычного Kubernetes Service с ingress-контроллером. Но маркетинг толкает людей на подвиги, и они ставят mesh «на всякий случай». А потом просыпаются в три часа ночи от того, что кластер лёг.

Что будет в этой статье (и почему это не очередной мануал)

Я не собираюсь рассказывать, как установить Istio с помощью istioctl install. Этого добра полно в официальной документации и в тысяче туториалов на Medium, которые копируют друг у друга одни и те же команды. Я расскажу о том, о чём молчат авторы этих туториалов. О граблях, на которые наступил сам и которые видел у коллег. О том, как не надо делать, потому что «так написано в блоге».

Мы разберём:
  • Почему ретраи - это чаще зло, чем добро.
  • Как таймауты могут убить производительность.
  • Почему mTLS не гарантирует шифрования.
  • Как Envoy теряет память и что с этим делать.
  • Когда Linkerd проще, но не лучше.
  • Как отлаживать прокси, когда дашборды врут.
  • И многое другое, что не влезет в 20 тысяч слов, но я постараюсь.
Если какое-то решение - костыль, я скажу, что это костыль. Если я не знаю ответа - я скажу «я не знаю» и предложу покопать вместе. Если где-то есть компромисс - я объясню его цену. В мире, где каждый второй пост - это реклама вендора или попытка продать курс, честность стоит дорого.

Ты и я - мы в одной лодке. Ты читаешь этот текст, потому что уже столкнулся или боишься столкнуться с проблемами. Я пишу этот текст, потому что хочу, чтобы ты прошёл этот путь быстрее меня. Чтобы ты не тратил недели на то, что можно понять за час. Чтобы ты чувствовал, что за твоей спиной есть кто-то, кто уже выкурил этот баг и может сказать: «Смотри, тут всё просто, просто замени этот параметр».

Пристегнись

Мы начинаем. Возьми клавиатуру, открой терминал, приготовься гуглить и ругаться. Впереди много кода, много логов и много правды. И помни: если ты не ошибаешься в конфигах, ты не набираешься опыта. Ошибайся, но на тестовых кластерах. Или читай эту статью и ошибайся меньше.

Поехали.


ИНСТРУМЕНТАРИЙ ХАКЕРА: ЧЕМ БУДЕМ ВСКРЫВАТЬ?​

Прежде чем мы полезем в конфиги, давай договоримся об инструментах. Без них ты будешь слепым котёнком в микроволновке.
  1. tcpdump + wireshark: Не верь дашбордам. Верь пакетам.
    kubectl exec -it pod-name -c istio-proxy -- tcpdump -i eth0 -w - | wireshark -k -i -
    (Если ты не знаешь эту команду - закрой пост и иди учить матчасть. Я подожду.)
  2. Istioctl / Linkerd viz:
    • istioctl proxy-status - покажет, кто из прокси ушёл в астрал.
    • istioctl proxy-config - золотая жила. clusters, endpoints, routes. Envoy хранит всё, что думает о тебе.
    • linkerd viz tap - лучшее, что случилось с отладкой после изобретения кофе. Тыкай и смотри, кто кому шлёт 503.
  3. Fortio / Vegeta: Забудь про ping. Нам нужна нагрузка под микроскопом.
    fortio load -c 8 -qps 500 -t 30s
  4. Kiali: Только если нужно быстро показать менеджеру, что «вот тут красная линия». Для реальной отладки - GUI тормозит мозг.
Мы будем использовать эти инструменты прямо в статье, на конкретных примерах.


ЧАСТЬ 1: ВЕЛИКОЕ ЗАБЛУЖДЕНИЕ О «ZERO TOLLERANCE» (нет, не zero trust, речь про таймауты)​

Кейс 1.1: Когда retry убивает твой кластер быстрее, чем DDoS​

Самый сладкий баг, который я видел в каждом втором стартапе.

Симптомы: Сервис А обращается к сервису Б. Внезапно сервис Б начинает тормозить (долгий GC, переезд ноды). В нормальном мире ты бы получил пачку 504/503, клиент бы обиделся и ушёл. Но у нас же Service Mesh! Умный!

Конфигурация (Istio):

YAML:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: svc-b-retry
spec:
  hosts:
  - svc-b
  http:
  - route:
    - destination:
        host: svc-b
    retries:
      attempts: 5
      perTryTimeout: 1s


Выглядит безобидно? НЕТ. Это атомная бомба.

Почему это ****** (извините, техническая неточность):
Допустим, у тебя 1000 RPS летит в под, который уже в состоянии Terminating или просто захлебнулся.
  1. Запрос пришёл. 1 секунда - нет ответа.
  2. Envoy (Istio) делает повтор.
  3. Ещё 1 секунда. Нет ответа.
  4. Ещё повтор. Ещё секунда.
  5. 5 попыток * 1000 RPS = 5000 RPS генерируется твоим же прокси.
Ты умножил нагрузку на сбойный сервис в 5 раз. Вместо того чтобы дать ему отдохнуть, ты доколачиваешь его лопатой.

Ирония судьбы: Разработчики ставят retries, чтобы повысить надёжность. По факту они создают машину положительной обратной связи, которая валит прод быстрее, чем rm -rf /.

Как надо:
Мы понимаем, почему ты это сделал. Ты хотел как лучше. Но давай включим голову.
  • Ограничь количество попыток. 2 - максимум. Третий раз уже не помог.
  • Используй baseRetryDelay и maxRetryDelay. Не долби подряд.
  • Самое важное: НЕ ставь retry на идемпотентные методы POST/PUT. Ты задвоишь платежи. Envoy не знает, идемпотентный у тебя запрос или нет. Он просто тупой грузчик.
Проверка инструментом:
istioctl pc routes deploy/svc-b --name 8080 -o json | jq '.[] | .retryPolicy'
Посмотри. Если видишь retryOn: 5xx и numRetries: 5 - ты в опасной зоне.


Кейс 1.2: Таймауты. Никто не умеет их считать.​

«А давай поставим таймаут 10 секунд, чтобы точно успело!».
Итог: Соединения висят, файловые дескрипторы жрут память, Envoy падает по OOM.

Linkerd-специфичная боль:
Linkerd, в отличие от Istio, не даёт тебе так просто настраивать таймауты на стороне клиента (в service profile). И люди просто их не ставят.

Твой бэкенд должен отвечать за 500ms. Если он не отвечает за 500ms - у тебя проблема в коде, а не в сети. Таймаут в 30 секунд - это не «надёжность», это «откладывание факапа».

«Но у нас тяжёлые отчёты считаются 20 секунд!»
Ответ: Вынеси это в асинхрон. Отдавай 202 Accepted и дай клиенту дёргать ручку статуса. Не заставляй HTTP-connection висеть как сопля на морозе.
Практика (Istio):

YAML:
http:
- timeout: 1.5s  # Жестко. Если не уложился - пробуй снова (см. кейс 1, но осторожно).
  retries:
    attempts: 1


ЧАСТЬ 2: МЁРТВЫЕ ЭНДПОИНТЫ И ЗОМБИ-ПОДЫ​

Кейс 2.1: Ты убил под, а Envoy всё ещё шлёт туда трафик​

Классика. Делаешь kubectl delete pod, под уходит в Terminating. Но трафик всё ещё идёт. Почему?

Краткий ликбез:
Envoy -умный. Он слушает API-сервер Kubernetes через Istiod (или Linkerd контроллер). Но между моментом, когда kubelet сказал «под умер» и моментом, когда Envoy обновил свой endpoint list -проходит время. Это называется eventual consistency. Иногда это время - 5 секунд. Иногда - 30.

Симптомы: 503 ошибки во время деплоя. Разработчик пишет в чат: «У нас всё крашнулось при рестарте!». SRE чешет репу.

Решение (неочевидное):
  1. Readiness Probe. Проверь, что твой сервис отвечает 200. Если он в статусе Terminating, kube-proxy (и mesh) должны его выкинуть.
  2. Istio: podDisruptionBudget. Защити себя. Не дай удалить больше 1 пода за раз.
  3. Хакерский лайфхак: PreStop hook.
Загугли, как он выглядит стандартно. А теперь сделай правильно:

YAML:
lifecycle:
  preStop:
    exec:
      command:
      - /bin/sh
      - -c
      - "sleep 15 && curl -X POST http://localhost:15020/quitquitquit"

(15020 - порт pilot-agent в Istio, который говорит Envoy'ю: «всё, брат, глуши моторы»).

Почему sleep 15? Потому что мы даём время контроллеру входа (Ingress Gateway) выкинуть этот под из балансировки. Без слипа - Envoy убивается мгновенно, а TCP-соединения обрываются на полуслове.

Я знаю, ты хочешь верить, что это «костыль». Да, это костыль. Это грязный хак. Но это работает. А Kubernetes - это вообще большой конструктор костылей. Привыкай.


ЧАСТЬ 3: LINKERD - «ПРОСТОТА» ХУЖЕ ВОРОВСТВА​

Я люблю Linkerd за его легкость и «просто работает». Но есть нюанс.

Кейс 3.1: mTLS есть, а шифрования нет?​

Linkerd по умолчанию включает взаимный TLS. Это круто. Но люди видят "srv-identity" в дашборде и думают, что трафик зашифрован и всё пучком.

Правда: Linkerd шифрует трафик только между прокси. Если у тебя есть Pod A (с прокси) и Pod B (с прокси) - да, трафик шифруется. Если Pod B упал, а трафик идёт напрямую к нему, минуя прокси (бывает при кривых NetworkPolicy) - трафик идёт plain text.

Как проверить:
linkerd viz edges deployment
Смотри колонку TLS. Если видишь disabled или not provided by remote - значит где-то дыра. Или старый pod, или кривая конфигурация.

Кейс 3.2: Service Profiles. Ты их не ставишь. И зря.​

Istio даёт VirtualService для тотального контроля. Linkerd даёт ServiceProfile - более легковесный, но с ограничениями.

Ошибка: Люди ставят Linkerd и думают «а, профили - это для продвинутых, мы пока обойдемся».
Результат: Нет таймаутов, нет retry (которые Linkerd умеет делать на стороне клиента), нет метрик по роутам.

ServiceProfile в Linkerd -это уебанский интерфейс (прости господи). Он не умеет делать сложную маршрутизацию. Но он умеет rate limiting и retries. И это может спасти тебе задницу.

Практика:
Даже если у тебя монолит - накинь ServiceProfile с дефолтными таймаутами.

YAML:
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: my-api.default.svc.cluster.local
  namespace: default
spec:
  routes:
  - name: POST /api/payment
    condition:
      method: POST
      pathRegex: /api/payment
    isRetryable: true  # ОСТОРОЖНО (см. часть 1)
    timeout: 500ms

Ты сразу увидишь в графане, где твои тормоза.


ЧАСТЬ 4: ENVOY - ЧЕРНЫЙ ЯЩИК С ОТВЕРТКОЙ​

Кейс 4.1: Memory Leak. Envoy жрет память как не в себя.​

Ситуация: Кластер работает неделю. Вдруг ноды начинают сыпаться по OOM. Смотришь в top - istio-proxy жрет 2GB RAM.

Вердикт: Скорее всего, это твои дефолтные настройки listener и connection pool.

Дефолты Istio (раньше):
  • maxConnections: 1024 (для HTTP/1.1)
  • connectTimeout: 10s
Если у тебя микросервис, который общается с 50 другими сервисами, Envoy создаёт кучу соединений. Они висят в TIME_WAIT или просто открыты.

Решение:

YAML:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: svc-b-connection-pool
spec:
  host: svc-b
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 10  # Жестко. Хватит.
        connectTimeout: 30ms
      http:
        http1MaxPendingRequests: 10
        http2MaxRequests: 50
        maxRequestsPerConnection: 5  # Закрывай соединение после 5 запросов, не держи вечность.
    outlierDetection:  # Это отдельная любовь.
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 60s

Outlier Detection - это «выкидывай плохие поды из балансировки». Если под постоянно падает с 500-ми ошибками, Envoy перестанет слать туда трафик на 60 секунд. Это лекарство от ретраев. Серьезно, включи это везде.


Кейс 4.2: 404 на​

Классика. Заходишь в под, curl localhost:15000/help (админка Envoy). Смотришь config_dump. Видишь, что listener на порту 8080 есть, но route_config указывает на виртуальный хост, который не совпадает с твоим Host header.

Диагноз: Envoy маршрутизирует по host:port. Если ты в запросе указал Host: svc-b.ns.svc.cluster.local, а в VirtualService указал просто svc-b - Envoy может не понять.

Ирония: «Но в Kubernetes это одно и то же!». Для DNS - да. Для Envoy - нет. Envoy сравнивает строки.

Решение:
Либо используй FQDN везде:

YAML:
hosts:
- svc-b.default.svc.cluster.local

Либо используй * (но осторожно).


ЧАСТЬ 5: СТАТИСТИКА. ПОЧЕМУ ТЫ НЕ ВИДИШЬ 404 В KIALI?​

Кейс 5.1: Метрики врут.

Istio экспортит метрики в Prometheus. Стандартный набор (istio_requests_total) содержит коды ответов.

Но: Если у тебя балансировка на стороне клиента (Envoy) решила, что под умер, и отдаёт 503 до того, как запрос ушёл на апстрим - это не попадёт в лог апстрима. Ты будешь видеть 503 в дашборде клиента, но разработчик бэкенда будет клясться, что у него 100% успешных.

При отладке всегда смотри метрики с двух сторон. destination_service и source_service. Не верь одной панели.

Инструмент:

istioctl proxy-config log deploy/svc-b --level debug

Врубаешь debug-логи в прокси. Да, он начнёт писать мегабайты логов. Да, это больно. Но ты увидишь, почему Envoy вернул 503: cluster_not_found, no_healthy_upstream или upstream_reset_before_response.


ЧАСТЬ 6: БЕЗОПАСНОСТЬ. AUTHORIZATION POLICY КАК СПОСОБ ЗАБЛОКИРОВАТЬ САМОГО СЕБЯ​

Кейс 6.1: AuthorizationPolicy с пустым action: ALLOW.

Новички думают: «Я напишу политику, которая разрешает доступ только от сервиса А к сервису Б».

YAML:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-svc-a-to-svc-b
spec:
  selector:
    matchLabels:
      app: svc-b
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/svc-a-sa"]

Что происходит на самом деле:
В Istio политики по умолчанию ALLOW, если нет DENY. Но как только ты создаёшь хоть одну ALLOW-политику - всё, что не попало под неё, становится DENY.
Ты разрешил доступ от svc-a. А мониторинг? А прометей? А сам Kiali? Он теперь не может стучаться в /stats пода. Всё упало.

Мы все через это проходили. Не казни себя. Просто помни: action: ALLOW в Istio - это эксклюзивный клуб. Никого посторонних.

Как надо:
Либо используй DENY политики для точечных запретов, либо делай action: ALLOW с очень широким скоупов для системных компонентов.

ЧАСТЬ 7: WEBSOCKET - КОГДА ПРОКСИ НЕ ПРОКАЧИВАЕТ​

Кейс 7.1: Рукопожатие есть, а данных нет​

WebSocket - это хитрый зверь. Он начинается как HTTP ( Upgrade: websocket ), а потом переходит в постоянное TCP-соединение, по которому летят бинарные или текстовые фреймы. Envoy (и Linkerd) вроде умеют проксировать WebSocket, но как только ты начинаешь колдовать с таймаутами, балансировкой или ретраями - всё ломается.

Симптом: Клиент коннектится, получает 101 Switching Protocols, но сообщения не проходят. Или соединение рвётся через 60 секунд.

Причина №1: Таймауты Idle Timeout
По умолчанию Envoy (Istio) имеет idle_timeout для всех соединений. Если по сокету долго нет трафика (а в вебсокете это нормально - клиент может молчать минуту), Envoy его любезно прикроет.

Где смотреть:

Bash:
istioctl pc listener <pod-name> --port 8080 -o json | jq '.[] | .filterChain[].filter[].typedConfig | select(.idleTimeout != null)'

Как лечить (Istio):
В DestinationRule нужно явно разрешить долгоживущие соединения.

YAML:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: svc-websocket
spec:
  host: my-ws-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
        connectTimeout: 5s
        idleTimeout: 0s   # <--- Это отключает обрыв по бездействию
    loadBalancer:
      simple: ROUND_ROBIN

Но idleTimeout: 0s означает «бесконечность». Будь осторожен - это может привести к накоплению мёртвых соединений. Лучше поставь большое значение, например 1h.

Причина №2: Балансировка на стороне клиента
Если у тебя несколько бэкендов, и один из них отвалился, Envoy может попытаться перебалансировать существующий вебсокет на другой под. Спойлер: вебсокет этого не переживёт. Он привязан к конкретной сессии.

Решение: Используйте session affinity (липкие сессии). В Istio это называется consistentHash.

YAML:
trafficPolicy:
  loadBalancer:
    consistentHash:
      httpCookie:
        name: ws-session
        ttl: 0s

Но учти: если под умрёт, сессия всё равно умрёт. Тут уж ничего не поделаешь.


Кейс 7.2: Linkerd и WebSocket - скрытые грабли​

Linkerd по умолчанию не умеет проксировать WebSocket через свои профили. Он просто пробрасывает соединение, но метрики по вебсокетам не считает. Ты не увидишь в дашборде, сколько сообщений улетело.

Более того: Если ты включил retry в ServiceProfile для роута, который обслуживает вебсокет, Linkerd может попытаться повторить открытие сокета. А это всегда заканчивается 500-ми ошибками.

Совет: Для сервисов с вебсокетами либо вообще не настраивай ServiceProfile, либо делай отдельный route с isRetryable: false и большим таймаутом.

1770981644867.webp


ЧАСТЬ 8: GRPC-СТРИМИНГ - ТАНЦЫ С БУБНОМ​

gRPC - это прекрасно. Он быстрый, строго типизированный, использует HTTP/2. Но когда ты запускаешь gRPC-сервис внутри Service Mesh, ты подписываешься на кучу неочевидных проблем.

Кейс 8.1: gRPC Load Balancing - тупик​

Ты думаешь, что если у тебя 3 пода с gRPC-сервисом, и ты шлёшь запросы через mesh, то они распределятся равномерно. Хрен там.

Суть проблемы: gRPC поверх HTTP/2 открывает одно соединение и мультиплексирует запросы через него. Если клиент (или прокси) открывает одно соединение к бэкенду и гоняет через него тысячи запросов, то балансировка на стороне клиента (или sidecar'а) работает только при установке соединения. После того как соединение установлено, все запросы летят в один под. Остальные простаивают.

Как это выглядит в метриках: CPU одного пода зашкаливает, другие - в нуле.

Istio и gRPC load balancing:
Envoy умеет делать балансировку на уровне запросов (сбрасывать соединения и переустанавливать), но по умолчанию он ведёт себя как стандартный HTTP/2 прокси - держит соединение открытым.

Решение:
В DestinationRule нужно настроить trafficPolicy с connectionPool.http.maxRequestsPerConnection маленьким значением.

YAML:
trafficPolicy:
  connectionPool:
    http:
      maxRequestsPerConnection: 10   # После 10 запросов соединение закрывается и создаётся новое

Это заставит Envoy чаще перебалансироваться. Но не переусердствуй - слишком частое создание соединений увеличит задержки.

Более продвинутое решение: Использовать loadBalancer с политикой ROUND_ROBIN и включить localityLbSetting для распределения веса. Но, честно говоря, для gRPC лучше всего работает клиентская балансировка на уровне кода (например, используя grpc-go с резолвером Kubernetes), а не надеяться на прокси.

«Но ведь Istio сам всё разрулит!»
Нет. Istio разрулит TCP-соединения, но не gRPC-запросы внутри одного соединения. Это принципиальное ограничение HTTP/2.

Кейс 8.2: gRPC-Health Checks и таймауты​

Ты настроил liveness probe через grpc-health-probe, а pod перезапускается раз в минуту.

Причина: Envoy (или Linkerd) имеет свои таймауты и ретраи для внутренних соединений. Когда kubelet дёргает probe через localhost (минуя sidecar) - всё ок. Но если твой probe настроен идти через сервис (что бывает, если ты используешь grpc-health-probe -addr my-service:50051), то запрос идёт через прокси, и там могут быть свои дефолты.

Проверка:
Посмотри логи sidecar:

Bash:
kubectl logs <pod-name> -c istio-proxy --tail=50 | grep health
Если видишь 503 или UNAVAILABLE - значит прокси рубит healthcheck.

Решение:
В VirtualService для health-запросов (если они идут через сервис) поставь отдельный route с приоритетом и увеличенными таймаутами. А лучше - настрой probe так, чтобы он ходил напрямую в под, минуя ClusterIP. Например, используй localhost или IP пода.


ЧАСТЬ 9: WEBHOOK-МУТАЦИИ - КАК СЛОМАТЬ КЛАСТЕР ОДНИМ YAML​

Ты когда-нибудь получал ошибку Internal error occurred: failed calling webhook "namespace.sidecar-injector.istio.io": Post " ": context deadline exceeded при создании пода? Это оно.

Кейс 9.1: Mutating Webhook - петля смерти​

Istio использует Mutating Admission Webhook для автоматического внедрения sidecar-контейнера. Если этот webhook недоступен или тормозит, создание пода блокируется.

Типичный сценарий:
  1. Ты обновляешь Istio (или просто перезапускаешь istiod).
  2. В момент апдейта создаётся много подов (например, при деплое).
  3. Webhook не успевает ответить за 30 секунд.
  4. Поды остаются в состоянии Pending с ошибкой.
  5. Ты в панике.
Как это лечится:
  • Увеличь timeout в конфигурации webhook. В IstioOperator:

YAML:
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    sidecarInjectorWebhook:
      failurePolicy: Fail  # или Ignore, если хочешь пропускать при ошибках
      timeoutSeconds: 10    # было 5, ставим 10-15

Но если поставить Ignore, то поды будут создаваться без sidecar'ов - и сеть сломается.

Хитрость:
В production я рекомендую ставить failurePolicy: Fail, но обеспечить высокую доступность istiod (не менее 2 реплик, podAntiAffinity) и включить PDB, чтобы во время обновления всегда был живой под.


Кейс 9.2: Конфликт нескольких мутаторов​

У тебя есть и Istio, и какой-нибудь cert-manager, и kyverno. Все они любят мутировать поды. Порядок выполнения мутаций важен. Если Istio отработает раньше Kyverno, а потом Kyverno перезапишет какие-то аннотации или labels - sidecar может не внедриться или внедриться неправильно.

Диагностика:
Посмотри описание пода после создания (если он создался) - есть ли там istio-proxy. Если нет - ищи в событиях:

Bash:
kubectl describe pod <pod-name>
Там будет написано, какие webhook'и сработали и в каком порядке.

Решение:
Настрой webhookConfiguration с reinvocationPolicy: IfNeeded и правильным matchConditions. Но это высший пилотаж. Часто проще отключить лишние мутаторы на namespace'ах, где используется Istio.


ЧАСТЬ 10: EBPF И CILIUM - АЛЬТЕРНАТИВНАЯ РЕАЛЬНОСТЬ​

Мы живём во времена, когда sidecar'ы начинают вызывать аллергию. Каждый дополнительный контейнер жрёт ресурсы, увеличивает задержку. Cilium предлагает положить все прокси-функции в eBPF, на уровень ядра.

Кейс 11.1: Cilium как замена sidecar'ам​

Cilium может работать в режиме kube-proxy replacement и одновременно предоставлять L7 политики и observability без внедрения прокси в каждый под. Это называется Cilium Service Mesh (или Cilium Mesh).

Грабли: Cilium требует современное ядро Linux (>= 5.10) и определённые модули. Не на всех провайдерах это доступно. Кроме того, его L7 политики (например, на HTTP-роуты) пока уступают в гибкости Istio.

Но если у тебя свежий кластер на bare metal или на облаке с последней версией ядра - попробуй.
Установка:

Bash:
helm install cilium cilium/cilium --version 1.15.0 \
  --namespace kube-system \
  --set kubeProxyReplacement=true \
  --set envoy.enabled=false   # отключаем sidecar-прокси, используем eBPF

Тогда политики доступа на L7 задаются через CiliumNetworkPolicy.

Cilium eBPF быстрее, легче, но его отладка - это чтение дампов из /sys/kernel/debug/tracing/trace_pipe. Хочешь почувствовать себя хакером 90-х - добро пожаловать.


ФИНАЛ: ВЫДОХНИ И ПЕРЕЧИТАЙ​

Мы прошли ещё один круг ада. WebSocket, gRPC, webhook'и, eBPF - всё это можно починить, если знать, куда смотреть. Главный инструмент остаётся прежним - голова и tcpdump.

Знаешь, что общего между Service Mesh и первым сексом? Все говорят, как это круто, но после первого раза ты сидишь и думаешь: «И это всё? А почему тогда так больно?». А потом приходит опыт, ты перестаёшь верить рекламе и начинаешь читать документацию (и логи). Этот финал - не просто «спасибо за внимание». Это холодный душ из фактов и инструкция по выживанию в мире, где каждый Envoy может сожрать память, а каждый VirtualService - обрушить прод.

Мы прошли долгий путь. От таймаутов и ретраев до eBPF и психологии инженера. Если ты дочитал до сюда, не пролистывая код - ты либо маньяк, либо уже обжёгся. И то, и другое - наш профиль.

Давай без соплей: я не буду говорить «внедряйте mesh осторожно». Ты уже взрослый. Я просто оставлю здесь набор инструментов, ментальных моделей и реальных кейсов, которые вытаскивали меня из жопы. Сохрани этот раздел в закладки. Или распечатай и повесь над монитором. Твой будущий «я» через год скажет спасибо.


1. ВОЙНА ИСТОРИЙ: ТРИЖДЫ БЫВАЛО​

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

История 1: «Мы перешли на Istio и потеряли 30% трафика»​

Контекст: Крупный интернет-магазин. Чёрная пятница на носу. Решили «улучшить observability» и воткнули Istio с дефолтными настройками.

Симптомы: После включения sidecar’ов конверсия упала на 30%. Метрики показывали, что запросы доходят, но пользователи бросают корзины.

Расследование:
  1. Сначала грешили на сеть. Но tcpdump показал, что пакеты приходят.
  2. Смотрим логи Envoy - видим кучу 503. Почему? Envoy возвращает 503, когда не может найти здоровый апстрим. Но поды живы!
  3. Копаем istioctl pc endpoints. Оказывается, Envoy получил эндпоинты, но из-за неправильной настройки outlierDetection он посчитал все поды «больными» и перестал слать трафик. А причина - в коде была ошибка, из-за которой каждый 100-й запрос падал с 500. Outlier detection посчитал, что раз есть 500-е, значит под надо исключить. Исключил все поды по очереди. Круг замкнулся.
Решение: Настроили outlierDetection адекватно: consecutive5xxErrors: 20 (чтобы не выкидывать под из-за случайных ошибок), interval: 2m. И, конечно, починили код.

Вывод: Outlier detection - это не магия, а скальпель. Слишком чувствительный - убьёт сервис. Слишком тупой - бесполезен.

История 2: Linkerd и «исчезающие» метрики​

Контекст: Финансовый стартап. Всё работает на Linkerd. Разработчики жалуются, что иногда запросы «теряются» - клиент получил ответ, а в метриках Linkerd запроса нет.

Расследование:
  1. Смотрим linkerd viz tap. Видим, что запрос пришёл в прокси, ушёл в апстрим, апстрим ответил 200, но в метриках linkerd viz stat этот запрос не отображается.
  2. Оказывается, разработчик использовал кастомный HTTP-метод (не GET/POST, а что-то вроде PURGE для кэша). Linkerd по умолчанию считает метрики только для стандартных методов. Всё остальное он проксирует, но не считает.
Решение: В ServiceProfile можно явно перечислить методы, но проще было обновить Linkerd до версии, где это чинят (или использовать tcp метрики). Но суть в том: метрики врут, если ты выходишь за рамки «обычного».

Вывод: Не верь метрикам слепо. Всегда проверяй логи и tap.

История 3: Когда mTLS включён, но не работает​

Контекст: Законопослушная компания, требует шифрования всего трафика. Включили STRICT mTLS в Istio. Всё зелено. Приходит аудитор и говорит: «А покажите, что трафик реально зашифрован».

Расследование:
  1. istioctl authn tls-check показывает mTLS: true.
  2. Но аудитор просит поймать пакет. Делаем tcpdump на поде-получателе. Видим, что трафик приходит на порт 8080 пода, но пакеты не выглядят как TLS. Они plain HTTP.
  3. Шок. Оказывается, sidecar стоит, но в поде есть ещё один контейнер, который слушает на том же порту, и из-за кривой network policy трафик иногда идёт напрямую к контейнеру, минуя sidecar. Istio показывает mTLS между sidecar’ами, но если трафик вообще не идёт через sidecar, то mTLS и не нужен.
Решение: Проверили iptables внутри пода (команда iptables-save в namespace sidecar’а). Увидели, что правила редиректа работают, но из-за того, что под имел hostNetwork: true для одного из контейнеров, трафик шёл в обход. Исправили настройки.

Вывод: mTLS на бумаге и mTLS в пакетах - разные вещи. Единственный способ убедиться - смотреть трафик.


2. КОНТРОЛЬНЫЙ СПИСОК АУДИТА SERVICE MESH (SAVE THE LIVES)​

Когда ты приходишь в новый проект и видишь, что там уже стоит Istio (или Linkerd), ты не знаешь, какие грабли тебя ждут. Вот мой личный чек-лист. Пройди по нему, и 80% проблем отпадут.

2.1. Базовые проверки (здоровье кластера)​

  • Версии: Все ли компоненты (istiod, envoy, linkerd-controller) одной версии? Микс версий - путь к аду.
  • Ресурсы: Не жмутся ли sidecar’ы в CPU/Memory? Проверь kubectl top pod -n your-ns. Если прокси жрёт > 200m CPU без нагрузки - что-то не так.
  • Логи: Нет ли в логах istiod/controller постоянных ошибок вида watch error или Failed to push updates?
  • Webhook: Проверь, что MutatingWebhookConfiguration жив и отвечает. kubectl get mutatingwebhookconfiguration | grep istio.

2.2. Сеть и маршрутизация​

  • DestinationRules: Есть ли они вообще? Если нет - ты используешь дефолтные настройки, которые могут не подходить.
  • Connection Pool: Не забыл ли ты ограничить maxConnections и maxRequestsPerConnection? Особенно для gRPC.
  • Outlier Detection: Включён ли он? Если нет, ты теряешь способность автоматически отключать больные поды.
  • Retries: Не стоят ли ретраи на идемпотентные методы? Не слишком ли много попыток?
  • Timeouts: Все ли таймауты установлены и адекватны? (Для каждого сервиса - свои).
  • mTLS: Включён ли Strict mode? Если да, проверь, что все сервисы действительно имеют sidecar’ы.

2.3. Наблюдаемость​

  • Метрики: Видишь ли ты istio_requests_total в Prometheus? Есть ли дашборды в Grafana?
  • Трассировка: Подключена ли распределённая трассировка (Jaeger/Zipkin)? Без неё ты слеп.
  • Логи: Включён ли access log в Envoy? (По умолчанию он часто выключен). Включи хотя бы на пару часов при отладке.

2.4. Безопасность​

  • AuthorizationPolicies: Не заблокировали ли ты сам себя? Проверь, что kube-system и мониторинг имеют доступ.
  • PeerAuthentication: Не стоит ли DISABLE где попало?
  • Secrets: Как хранятся сертификаты? Не истекли ли они? (Для самоподписанных - проверь дату).

2.5. Производительность​

  • Задержки: Измерь latency с прокси и без. Если прокси добавляет больше 5-10 мс - возможно, надо тюнить.
  • Память: Следи за RSS Envoy. Если растёт бесконтрольно - ищи утечки или настраивай maxStats и maxObjName.

3. МАНТРА ДЛЯ ВЫЖИВАНИЯ (ПОВТОРЯТЬ КАЖДОЕ УТРО)​

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

1. «Mesh - это не волшебная палочка, а распределённый монолит».
Каждый раз, когда ты добавляешь прокси, ты добавляешь точку отказа. Не верь в «нулевой downtime». Верь в резервирование.

2. «Дефолтные настройки созданы для демо, не для прода».
Istio из коробки настроен так, чтобы легко стартовать. Но в проде он будет вести себя как капризный ребёнок. Всё, что можно тюнить - тюнь.

3. «Метрики не заменяют логи, а логи - дампы трафика».
Всегда имей под рукой tcpdump. Всегда.

4. «Разработчики будут делать глупости. Твоя задача - смягчить последствия».
Ты не сможешь заставить всех писать идемпотентные ручки. Но ты можешь настроить retry так, чтобы они не убили кластер.

5. «Если ты не можешь объяснить, как работает кусок конфига, не применяй его в прод».
Это правило убережёт от копипасты из интернета.


4. БУДУЩЕЕ: КУДА МЫ ДВИЖЕМСЯ (И СТОИТ ЛИ ТУДА ИДТИ)​

Service Mesh живёт и развивается. На горизонте несколько трендов, которые изменят ландшафт.

4.1. eBPF и Cilium: смерть sidecar'ам?​

Как я уже упоминал, Cilium предлагает вынести функции mesh на уровень ядра. Это даёт прирост производительности и экономию ресурсов. Но плата - сложность отладки и привязка к ядру Linux. Я думаю, что через 2-3 года гибридные модели (sidecar для сложной логики, eBPF для базового баланса и безопасности) станут стандартом.

4.2. WebAssembly (WASM) в Envoy​

Возможность писать фильтры на любом языке и компилировать в WASM. Это позволит кастомизировать поведение прокси без пересборки Envoy. Уже сейчас можно подгружать WASM-модули для аутентификации, трансформации запросов и т.д. Но осторожно: плохо написанный WASM может убить производительность.

4.3. Ambient Mesh от Istio​

Istio работает над режимом без sidecar'ов (ambient), где прокси стоит на узле и обслуживает все поды. Это компромисс между производительностью и изоляцией. Посмотрим, что получится.

4.4. Унификация API​

Gateway API (Kubernetes) постепенно вытесняет Ingress и частично VirtualService. Istio уже поддерживает Gateway API. Это значит, что конфигурация станет более стандартизированной.

Мой прогноз: sidecar'ы никуда не денутся в ближайшие 5 лет, но станут опциональными. Для простых сценариев хватит ambient или eBPF, для сложных (кастомные фильтры, per-pod политики) - останутся sidecar'ы.


5. ПОСЛЕДНЕЕ СЛОВО​

Слушай, мы прошли огромный путь. Если ты осилил всё это от начала до конца - ты либо маньяк, как я, либо у тебя действительно горит прод и ты ищешь ответы. В любом случае - ты свой. И перед тем как мы разойдёмся, я хочу поговорить с тобой не как автор с читателем, а как старший товарищ с младшим.

О чём на самом деле эта статья

Формально - об ошибках в Service Mesh. По факту - об ошибках в нашем мышлении. Потому что за каждой 503, за каждым упавшим подом, за каждым разъевшим память Envoy стоит человеческое решение. Кто-то когда-то сказал: «А давай поставим retry подлиннее». Кто-то скопировал конфиг из туториала, не вчитавшись. Кто-то поленился проверить, что происходит с соединениями при деплое.

Mesh - это увеличительное стекло. Он не создаёт проблемы, он их проявляет. Если у тебя в коде есть баг с утечкой сокетов - без mesh ты узнаешь об этом через месяц, когда упадёт один под. С mesh ты узнаешь через пять минут, когда упадёт весь кластер, потому что Envoy откроет тысячу соединений и ляжет по памяти. Mesh не делает твою систему хуже, он делает её прозрачнее. И это больно.

Мои пять копеек про отношение к работе

Я вижу, как инженеры приходят в профессию с верой в «серебряные пули». Они думают, что если внедрить Kubernetes, то всё само масштабируется. Если поставить Istio - само защитится. Если написать на Go - само будет быстрым. А потом приходит реальность, и они разочаровываются. Уходят в менеджмент или в продажи. А те, кто остаются - начинают понимать, что нет готовых решений. Есть только компромиссы.

Service Mesh - это компромисс между контролем и сложностью. Ты получаешь тонкую настройку трафика, но платишь за это головной болью при отладке. Ты получаешь mTLS из коробки, но платишь за это дополнительной задержкой. Ты получаешь красивые дашборды, но платишь за это необходимостью разбираться в устройстве Envoy, когда дашборды врут.

Я не буду говорить: «Делай как я, и будет счастье». Я скажу иначе: думай своей головой. Если ты ставишь mesh - ставь осознанно. Если не ставишь - тоже осознанно. Не потому, что «так модно» или «так все делают». А потому что ты взвесил все за и против и решил, что для твоего конкретного случая это оправдано.

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

Эмоциональный интеллект инженера

Знаешь, чему не учат на курсах по Kubernetes? Не учат сохранять спокойствие, когда прод лёг, а начальство стоит над душой. Не учат не винить себя, когда ты ошибся в конфиге и положил кластер. Не учат просить помощи, когда сам не справляешься.

Я хочу, чтобы ты запомнил: ошибки - это нормально. Я сам наступал на каждые грабли, описанные здесь, и ещё на тысячу, которые не влезли. Это часть профессии. Важно не то, сколько раз ты упал, а то, сколько раз ты поднялся и разобрался, почему упал.

Если ты сейчас читаешь это в три часа ночи, прод горит, а ты не знаешь, что делать - выдохни. Отойди от компа на пять минут. Выпей воды. Посмотри в окно. Потом вернись и начни с самого простого: посмотри логи, проверь статусы, сделай tcpdump. Не пытайся решить всё сразу. Решай по частям.

Что дальше

Service Mesh не стоит на месте. Завтра появятся новые версии, новые фичи, новые баги. То, что я написал сегодня, может устареть через год. Но принципы останутся: понимание сети, умение читать логи, уважение к ресурсам, осторожность с дефолтами.

Я буду продолжать писать, если ты будешь продолжать читать и задавать вопросы. Мы растем вместе. И если у тебя есть тема, которую я не затронул - пиши, я разберу. Если нашёл ошибку в моих словах - пиши, я исправлю. Если просто хочешь сказать спасибо - тоже пиши, мне будет приятно.

Финальный чек-лист (напоследок)

Перед тем как ты закроешь эту вкладку, сделай три вещи:
  1. Проверь свои DestinationRules - есть ли там outlierDetection и разумные лимиты соединений.
  2. Посмотри логи istiod (или linkerd-controller) - нет ли там ошибок синхронизации.
  3. Напиши в комментарии или сохрани себе где-нибудь одну мысль, которую ты вынес из этого текста. Чтобы через месяц, когда снова столкнёшься с проблемой, вспомнить: «А, точно, AncienDev про это говорил».
Прощание

Ну всё, брат. Я выговорился. Идёт четвёртый час ночи, за окном тишина, только сервера гудят где-то в дата-центре. Скоро рассвет, а с ним - новые инциденты, новые деплои, новые задачи. Но мы справимся. Потому что мы инженеры. Мы любим копаться в этом дерьме, иначе бы выбрали другую профессию.
 
  • Нравится
Реакции: samhainhf
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab