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

12 кластеров в одном GitOps-репозитории: иерархия tenants, изоляция namespace и неожиданные зависимости fleet-контроллера

Разбираем, как выстроить управление кластерным флотом через Flux Fleet и Deckhouse Fleet Controller: иерархия tenants, изоляция namespace-окружений, где возникают скрытые зависимости.

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

Fleet-подход к GitOps набирает зрелость: Flux Fleet и Deckhouse Fleet Controller применяются в проде у крупных команд для управления многокластерными флотами

Когда кластеров становится больше десяти, GitOps-репозиторий начинает расти в двух направлениях сразу: вширь - по количеству окружений, и вглубь - по уровням наследования конфигурации. Именно здесь большинство команд обнаруживают, что «один репозиторий» и «единая точка управления» - это не одно и то же.

Мы прошли этот путь с флотом из 12 кластеров, где есть прод, несколько staging-стендов, dev-кластеры командная и изолированный контур для задач с повышенными требованиями по безопасности. Ниже - как выглядит рабочая схема, где она трещит по швам и что в ней до сих пор неочевидно.

Иерархия tenants: три уровня, не два

Первый инстинкт при fleet-подходе - разделить репозиторий на «платформенный слой» и «слой приложений». Это правильно, но недостаточно. На практике нужен третий уровень - контекст кластера.

Структура, которая у нас сложилась:

fleet/
  base/          # платформа: CNI, мониторинг, политики безопасности
  tenants/       # командные namespace-пространства с квотами и RBAC
    team-a/
    team-b/
  clusters/      # оверлеи на конкретный кластер
    prod-msk/
    staging-01/
    dev-shared/

Flux Fleet читает clusters/<name>/ как точку входа для каждого кластера. Там живут Kustomization-ресурсы, которые ссылаются на нужные компоненты из base/ и tenants/. Каждый кластер явно перечисляет, что он берёт, а не наследует всё по умолчанию.

Почему не двухуровневая схема. Без отдельного уровня кластерного контекста начинается Copy-paste оверлеев: у prod-msk и prod-spb одинаковая база, но разные ресурсные квоты и разные endpoints внешних сервисов. Без третьего слоя это либо дублирование, либо переменные в Helm values, которые захламляют tenants/. Третий слой разделяет эти concerns чисто.

Изоляция namespace-окружений

С Flux Fleet изоляция namespace достигается через ServiceAccount-ограничения: каждый tenant-Kustomization запускается от имени ServiceAccount с доступом только к своим namespace. Это работает, но есть два момента, которые обычно упускают.

Первое - CRD не изолированы. Если tenant деплоит оператора с кастомными CRD - эти CRD кластерные. Один tenant может зарегистрировать CRD, другой случайно (или намеренно) начнёт им пользоваться. В нашем случае это проявилось когда команда A задеплоила оператора для PostgreSQL, а команда B начала создавать PostgreSQLCluster-ресурсы в своём namespace - и оператор команды A их подхватил, потому что у него был watchNamespaces: *. Решение: явный watchNamespaces в каждом операторе, и это должно быть частью платформенных политик, а не добровольным соглашением команд.

Второе - NetworkPolicy живёт рядом с деплоем. Если team-a и team-b деплоятся независимо, NetworkPolicy для их namespace тоже попадают в отдельные Kustomization-ресурсы. Это создаёт окно между моментом, когда Pod уже запущен, и моментом, когда политика применена. На практике для dev-кластеров это несущественно, для prod - нет. Мы вынесли NetworkPolicy для всех namespace в отдельный base/network-policies/ с приоритетным порядком синхронизации (через dependsOn в Flux).

Где fleet-контроллер создаёт неожиданные зависимости

Вот здесь самое нетривиальное наблюдение.

Deckhouse Fleet Controller хорошо умеет распространять конфигурацию по кластерам - синхронизирует Deckhouse-модули, propagates политики, обновляет nodeGroups. Но у него есть поведение, которое мы не сразу заметили: если на кластере не прошла синхронизация одного ресурса, контроллер останавливает обработку последующих ресурсов для этого кластера по умолчанию.

В Flux это поведение тоже есть, но там оно явное: dependsOn указываешь руками. В Fleet Controller зависимость между ресурсами частично выводится автоматически из наличия общих модулей. Из-за этого у нас однажды завис деплой обновления сетевых политик на staging-02 - из-за того, что модуль мониторинга на этом кластере не прошёл health check (у него был CrashLoopBackOff на одном поде из-за неправильного лимита памяти). Мониторинг и сетевые политики - несвязанные вещи с нашей точки зрения. С точки зрения контроллера - связанные, потому что оба живут в системном namespace.

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

Похожая история с Flux Fleet: если Kustomization для базового слоя (base/) завис в Progressing дольше таймаута - все Kustomization, которые от него зависят через dependsOn, встают в очередь. При большом флоте это означает, что одна проблема на одном кластере может задержать синхронизацию на нескольких. Мы добавили alerting именно на состояние Progressing старше 10 минут - это оказалось полезнее, чем alert на Failed.

Что до сих пор неудобно

Мультикластерный GitOps требует хорошей видимости того, какая версия конфигурации реально применена на каком кластере. Flux даёт это через kubectl get kustomizations -A, но агрегированного view по всему флоту из коробки нет. Приходится либо строить самим (мы сделали простой скрипт, который опрашивает API каждого кластера и собирает статусы в единую таблицу), либо использовать Weave GitOps Enterprise, если бюджет позволяет.

Deckhouse Fleet Controller в части видимости удобнее - у него есть централизованная консоль, но она заточена под Deckhouse-специфику. Если часть кластеров на Deckhouse, а часть на upstream Kubernetes - единого view всё равно нет.

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

Контакт

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

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