Зачем нужна эта статья
В статье рассмотрим основы использования FluxCD и увидим, что он не так сложен, как может показаться. Действительно, при его эксплуатации можно наткнуться на некоторые проблемы, например:
- использование большого монорепозитория с исходниками, бинарными файлами, результатами сборок и тому подобного без настройки параметров .spec.ignore или .sourceignore — что сильно увеличивает нагрузку на сеть, Git, контроллеры Flux и время реконсиляции;
- применение единственного объекта Kustomization на весь кластер из-за чего сложно определить, в какой группе манифестов возникла ошибка;
- избыточная связность компонентов через dependsOn, что увеличивает время каждой реконсиляции и усложняет проект;
- дублирование конфигураций окружений dev, stage, prod вносит путаницу и раздувает репозиторий;
- ситуации, когда изменение попало в Git, но долго не появляется в кластере;
- удаление файла неожиданно удаляет ресурс.
В статье постарался показать проблемы и их решения. Сам сталкивался с ними при вкатывании во FluxCD. Надеюсь, будет полезно.
Коротко о Flux
Kubernetes отлично поддерживает заданное состояние, но вся сложность в том, как его задавать при использовании большого числа приложений, кластеров, и множества сотрудников, которые хотят что-то поменять.
Обычно это делают через инструменты CI/CD — такой подход называется Push-модель. Подобный способ чреват: CI/CD‑сервер должен иметь права администратора для работы с продакшен‑кластером. Взломают сервер — получат полный доступ и к кластеру. К тому же API Kubernetes должен быть доступен извне, что тоже не добавляет безопасности.
FluxCD решает все сложности с получением состояния, используя идеологию GitOps — способ доставки меняется на Pull-модель, которая имеет несколько преимуществ:
- безопасность — Flux запускается внутри кластера и сам следит за изменениями в репозитории, а значит больше не нужно держать общедоступный API;
- Git как источник истины — вся инфраструктура описана в коде, нет необходимости в вызовах kubectl, достаточно влить изменения в главную ветку;
- защита от дрейфа — если кто‑то поменяет настройки прямо в кластере, Flux мгновенно заметит расхождение и восстановит правильное состояние, что защищает от ошибочных или неучтенных ручных правок.
Структура репозитория
Структуру репозитория FluxCD, конечно, лучше продумывать исходя из нужд проекта. Универсального варианта не существует. Монорепозиторий упрощает атомарные изменения и обзор состояния всей инфраструктуры. Напротив, несколько репозиториев позволяют разделить зону ответственности, права доступа и жизненный цикл релизов разных команд. Если по какой-то причине монорепозиторий не подходит, на официальной странице Flux можно рассмотреть другие варианты.
Ниже показан используемый нами вариант монорепозитория. Абстракции в нем разделены по папкам:
├── infra
├── clusters
└── tenants
Рассмотрим структуру подробнее.
- infra/ — содержит манифесты и наборы приложений. Здесь рекомендуется хранить только инфраструктурные сервисы.
- clusters/ — описывает каждый существующий кластер(dev, stage, prod) и определяет, какие манифесты и наборы из каталога infra будут к нему применяться.
- tenants/ — содержит конфигурации тенантов, отличных от административных. С помощью этой директории можно подключить репозиторий команды разработки с собственным описанием деплоя приложения. В основном репозитории этот манифест будет подхвачен и установлен контроллерами FluxCD от имени соответствующего ServiceAccount (SA).
Пройдемся по каждой папке:
infra
├── apps # инфраструктурные приложения
├── crds # CRD для инфраструктурных приложений
├── operators # как apps, только в отдельной директории
└── security # как apps, только в отдельной директории
Разделение манифестов по отдельным каталогам позволит в будущем применять их в нужной последовательности.
Рассмотрим каждый из них чуть подробнее.
infra/
infra
├── apps
# Манифесты приложений, helm-repo/helm-release, kustomization
│ ├── base
│ │ ├── envoy
│ │ ├── cert-manager
│ │ ├── node-exporter
│ │ └── ...
# Наборы приложений для определенного окружения
# Линкуются из infra/apps/base в kustomization
# Есть возможность перезаписать значения из оригинального манифеста или добавить дополнительный манифест окружению
│ └── bundles
│ ├── dev-cluster
│ ├── stage-cluster
│ └── prod-cluster
├── crds
# CRD приложений, у которых они есть
│ ├── base
│ │ ├── envoy
│ │ ├── cert-manager
│ │ └── ...
# Набор CRD для окружения
│ └── bundles
│ ├── dev-cluster
│ ├── stage-cluster
│ └── prod-cluster
# Манифесты для деплоя операторов без CR(Custom Resource)
├── operators
│ ├── base
│ │ ├── cnpg-operator
│ │ ├── external-secrets-operator
│ │ └── ...
│ └── bundles
│ ├── dev-cluster
│ ├── stage-cluster
│ └── prod-cluster
# Манифесты для безопасности
└── security
├── base
│ ├── kyverno # Манифесты для деплоя kyverno
│ └── kyverno-policies # Политики kyverno
# Набор для окружения
# Есть возможность протестировать новую политику
└── bundles
├── stage
└── prod
В структуре прослеживается общий паттерн хранения файлов в разных модулях: каталоги base/ и bundles/. В первом хранятся базовые манифесты, а во втором — через kustomize создается нужный набор ресурсов для конкретного окружения. Также есть возможность переопределить часть манифеста из apps/ в нужном бандле, не затрагивая оригинальный манифест.
dependsOn
Контроллеры FluxCD, как и сам Kubernetes, обрабатывают манифесты асинхронно. DependsOn — это механизм Flux, который позволяет задать строгую очередность применения манифестов (Kustomization и HelmRelease), чтобы избежать ошибок из-за отсутствующих зависимостей.
Цепочки зависимостей по dependsOn можно выстраивать по‑разному. В основном просматривается такой подход:
CRDs → Kyverno → политики Kyverno → операторы → инфраструктурные приложения → бизнес‑приложения
В обеих группах apps могут разворачиваться Custom Resources (CR) от операторов. Использование Kyverno зависит от конкретного проекта. Если он применяется, то через работающий validating webhook kyverno должны пройти все манифесты, которые нужно сверять с политикой безопасности. По‑другому не сработает.
Также в кластере сначала должны появиться определения CRD (Custom Resource Definition), прежде чем будут применены манифесты CR (Custom Resources). Тоже самое касается директории operators — описанный нами CR не обработается, если поды оператора еще не запущены. К тому же, некоторые операторы (например, CloudNativePG с его ресурсами резервных копий) имеют validating webhook — мы обязаны сначала развернуть оператор, а затем применить его Custom Resources.
Получается, что такое разделение дает:
- логичную последовательность применения;
- отдельный статус каждого слоя;
- независимые retry и timeout;
- понятную локализацию ошибок;
- отдельные права для ServiceAccount;
clusters/
Посмотрим на каталог clusters/:
# Описываем каждый существующий кластер
├── dev-cluster
│ ├── infra
# В каждой папке описан kustomization, в котором cсылаемся на соответствующий bundle из корневой директории infra/. При необходимости в этом kustomization можно добавить или перезаписать параметры для конкретного кластера.
│ │ ├── apps
│ │ ├── crds
│ │ ├── operators
│ │ ├── security
# Описывается Kustomization от FluxCD (flux-ks.yaml), где применяется kustomization от Kubernetes для каждой директории, которые описаны для этого кластера (например, путь до приложений ./clusters/dev-cluster/infra/apps). Указываются нужные dependsOn, то есть:
# crds -> (security: kyverno -> kyverno-policy) -> operators -> apps
│ │ └── flux-ks.yaml
│ └── kustomization.yaml
В файле flux-ks.yaml наборы из apps, crds, operators, security объединяются в единую конфигурацию для кластера. Попутно можно перезаписать или добавить для него параметры окружения.
Мы неспроста описывали манифесты для кластера вне его директории в /infra (bundles/cluster-name), а затем создавали на них ссылки из clusters/. Это сделано на случай, если захочется иметь, например, несколько кластеров в продакшен (например, в разных зонах доступности), но с похожими наборами приложений. Тогда мы подключаем бандл от кластера в продакшен, просто описываем в clusters/ еще одну конфигурацию и при необходимости вносим требуемые изменения.
Высокая связность между манифестами добавляет сложности , и нужно быть готовым погрузиться в архитектуру при разборе инцидентов. Поэтому, в малых проектах в большинстве случаев такая схема не имеет особого смысла, только если нет уверенности в дальнейшем росте проекта.
Манифесты FluxCD
Также в репозитории нет манифестов для контроллеров FluxCD, которые генерируются командой flux bootstrap. Такие манифесты можно держать в отдельном репозитории, в папке clusters/ или, что лучше и удобнее, использовать Flux Operator.
Flux Operator избавляет от хранения сгенерированных манифестов контроллеров Flux. Их состав, версию и настройки описывает компактный ресурс FluxInstance, а оператор управляет установкой и обновлением контроллеров. Сам он и первоначальный FluxInstance все равно должны быть установлены внешним способом — например, с использованием Terraform или OpenTofu. В случае редких установок действовать проще всего вручную через Helm. А ресурс FluxInstance при желании можно размещать в директории clusters/ для каждого кластера. Подробнее — в документации оператора FluxCD.
tenants/
Директория tenants/ предназначена для настройки тенантов, отличных от административных. Посмотрим на ее структуру:
tenants/
└── base
└── dev-team
├── kustomization.yaml
# RBAC, которым flux будет деплоить приложение от разработчиков
├── rbac.yaml
# GitRepository указывает на репозиторий, где находятся манифесты для деплоя бизнес приложения. Его поддерживают разработчики
# Kustomization от fluxcd деплоит это приложение с описанным RBAC
└── sync.yaml
Получается, мы поделили наш кластер Flux’ом на два тенанта: на ops-team (администраторы) и на dev-team (разработчиков бизнес-приложений). При этом, разработчики самостоятельно пишут манифесты в своем репозитории, где хранится код приложений.
Разработчик не обязательно имеет доступ к Kubernetes API. Он работает с разрешенным Git-репозиторием, а Flux применяет изменения с правами ограниченного ServiceAccount. Ролевая модель RBAC ограничивает взаимодействие с Kubernetes API. Для полной безопасности нужны политики доступа и квоты: одними правами доступа нельзя предотвратить, например, создание небезопасного пода или исчерпание ресурсов неймспейса.
Использование отдельного ServiceAccount само по себе не делает Flux мультитенантным. Необходимо также запретить перекрестные ссылки между неймспейсами (cross-namespace references) и исключить возможность применения ресурсов тенанта от имени cluster-admin, что включается в настройках контроллеров Flux с помощью механизма multi-tenancy lockdown.
Подробнее про мультитенантность можно почитать в документации FluxCD на GitHub.
Разделение большого монорепозитория
Монорепозиторий удобен, но при большом количестве кластеров и приложений один невалидный участок может затруднять реконсиляцию или увеличивать размер загружаемого артефакта.
Для особенно крупных репозиториев можно дополнительно использовать git sparse checkout, несколько ресурсов GitRepository для разных путей либо публиковать отдельные OCI‑артефакты.
Разделение стоит вводить тогда, когда оно действительно улучшает изоляцию и эксплуатацию: слишком большое количество мелких Kustomization также усложняет граф зависимостей.
Развертывание бизнес‑приложений
Продолжим тему тенантов. Команда разработки dev-team может не просто применять Flux Kustomizations и HelmReleases из разрешенных ресурсов (GitRepository, OCIRepository and HelmRepository), но также обновлять приложения через ImagePolicy и ImageUpdateAutomation.
В таком случае Flux будет автоматически отслеживать новые теги Docker‑образов и обновлять соответствующие поля в Git-репозитории. Ресурс ImageUpdateAutomation создает коммит с новой версией, после чего обычный процесс реконсиляции применяет изменение. Также можно настроить push коммитов в другую ветку для ручного merge request (по умолчанию Flux отправляет изменения в основную ветку).
Например, в манифесте ничего не поменялось, но выпущен новый OCI‑образ бизнес‑приложения. Если изменения не критичные и не нужно обновлять, скажем, ConfigMap, то можно настроить автоматическое обновление для патч‑версий. Как только образ появятся в Contrainer Registry, FluxCD зафиксирует новую версию и обновит приложение.
Также можно ускорить процесс развертывания с помощью вебхуков, убрав периодическое сканирование Container Registry, если тот их поддерживает. Для этого необходимо настроить Receiver во Flux. Теперь при появлении в Container Registry нового образа, Flux получает сообщение и сразу начинает процедуру обновления.
При использовании GitLab рекомендуется настроить интеграцию, тогда не нужно будет публиковать балансировщик нагрузки для приема вебхуков.
Механизм автообновления образа во Flux по сути изменяет только манифест, в котором находится образ. Это может быть как стандартный ресурс Kubernetes — например, Deployment, Statefulset, Daemonset, — так и Custom Resources от сторонних контроллеров Вот несколько примеров:
- Flagger — наиболее естественная интеграция с Flux, ориентирована на метрики и управление трафиком;
- Argo Rollouts — развитая модель rollout и хороший отдельный UI, не требующий Argo CD;
- Kruise Rollouts — особенно интересен при использовании OpenKruise и расширенных workload-контроллеров.
Каждый из инструментов выше в той или иной степени позволяет реализовать недостающие в базовых ресурсах Kubernetes такие вещи, как стратегии деплоя: canary, A/B и blue/green. То есть возможностей становится больше.
GitHub не тормозит!
В последнее время отдельные участки интернета могут быть временно или полностью недоступны по разным причинам. Лучше иметь копии нужных манифестов или Helm‑чартов в каком-то локальном репозитории. Также стоит рассмотреть зеркала для OCI‑образов, чтобы лишний раз не смотреть, на то как Flux не смог загрузить необходимые данные. По личному опыту могу сказать, что с GitHub тяжело тянуть манифесты CRD через FluxCD, лучше переносить их в локальный репозиторий.
При локальном хранении репозиторий станет только предсказуемее и безопаснее, так как. полностью находится под контролем. Уходит и риск подброса вредоносного манифеста. Напротив, условный GitHub может быть подвержен компрометации, которую невозможно предотвратить не будучи мейнтейнером проекта.
Если нет возможности копировать репозитории, то все равно можно обезопаситься через настройку верификации по OpenPGP-подписям. Проверка PGP у объекта GitRepository подтверждает подпись ревизии, но автоматически не защищает внешние сырые URL или внешние базы Kustomize, загружаемые во время сборки. Для продакшен рекомендуется отключать такие удаленные базы. Подробнее об этом можно прочесть на сайте Flux в статье «Security Best Practices».
Манифесты в OCI — это круто
По возможности следует использовать Helm‑чарты в формате OCI. Они упрощают зеркалирование, поддерживают неизменяемое версионирование (immutable versioning), контроль доступа и проверку подписей. В крупных инсталляциях OCI‑формат также может снизить нагрузку, связанную с постоянным скачиванием и обработкой индекса Helm-репозитория. К сожалению, не во всех Helm-чартах такой формат распространен.
Для большого количества кластеров или при повышенных требованиях к безопасности цепочки поставок (supply chain) можно публиковать проверенные манифесты как OCI‑артефакты. В продакшен‑среде желательно ссылаться на digest либо контролируемый semver-тег и проверять подпись. Git при этом остается исходным источником истины, а OCI становится каналом доставки. При этом снимается нагрузка на сервер Git из-за большого количества запросов со множества кластеров, вызванную контроллерами Flux.
Стоит учитывать, что путь доставки при этом увеличится:
Git → валидация в CI → OCI‑артефакт → подпись → реестр → Flux
Преимущества такого подхода:
- неизменяемая хеш‑сумма образа (immutable digest);
- криптографические подписи и подтверждение происхождения (provenance);
- эффективное зеркалирование согласно формату OCI;
- CI может доставлять в кластер только проверенный результат;
- меньше операций Git при большом количестве кластеров.
Недостатки:
- появляется обязательный CI для сборки и публикации;
- изменения нельзя доставить до сборки артефакта;
- усложняется отладка;
Дополнительные рекомендации
Помимо структуры репозитория и порядка применения ресурсов, при эксплуатации FluxCD стоит обратить внимание на несколько механизмов, которые повышают надежность, безопасность и удобство сопровождения.
Удаление ресурсов с помощью prune
Для Flux Kustomization обычно следует включать:
spec:
prune: true
В этом случае Flux удаляет из кластера ресурсы, которые были применены, но затем удалены из Git‑репозитория. Без включения prune такие объекты останутся в кластере и «осиротеют», так как больше не присутствуют в источнике правды.
Для критичных ресурсов, которые непозволительно случайно удалить, например PVC или Namespace, можно использовать аннотацию:
metadata:
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
Перед включением prune в существующем кластере желательно проверить принадлежность (ownership) ресурсов и результат команды kustomize build, поскольку удаление файла из репозитория после этого может привести к удалению соответствующего объекта из Kubernetes.
Проверка готовности с помощью wait и healthChecks для dependsOn
Параметр dependsOn определяет порядок реконсиляции объектов Flux Kustomization, но полезен в случаях, когда Flux может определить реальную готовность зависимости.
Для большинства инфраструктурных компонентов можно включить:
spec:
wait: true
timeout: 10m
Директива wait: true заставляет Flux дождаться готовности всех поддерживаемых ресурсов в составе Kustomization. Это помогает избежать ситуации, когда Deployment оператора уже создан, но его под или валидирующий вебхук еще не готовы принимать запросы.
Если ждать готовности всех ресурсов не требуется, можно перечислить только ключевые:
spec:
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: kyverno
namespace: kyverno
timeout: 5m
Для Custom Resources, статус которых Flux не может интерпретировать автоматически, существуют механизм healthCheckExprs на основе CEL. Он позволяют считать ресурс готовым только при выполнении определенного условия в поле status.
Такая проверка особенно полезна в сложных цепочках зависимостей, например:
- CRD → оператор → конфигурация оператора → приложение;
- Kyverno → политики → остальные приложения;
- service mesh → приложения с внедрением sidecar‑контейнеров.
Подробнее о проверках готовности и CEL в dependsOs — в статье о кастомизации на сайте FluxCD.
Тайм-ауты и повторные попытки
У каждого Flux Kustomization желательно задавать явные интервалы. Во‑первых, это убережет от их переопределения при выходе новых версий Flux, а во‑вторых, упростит ревью, так все значения видны и не придется догадываться, о действующих интервалах, заданных по умолчанию:
spec:
interval: 30m
retryInterval: 2m
timeout: 10m
Параметр interval задает частоту плановой реконсиляции, retryInterval — задержку перед повторной попыткой в случае ошибки, а timeout ограничивает продолжительность применения манифестов и проверок готовности.
Для HelmRelease также стоит определить поведение при временных ошибках установки или обновления:
spec:
interval: 30m
install:
strategy:
name: RetryOnFailure
retryInterval: 2m
upgrade:
strategy:
name: RetryOnFailure
retryInterval: 2m
Стратегия RetryOnFailure полезна при временной недоступности вебхуков, API или внешних зависимостей. Выбирать ее нужно с учетом особенностей приложения: автоматические откат (rollback) или удаление (uninstall) могут быть опасны для stateful-систем.
Дрейф конфигурации в HelmRelease
Flux Kustomization регулярно исправляет расхождение между Git‑репозиторием и действительным состоянием кластера. Для ресурсов, установленных через Helm, аналогичное поведение можно включить в настройках HelmRelease:
spec:
driftDetection:
mode: enabled
После этого helm-controller будет обнаруживать и исправлять ручные изменения ресурсов Helm-релиза.
Иногда отдельные поля изменяются другими контроллерами легитимно. Например, количество реплик может управляться HPA. Такие поля нужно исключать из проверок:
spec:
driftDetection:
mode: enabled
ignore:
- paths:
- /spec/replicas
target:
kind: Deployment
Иначе Flux и другой контроллер могут постоянно перезаписывать изменения друг друга.
Уведомления об ошибках
Flux умеет отправлять события через Provider и Alert в Telegram, Slack, Microsoft Teams, GitLab, GitHub и другие системы.
Небольшая, но при этом полезная настройка — уведомления обо всех ошибках реконсиляции:
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: reconciliation-errors
namespace: flux-system
spec:
providerRef:
name: operations
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
- kind: HelmRelease
name: "*"
- kind: GitRepository
name: "*"
- kind: OCIRepository
name: "*"
Особенно важно отслеживать следующие сбои:
- невозможность получить данные из Git‑репозитория или OCI‑артефакта;
- ошибки при обновлении Helm‑релизов;
- неготовность заявленных зависимостей;
- ошибки применения Custom Resources;
- неуспешную проверку подписи;
- проблемы с расшифровкой секретов.
Периодический опрос и самовосстановление не заменяют мониторинг: Flux может продолжать повторные попытки несколько часов, пока команда не заметит проблему.
Приостановка реконсиляции
Перед ручным обслуживанием ресурса реконсиляцию можно временно приостановить, а затем возобновить:
flux suspend kustomization infra-apps
flux resume kustomization infra-apps
Аналогичные команды существуют для HelmRelease, GitRepository и других Flux-ресурсов.
Также можно реконсиляцию запускать принудительно:
flux reconcile kustomization infra-apps --with-source
Важно не забывать возобновлять приостановленные ресурсы. Иначе изменения перестают применяться, а дрейф конфигураций — исправляться. Поэтому для продакшен полезно настроить алерты на длительно находящиеся в приостановленном состоянии объекты.
Автоподстановка переменных в манифесты
Если один бандл применяется в нескольких кластерах, специфичные для них значения можно хранить в ConfigMap или Secret:
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-settings
namespace: flux-system
data:
cluster_name: prod-ru-1
cluster_domain: prod.example.ru
environment: production
И подключать в Kustomization:
spec:
postBuild:
substituteFrom:
- kind: ConfigMap
name: cluster-settings
После этого в манифестах можно использовать синтаксис подстановки:
value: ${cluster_name}
Секретные значения следует получать не из ConfigMap, а из Secret, который хранит их в зашифрованном виде. При этом нужно помнить, что механизм substitution просто выполняет строковую подстановку (envsubst), а не работает, как полноценный шаблонизатор. Но все же кое‑какие дополнительные возможности у него есть.
Чтобы изменение ConfigMap или Secret вызывало немедленную реконсиляцию, можно добавить специальную метку:
metadata:
labels:
reconcile.fluxcd.io/watch: Enabled
Без этой метки изменение также будет обнаружено, но только во время следующей плановой реконсиляции.
Минусы у подхода postBuild.substitution тоже есть.
Во‑первых, усложняется отладка, так как трудно определить итоговое значение в сгенерированном манифесте, особенно при большой вложенности;
Во‑вторых, числа со знаком минуса (например, ID чата в Telegram) воспринимаются как математические выражения для envsubst, поэтому их приходится экранировать, передавая строку вместе с кавычками: “\”-100500123\””;
В третьих, некоторые приложения (например, дашборды Grafana), в чужих ConfigMap используют символ $, который совпадает с оператором подстановки во Flux. Тут два варианта: либо дублирование $$ (тогда передастся один знак $), либо отключить подстановку для всего файла через аннотацию:
kustomize.toolkit.fluxcd.io/substitute: disabled
Управление секретами
Секреты в открытом виде нельзя хранить в Git даже в приватном репозитории: они остаются в истории, копиях и артефактах CI.
Flux поддерживает встроенную расшифровку SOPS:
spec:
decryption:
provider: sops
secretRef:
name: sops-age
Другой вариант — External Secrets Operator, который получает секреты из Vault, Bitwarden Secrets Manager или другого облачного хранилища секретов.
Выбор зависит от модели эксплуатации:
- SOPS — зашифрованный секрет должен версионироваться вместе с приложением;
- External Secrets — значение хранится и ротируется во внешней системе;
- в обоих случаях нужно ограничить доступ контроллеров Flux и сервисных аккаунтов тенанта к секретам других команд.
Дополнительно желательно проверять историю Git с помощью Gitleaks или аналогичного инструмента.
Проверка подписей и неизменяемые ссылки
Для продакшен недостаточно просто скачать артефакт из доверенного реестра, желательно проверять его подпись.
OCIRepository поддерживает Cosign и Notation:
spec:
verify:
provider: cosign
matchOIDCIdentity:
- issuer: "^https://token.actions.githubusercontent.com$"
subject: "^https://github.com/example/platform/.*$"
В продакшен для артефактов лучше использовать неизменяемые digest‑суммы:
spec:
ref:
digest: sha256:...
Semver-теги также допустимы для контролируемого автоматического обновления, но mutable-теги (вроде, latest) дают более слабые гарантии воспроизводимости.
Надежная цепочка доставки выглядит так:
Git → валидация вCI → OCI‑артефакт → подпись → реестр → верификация во Flux
Проверка подписи гарантирует подлинность артефакта, но не проверяет корректность его содержимого. Поэтому она должна дополнять код‑ревью, валидацию схем и проверку политик безопасности.
Валидация до слияния
Flux неизбежно обнаружит невалидный манифест во время реконсиляции, но работающий продуктовый кластер — неподходящее место для первичной проверки.
Поэтому важно построить CI таким образом, чтобы манифесты проходили какую-то предварительную проверку на валидацию. Можно рассмотреть такие инструменты:
- yamllint — для синтаксиса и оформления;
- kubeconform — для Kubernetes и CRD‑схем;
- cuelang — в качестве более расширенного валидатора схем, альтернатива kubeconform;
- Kyverno CLI или Conftest — для тестирования политик безопасности;
- проверка отсутствия незашифрованных секретов в открытом виде;
- поиск устаревших версий API;
В kubeconform мы заметили какой-то баг: процесс просто зависает при большом количестве манифестов, отследить тяжело.В последних версиях исправления так и не появилось. Если требуется полная надежность, лучше валидировать схему с помощью cuelang.
Ограничение внешних зависимостей Kustomize
Kustomize позволяет ссылаться напрямую на удаленные Git-репозитории, например:
resources:
- https://github.com/example/project//deploy?ref=main
Для продакшен это нежелательно:
- внешнее содержимое может измениться без явного коммита в основном репозитории;
- реконсиляция зависит от доступности постороннего сервиса;
- проверка подписи основного ресурса GitRepository не распространяется на внешние источники;
- сборка становится менее воспроизводимой.
Безопаснее скопировать зависимость в контролируемый репозиторий или опубликовать его как подписанный OCI‑артефакт. В мультитенантных окружениях также рекомендуется запускать kustomize-controller с запретом на использование внешних источников.
Заключение
Мы прошлись по основам GitOps на FluxCD:
- структуру монорепозитория с разделением на infra/, clusters/ и tenants/;
- порядок развертывания бизнес‑приложений с помощью dependsOn;
- автоматическое обновление образов.
Затем разобрали набор практик для работы в продуктовом контуре:
- удаление ресурсов с помощью prune,
- проверку готовности,
- защиту от дрейфа конфигурации,
- настройку уведомлений об ошибках,
- управление секретами,
- проверку подписей;
- валидацию до стадии слияния.
Как видно, ничего запредельно сложного в FluxCD нет — это обычный набор манифестов и несколько выработанных привычек.
Если из статьи запомнить всего пять вещей, пусть это будут следующие.
- Структура репозитория решает половину проблем. Разделяйте слои: CRD‑манифесты → операторы → инфраструктура → бизнес-приложения. Не сваливайте все в один объект Kustomization.
- Использование dependsOn без явного ожидания и проверок готовности не создает зависимость. Flux должен уметь определять реальную готовность, иначе порядок применения — иллюзия.
- Активация prune требует продуманные исключения. Git — единственный источник правды, но критичные ресурсы (PVC, Namespace) нуждаются в дополнительной защите.
- Валидация манифестов должна проходить до их применения в кластере. В этом помогут kubeconform или cuelang, тестирование политик безопасности и проверка на оставленные открытыми секреты в CI. Помните: продуктовый контур — не место для первичной проверки манифеста.
- Настройте уведомления об ошибках реконсиляции. Flux может молча повторять попытки часами, пока никто не заметит неполадок.
Если что-то пошло не так, чаще всего причина — в одном из нижеперечисленного:
- ничего не появляется в кластере — или ресурс приостановили и забыли возобновить, или ошибка в цепочке dependsOn выше по графу, или истекло время ожидания зависимости;
- проблемы с сетью и Git — недоступность репозитория (например, GitHub), слишком большой монорепозиторий без .spec.ignore, а также скачивание внешних зависимостей во время сборки;
- ресурс неожиданно удален — включена очистка (prune), а файл убрали из репозитория, не проверив принадлежность (ownership) ресурсов и результаты сборки Kustomize;
- постоянный конфликт контроллеров — механизм выявления расхождений (drift detection) перезаписывает то, что легитимно меняет HPA или другой оператор, что решается исключением подобных полей из проверок;
- substitution-грабли — неэкранированный $ в конфигурациях (например, в дашбордах Grafana или ID чатов Telegram) и большая вложенность postBuild.substitute, из-за чего непонятно, какое именно значение попало в итоговый манифест.
Все это — не «карма FluxCD», а вполне диагностируемые вещи. Команды flux get kustomizations, flux reconcile –with-source и события в kubectl describe почти всегда указывают на причину.
Не бойтесь начинать с малого: один кластер, один бандл, минимальная цепочка зависимостей — и итеративно усложняйте, когда почувствуете необходимость. Опыт, описанный выше, набивался ровно так же, постепенно. У вас все получится — FluxCD действительно проще, чем кажется с первого взгляда.
Если хотите углубиться:
- документация FluxCD — особенно разделы Repository Structure, Security Best Practices и Multi-tenancy;
- пример мультитенантной конфигурации.
Опробовать FluxCD можно на базе нашего сервиса Managed Kubernetes.