Helm 3.3 и OCI-реестры: переводим чарты в Harbor с semver-стратегией для monorepo
Helm 3.3 вышел с экспериментальной поддержкой OCI-реестров. Переносим чарты в Harbor: версионирование, сканирование Trivy и интеграция с GitLab CI для зависимых чартов в monorepo.
Helm 3.3 выходит с экспериментальной поддержкой OCI-реестров для хранения чартов как стандартных артефактов
Helm 3.3 вышел на прошлой неделе, и среди нескольких технических изменений нас сразу зацепила одна вещь: экспериментальная поддержка OCI-реестров через переменную HELM_EXPERIMENTAL_OCI=1. Идея простая - чарты хранятся в том же реестре что и образы, по тому же протоколу, со всеми вытекающими: единый access control, сканирование на уязвимости, стандартный workflow для продвижения артефактов. У нас Harbor уже стоит в инфраструктуре, так что решили проверить насколько это работоспособно в реальных условиях.
Откуда берётся проблема с chartmuseum
До этого мы держали чарты в chartmuseum. Схема рабочая, но обрастает вопросами: отдельный сервис, отдельный access control, отдельный lifecycle. Когда разработчик пушит образ в Harbor и чарт в chartmuseum - это два разных места с разными правилами. Когда security-команда хочет понять что задеплоено - смотреть нужно в двух местах.
Второй момент - в монорепозитории с несколькими сервисами и зависимыми чартами semver-стратегия быстро становится нетривиальной задачей. Chartmuseum не мешает вам пушить одну и ту же версию дважды - если не следить, получите неконсистентное состояние.
Harbor как OCI-реестр для чартов
Harbor 2.x поддерживает OCI Distribution Spec, что делает его совместимым с новым Helm 3.3 API. Включение простое:
export HELM_EXPERIMENTAL_OCI=1
helm registry login harbor.company.com \
--username robot\$ci-helm \
--password "$HARBOR_TOKEN"
Пуш чарта выглядит так:
helm chart save ./charts/myapp harbor.company.com/charts/myapp:1.4.2
helm chart push harbor.company.com/charts/myapp:1.4.2
Пул и установка:
helm chart pull harbor.company.com/charts/myapp:1.4.2
helm chart export harbor.company.com/charts/myapp:1.4.2 --destination /tmp/charts
helm upgrade --install myapp /tmp/charts/myapp
Интерфейс чуть многословнее старого helm repo add + helm install, но в CI это скрывается в скриптах.
Trivy и сканирование чартов
Вот где OCI-подход даёт практический плюс. Harbor умеет запускать Trivy не только на образы, но и на чарты в OCI-формате. При пуше чарта в репозиторий с настроенной политикой сканирования Harbor автоматически проверяет его содержимое.
Что именно сканируется в чарте через Trivy: образы, которые чарт объявляет в values.yaml в полях image.repository + image.tag. Trivy вытаскивает эти ссылки и проверяет их по своим базам CVE. Работает не для всех шаблонов одинаково - если образ задаётся через сложный lookup или условный хелпер, Trivy его может пропустить. Но для стандартной структуры values.yaml с явными полями - работает.
Политику блокировки деплоя при CRITICAL-уязвимостях мы пока не включали на все проекты - слишком много легаси-образов с хвостами. Включили как advisory: Harbor помечает чарт статусом сканирования, CI читает этот статус и пишет в отчёт.
Semver-стратегия для зависимых чартов в monorepo
Здесь самое интересное по части process. У нас monorepo с тремя сервисами: api, worker, frontend. Плюс общий umbrella-чарт, который зависит от всех трёх через Chart.yaml:
dependencies:
- name: api
version: "~1.4.0"
repository: "oci://harbor.company.com/charts"
- name: worker
version: "~2.1.0"
repository: "oci://harbor.company.com/charts"
- name: frontend
version: "~3.0.0"
repository: "oci://harbor.company.com/charts"
Правила, которые мы выработали для команды:
Патч-версия (1.4.x) - изменения только в шаблонах без смены контракта: добавление аннотации, правка resource limits, новый sidecar без нового параметра в values.
Минорная версия (1.x.0) - новый опциональный параметр в values с дефолтным значением. Обратная совместимость сохранена - старый values.yaml без нового параметра работает.
Мажорная версия (x.0.0) - переименование параметра, удаление параметра, смена структуры values. Требует явного обновления зависимого umbrella-чарта.
В GitLab CI версию чарта мы выводим из git tag. Tag api/v1.4.2 триггерит pipeline только для api-чарта с версией 1.4.2. Umbrella-чарт версионируется отдельно и собирается при изменении любого из дочерних - там CI сам обновляет Chart.lock через helm dependency update.
build-chart:
stage: package
script:
- export HELM_EXPERIMENTAL_OCI=1
- CHART_VERSION=$(echo $CI_COMMIT_TAG | sed 's|.*/v||')
- CHART_NAME=$(echo $CI_COMMIT_TAG | cut -d/ -f1)
- helm chart save ./charts/$CHART_NAME harbor.company.com/charts/$CHART_NAME:$CHART_VERSION
- helm chart push harbor.company.com/charts/$CHART_NAME:$CHART_VERSION
only:
- tags
Тильда в ~1.4.0 в dependencies означает «любая патч-версия из 1.4.x» - это позволяет автоматически подтягивать патч-обновления без ручного bump umbrella-чарта. Минорные и мажорные требуют явного изменения.
Что работает, что нет
OCI-поддержка в Helm 3.3 помечена как экспериментальная, и это честная маркировка. helm search по OCI-реестру не работает - нельзя сделать helm search repo и увидеть список чартов. Harbor UI показывает артефакты, но через свой интерфейс. Для discovery в CI это не проблема - версии прибиты явно. Но для операционного обзора «что вообще есть в реестре» нужен либо Harbor API, либо отдельный скрипт.
helm dependency update с OCI-зависимостями работает с экспериментальным флагом. На CI-раннерах нужно убедиться что переменная пробрасывается в каждый шаг где используется helm.
Где сейчас
Один production-проект переведён, несколько в процессе. Единый реестр для образов и чартов в Harbor под managed-сопровождением упрощает audit trail: когда нужно понять что именно задеплоено в конкретный момент - смотришь в одно место. Trivy-сканирование настроено, CRITICAL-блокировку добавим после разбора хвостов в легаси-образах.
Semver-стратегия пока живёт в wiki. Задача следующего месяца - сделать CI-проверку что версия чарта в теге соответствует version в Chart.yaml, иначе это регулярно расходится при ручных коммитах.