Трендовые github проекты в нашем телеграм канале. Подпишись → Как ломается сервисная сетка, когда падает кластер или регион
Сервисная сетка почти никогда не падает так, чтобы трафик перестал ходить совсем. Обычно она теряет способность реагировать на изменения: перестаёт обновлять список эндпойнтов, не пускает новые поды, не даёт масштабироваться. Приложение при этом какое-то время работает, дашборды зелёные, а проблема всплывает в момент, когда нужно восстанавливаться. Поэтому в учения по DR сетку надо включать как отдельный компонент со своими точками отказа.
Ниже разобран стенд из трёх кластеров с Linkerd и четыре сценария: отказ control plane, кластера, зоны доступности и региона.
Стенд
Три кластера — WEST, CENTRAL и EAST — подняты локально через k3d в общей Docker-сети. В каждом по три узла, каждый узел изображает отдельную зону доступности. CENTRAL и EAST находятся в одном «регионе», WEST — в другом. В WEST живут генератор нагрузки k6 и мониторинг на Prometheus + Grafana; сбои вносятся только в CENTRAL и EAST, чтобы не потерять метрики.
Приложение — podinfo, по одной реплике на узел, девять реплик всего. Оно опубликовано сразу в трёх мультикластерных режимах Linkerd:
- federated — один логический сервис, эндпойнты которого собираются из всех кластеров-участников;
- flat (pod-to-pod) — сервисы удалённых кластеров зеркалируются, клиент сам выбирает, куда идти (в тесте k6 ходит в EAST);
- gateway — в исходном кластере создаётся сервис, эндпойнт которого указывает на шлюз в целевом кластере; поды разрешает уже целевой кластер.
Нагрузка постоянная: 30 RPS на каждый режим в каждом кластере. Сбои вносятся остановкой подов и Docker-контейнеров-узлов, удалением Docker-сети и задержками через Chaos Mesh. Для облака те же сценарии воспроизводятся через Chaos Mesh или LitmusChaos.
На дашборде отслеживались: запросы без mTLS, RPS, доля ошибок, число эндпойнтов federated-сервиса, доступность шлюза и доля межзонального трафика.
Сценарий 1: отказ control plane
Недоступен destination
Сервис destination отвечает за обнаружение эндпойнтов и контроллер политик. Его выключили в CENTRAL, а затем отмасштабировали приложение в EAST до нуля. Через 90 секунд CENTRAL по-прежнему считал все federated-эндпойнты живыми, хотя подов в EAST уже не существовало. Сетка продолжала работать на устаревшей картине мира.
Недоступен identity
Сервис identity выдаёт прокси сертификаты. Уже запущенные прокси спокойно работают со своими сертификатами до истечения срока — по умолчанию это 24 часа с последнего успешного обновления. Новые поды сертификат получить не могут и в сетку не попадают. На практике это значит, что поднять реплики во время аварии не получится.
Отсюда главный вывод сценария: план переключения почти всегда включает масштабирование, а масштабирование зависит от живого control plane. Если сетка лежит, процедура восстановления упирается в неё. Health-чеки подов при этом могут маскировать проблему: приложение отвечает, пока не понадобится что-то поменять.
Сценарий 2: отказ кластера
В EAST остановили все три узла, затем повторили тест с удалением Docker-сети между кластерами — полная потеря связности.
Режимы реагируют по-разному, и разница определяется тем, где происходит обнаружение эндпойнтов: в локальном кластере (federated, flat) или в удалённом (gateway). В одних режимах отказ виден сразу, в других — только косвенно.
Самая неприятная ловушка: если discovery продолжает отдавать мёртвые эндпойнты, запросы уходят туда и висят до тайм-аута клиента. Если тайм-аутов нет, метрики вроде request_total даже не растут — ошибок в статистике нет, потому что запросы не завершаются. Алерт на долю ошибок такую ситуацию пропустит. Тайм-ауты и ретраи нужно задать либо в клиенте, либо средствами Linkerd.
Для тестов полезнее жёсткие обрывы, чем graceful shutdown. Корректное завершение замечают readiness-пробы и другие механизмы, а внезапная потеря узла обнаруживается только по тайм-аутам discovery. Именно так получаются честные цифры времени обнаружения и восстановления.
Сценарий 3: зона деградирует
В одну зону кластеров EAST и CENTRAL добавили задержку +400 мс. Поды всё это время оставались healthy.
Topology-aware routing в Kubernetes такую зону не видит: формально всё работает, и трафик продолжает идти в медленные эндпойнты. HAZL (High Availability Zonal Load Balancing из Buoyant Enterprise for Linkerd) смотрит на фактическое поведение эндпойнтов и уводит трафик туда, где отвечают быстрее.
У HAZL есть и второй эффект — деньги. В штатном режиме он держит трафик в своей зоне даже при переходе между кластерами. Open source Linkerd распределяет ту же нагрузку без учёта зон, и в облаке большинство запросов превращается в оплачиваемый межзональный egress.
Ограничение: межкластерная зональная локальность HAZL пока работает только в режиме flat (pod-to-pod), на gateway она не распространяется.
Сценарий 4: отказ региона
Оба кластера региона a выключены, остаётся только WEST.
Federated-режим это пережил: поды WEST уже входили в пул federated-сервиса, и трафик просто перетёк на них. Условие одно — WEST должен выдержать всю нагрузку и уметь масштабироваться. Если запаса мощности нет, нужны rate limiting и circuit breaking, чтобы сервисы принимали ровно столько запросов, сколько могут обработать; Linkerd поддерживает оба механизма.
Flat и gateway в этом тесте отказали, потому что их целевые эндпойнты находились в упавшем регионе. Это свойство конкретной схемы стенда, в другой топологии результат будет другим.
Настройки установки, которые выстрелят во время аварии
Установка Linkerd с дефолтными значениями Helm-чарта и установка по production-гайду Buoyant ведут себя по-разному:
webhookFailurePolicy. Без HA-режима для proxy injector стоитIgnore. Если injector недоступен, поды стартуют без прокси и оказываются вне сетки — без mTLS и без политик. В HA-режиме значение меняется наFail, и такие поды не создаются вовсе. Какой вариант лучше, зависит от того, что для вас хуже во время аварии: поды вне сетки или поды, которые не запускаются.defaultInboundPolicy. В обоих режимах по умолчаниюall-unauthenticated: поды в сетке принимают трафик без mTLS, пока политику не ужесточат глобально или на уровне namespace.
Перед тем как писать DR-план, стоит пройтись по production-чеклисту Buoyant и сверить свои значения.
Алерты, которых нет из коробки
Алерт на долю 5xx ловит только симптом. Для сетки нужны отдельные сигналы:
- состояние компонентов control plane (
destination,identity, proxy injector); - срок действия сертификатов прокси;
- поды, застрявшие на инициализации и не вошедшие в сетку;
- трафик без mTLS там, где его быть не должно;
- для federated — число эндпойнтов, для gateway — доступность шлюза.
После настройки их нужно проверить: смоделировать отказ и убедиться, что алерт пришёл, а после восстановления — что он закрылся.
Как построить тест
Возьмите один сценарий — например, выход кластера из строя или рост задержки в регионе — и прогоните его целиком:
- Зафиксируйте исходное состояние: сервисы работают, нагрузка стабильная.
- Запишите ожидаемое поведение: например, межкластерные запросы падают по тайм-ауту.
- Внесите сбой и проверьте, что алерты сработали.
- Восстановите систему и проверьте, что алерты закрылись.
- Разберите, какие компоненты отказали, исправьте и повторите.
Последний этап обязателен. Проблемы с identity на стенде проявлялись только при попытке поднять реплики, то есть уже во время восстановления. Тест, который заканчивается на «сбой внесён, алерт пришёл», их не поймает. Отдельный полезный вариант: остановить часть control plane и одновременно дать нагрузку, которая запустит автоскейлинг, — так сразу видно, успеют ли алерты раньше пользователей.