GitOps без GitHub: Flux v2 OCI + Harbor на отечественной инфраструктуре
Flux v2.0 поддерживает хранение чартов и манифестов в OCI-реестрах. Настраиваем GitOps через Harbor без зависимости от зарубежных сервисов.
Flux v2.0 выпустил стабильную поддержку OCI-артефактов - Helm-чарты и Kustomize-манифесты можно хранить в любом OCI-совместимом реестре без зависимости от GitHub
Flux v2.0 стал стабильным несколько недель назад, и в числе новинок - нормальная поддержка OCI-артефактов. Helm-чарты и Kustomize-манифесты теперь можно хранить прямо в OCI-реестре: push как образ, pull через OCIRepository в Flux, никакого GitRepository-источника из GitHub не нужно. Для нас это важно - большинство клиентов с требованиями по КИИ не могут использовать GitHub или публичные Helm-репозитории. CI/CD должен работать в периметре.
Проблема, которую решает OCI-источник
Стандартный Flux-паттерн - GitRepository или HelmRepository. GitRepository - это git-репозиторий, обычно на GitHub или GitLab. HelmRepository - index.yaml с чартами, обычно на artifacthub.io или где-то снаружи. Оба варианта в закрытом контуре требуют зеркалирования или альтернативы.
Мы уже разбирали GitFlic как замену GitLab - с git-источником всё решаемо. Но с Helm-чартами история сложнее: поддерживать свой HelmRepository с index.yaml - это отдельная инфраструктура, которая постоянно требует внимания. OCI-подход убирает эту прослойку: реестр образов уже есть, чарт кладётся туда же как артефакт, Flux тянет его напрямую.
Что за стек и почему Harbor
На нескольких клиентах мы ведём K8s-кластеры на РЕД ОС в рамках managed-сопровождения. Harbor уже стоял как реестр образов - он входит в реестр отечественного ПО через несколько дистрибутивов, у него есть российская коммерческая поддержка от нескольких вендоров, и он умеет OCI из коробки начиная с версии 2.0. Логично было не добавлять новый компонент, а использовать то, что уже работает.
Flux мы используем с версии 0.x, на GA v2.0 переходили постепенно в течение октября-ноября.
Как это устроено
Схема выглядит так: CI-пайплайн собирает чарт и публикует его в Harbor как OCI-артефакт. Flux в кластере следит за OCIRepository-ресурсом и при появлении нового тега применяет изменения. GitOps-цикл работает, но git хранит только исходники и конфигурацию Flux - не сами чарты.
Публикация чарта из пайплайна:
helm package ./chart --version "${CI_COMMIT_TAG}"
helm push chart-name-${CI_COMMIT_TAG}.tgz oci://harbor.internal/charts
Ресурс в кластере:
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: app-chart
namespace: flux-system
spec:
interval: 5m
url: oci://harbor.internal/charts/chart-name
ref:
semver: ">=1.0.0"
secretRef:
name: harbor-credentials
Поверх него - HelmRelease, который ссылается на этот OCIRepository как источник. Flux опрашивает реестр по интервалу, видит новый тег, подходящий под semver-фильтр, и запускает reconciliation.
Что пришлось разобрать
Аутентификация. Harbor с TLS и внутренним CA - стандартная ситуация. Flux при работе с OCI использует те же механизмы, что и при pull образов: imagePullSecret формата kubernetes.io/dockerconfigjson. Но для OCIRepository секрет указывается в spec.secretRef, а не через serviceaccount - это несколько сбивает с толку, если привык к стандартным image pull secrets. Пришлось покопаться в документации.
Верификация подписи. Flux v2 умеет проверять подпись OCI-артефактов через Cosign. У клиентов нет PKI-инфраструктуры, которая покрывала бы подпись артефактов, поэтому эту функцию пропустили - не блокер для текущих задач.
Semver-фильтр и теги. Flux умеет подписываться на диапазон версий: >=1.2.0 <2.0.0, ~1.3, конкретный тег или latest. Мы используем semver-диапазон для staging (берёт любую новую patch-версию) и точный тег для production (обновляем вручную через коммит в flux-конфиг). Это стандартный GitOps-паттерн, но с OCI он работает приятнее: источник истины - тег в реестре, а не отдельный values.yaml с версией чарта.
Совместимость flux push. Flux CLI умеет публиковать не только чарты, но и Kustomize-директории как OCI-артефакт через flux push artifact. Для инфраструктурных компонентов, которые мы раньше держали как GitRepository + Kustomize, это даёт тот же результат - без зависимости от внешнего git.
Что получилось
CI/CD-цикл теперь замкнут внутри периметра: GitFlic как git-хостинг, GitFlic Runner (или Woodpecker, у одного клиента) как CI, Harbor как реестр и образов, и OCI-чартов, Flux как оператор GitOps. Ни одного обращения наружу в runtime.
Это не полная автономия - upstream-зависимости (образы базовых систем, Helm-чарты сторонних компонентов) по-прежнему надо зеркалировать отдельно. Но оркестрационный слой теперь не требует внешних сервисов.
Мы только начали катить этот подход на клиентские кластеры - первые две среды работают несколько недель. Пока трений меньше, чем ожидали: операторы быстро разобрались, что чарт публикуется как образ, и перестали удивляться oci://-адресам. Посмотрим, как поведёт себя при более частых деплоях и при обновлении самого Flux - v2.0 только вышел, и граблей впереди наверняка хватит.