GitOps для Deckhouse через Flux v2: три кластера, одна боль с registry и drift detection
Перевели три Deckhouse-кластера на GitOps через Flux v2: разбираем структуру репо, проблемы с image policy для отечественных registry и реальный drift detection.
GitOps-подход с Flux v2 и Deckhouse становится стандартом для отечественных k8s-деплойментов - осень 2025
Примерно полгода назад мы решили, что ручное применение манифестов к трём Deckhouse-кластерам - это не процесс, а рулетка. Разные версии конфигов на staging и prod, изменения «быстро руками» которые потом забывают закоммитить, расхождения между кластерами которые обнаруживаются в самый неподходящий момент. Классика. Взяли Flux v2 и начали переводить.
Это не история успеха с круглыми числами в конце. Это отчёт о том, что получилось, где споткнулись и где пока компромисс.
Структура репозитория
Первый вопрос при внедрении GitOps - как организовать репо. Монорепо для всех кластеров или отдельные? Попробовали оба подхода и остановились на монорепо со следующей раскладкой:
clusters/
prod/
flux-system/ # bootstrap-компоненты Flux
infrastructure/ # HelmRelease, HelmRepository, базовые Kustomization
apps/ # приложения клиента
staging/
...
dev/
...
base/
infrastructure/ # общие шаблоны через Kustomize
apps/ # базовые манифесты приложений
base/ содержит общее, в clusters/<env>/ - патчи и оверрайды через Kustomize. Flux в каждом кластере смотрит на свою директорию и не видит остальных. На практике это означает, что промоушн изменения с dev на prod - это PR с копированием патча в нужную директорию. Немного церемоний, зато явно.
Что работает хорошо. Deckhouse-специфичные ресурсы - NodeGroup, IngressNginxController, модульные конфигурации через ModuleConfig - прекрасно живут в GitOps. Flux видит их как обычные CRD, reconcile работает, drift detection ловит ручные изменения.
Что создало головную боль. Сам Deckhouse обновляет часть своих объектов автоматически - в частности, NodeGroup при staged rollout получает статусные аннотации, а некоторые ModuleConfig получают defaultsFrom от самой платформы. Flux это интерпретирует как drift и хочет откатить. Решение - spec.ignore в Kustomization с явным перечислением полей, которые Deckhouse меняет сам. Не элегантно, но работает.
Image policy и отечественные registry
Вот здесь пришлось потратить время.
Flux v2 имеет ImageRepository и ImagePolicy - механизм автоматического обновления образов при появлении новых тегов. Выглядит красиво: Flux следит за registry, видит новый тег, обновляет манифест в git через ImageUpdateAutomation, Flux применяет - полный цикл без ручного вмешательства.
Проблема в том, что отечественные registry - в нашем случае это Harbor, развёрнутый у клиента в изолированном сегменте, и несколько репо на Nexus - используют self-signed сертификаты и нестандартные порты. ImageRepository требует явного указания secretRef с TLS-конфигурацией, и первые попытки давали невнятную ошибку x509: certificate signed by unknown authority без указания на то, какой именно компонент Flux не доверяет сертификату.
Оказалось, что сертификат нужно добавлять не только в секрет для ImageRepository, но и отдельно монтировать в поды image-reflector-controller, если он запущен вне кластера или с кастомной конфигурацией. В нашем случае кластер изолирован, и image-reflector-controller ходит в Harbor через внутренний IP - там была ещё и проблема с SNI, потому что Harbor был проксирован через nginx с hostname, который отличался от IP.
Итоговое решение:
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: app-images
namespace: flux-system
spec:
image: harbor.internal.example/project/app
interval: 5m
certSecretRef:
name: harbor-tls
insecure: false
В секрете harbor-tls - CA-сертификат Harbor. Дополнительно пришлось патчить deployment image-reflector-controller через kustomizeconfig чтобы смонтировать тот же CA в под. Это не очевидно из документации Flux, пришли к этому через несколько часов дебага.
На Nexus была другая история: Nexus hosted repository для Docker не всегда корректно отдаёт _catalog endpoint, который Flux использует для листинга тегов. Для части репозиториев пришлось переключить ImagePolicy на конкретный диапазон тегов через semver вместо latest, что обходит проблему листинга.
Drift detection в реальных условиях
Один из главных аргументов за GitOps - drift detection: если кто-то что-то поменял руками в кластере, Flux это видит и сообщает (или откатывает, в зависимости от настройки).
На практике в первые недели drift alerts были источником шума, а не сигнала. Причины:
- Deckhouse меняет свои объекты - уже описали выше.
- Операторы приложений - cert-manager, external-secrets, Crossplane - добавляют аннотации и метки к объектам, которые они контролируют. Flux видит это как расхождение.
- HPA меняет
spec.replicasв Deployment. Если в git зафиксировано конкретное число реплик, Flux будет постоянно воевать с HPA.
Для HPA решение стандартное - убрать spec.replicas из манифеста в git, если HPA за него отвечает. Flux в таком случае не трогает поле, которого нет в желаемом состоянии.
Для операторов - spec.ignore с field-level exclude по конкретным путям. Это ручная работа: для каждого оператора нужно выяснить, какие именно поля он мутирует, и занести их в ignore. Мы ведём общий список в base/infrastructure/flux-ignore-patches.yaml.
После нескольких недель настройки drift alerts стали значимыми: теперь если что-то горит в Flux - это действительно ручное изменение или баг в операторе, а не шум от платформы.
Где сейчас
Все три кластера работают через Flux уже около двух месяцев. Image policy для production намеренно отключена - автоматическое обновление образов в prod кажется нам слишком рискованным без дополнительного approval-шага. Используем Flux только для drift detection и reconcile конфигурации, а обновление образов - через PR с явным изменением тега в манифесте.
Это компромисс, и мы его осознаём. Полный GitOps с автообновлением образов требует либо доверенной цепочки CI с нормальным staging, либо готовности к тому что в prod иногда прилетает что-то неожиданное.
В рамках managed-сопровождения для двух из трёх кластеров именно такой подход - GitOps для конфигурации, ручной PR для образов - сейчас ощущается как разумный баланс между автоматизацией и контролем.