Управление множеством релизов: Helmfile

Когда релизов в кластере десятки, командами helm install их не уследить. Helmfile описывает их все одним декларативным файлом.

Helmfile — отдельный инструмент (не часть Helm), который описывает желаемый набор релизов в файле helmfile.yaml и приводит кластер к этому состоянию одной командой. Это «декларативный Helm поверх Helm».

Проблема, которую решает Helmfile

Реальный кластер — это не один релиз, а набор: ваше приложение, БД, ingress-контроллер, мониторинг, очередь. Держать в голове десяток helm upgrade --install с правильными флагами, версиями и values — источник ошибок. Helmfile превращает это в один версионируемый файл-манифест релизов.

Проблема не только в количестве команд, но и в повторяемости и порядке. Когда деплой живёт в виде bash-скрипта или, хуже, в голове дежурного, неизбежно расходятся окружения: на staging кто-то поставил версию чарта руками и забыл, на prod флаг --set отличается на один символ. Helmfile делает состояние кластера декларативным: то, что лежит в git, и есть то, что должно быть развёрнуто. К этому добавляется управление зависимостями между релизами — БД должна подняться раньше приложения, ingress-контроллер раньше Ingress-ресурсов; Helmfile умеет выстраивать релизы в нужной последовательности, чего голый набор helm install не гарантирует.

Стоит сразу очертить границу применимости. Helmfile — это императивный CLI-инструмент: кто-то (человек или CI-джоба) должен его запустить, чтобы изменения доехали. Он отлично ложится в pull-based пайплайны, но если вы строите полноценный GitOps с контроллером в кластере, который сам подтягивает изменения, то прямой конкурент Helmfile — это Argo CD или Flux. Выбор между ними — отдельный архитектурный вопрос; Helmfile проще и не требует ничего ставить в кластер, контроллеры — мощнее в части автосинхронизации и наблюдаемости дрейфа.

helmfile.yaml

repositories:
  - name: bitnami
    url: https://charts.bitnami.com/bitnami

releases:
  - name: web
    namespace: prod
    chart: ./charts/webapp
    version: 1.2.0
    values:
      - values/base.yaml
      - values/prod.yaml

  - name: orders-db
    namespace: data
    chart: bitnami/postgresql
    version: 13.2.24
    values:
      - values/postgres-prod.yaml

  - name: ingress
    namespace: ingress
    chart: bitnami/nginx-ingress-controller
    version: 11.x.x

Основные команды

helmfile diff      # показать разницу между файлом и кластером (как plan)
helmfile apply     # привести кластер к желаемому состоянию (diff + sync)
helmfile sync      # применить все релизы безусловно
helmfile destroy   # удалить все описанные релизы

helmfile apply — рабочая лошадка: он сначала считает diff, и применяет только изменившиеся релизы. Это похоже на terraform apply, но для Helm-релизов.

Разница между apply и sync на практике важна. sync прогоняет helm upgrade --install по всем релизам безусловно — он надёжен, но медленный и шумный: даже неизменившиеся релизы переустанавливаются, ревизия инкрементируется, в истории появляется лишний шум. apply экономнее: он сначала просит у helm-diff разницу и трогает только то, что реально поменялось, — поэтому в CI обычно используют именно его. Однако у apply есть тонкость: если diff пуст из-за того, что helm-diff чего-то «не увидел» (например, изменения внутри хука или внешнего ресурса), apply может пропустить релиз. Поэтому периодический «выравнивающий» sync — здоровая практика.

Связку helmfile diff в pull request'е стоит выделить отдельно: она превращает ревью инфраструктуры в осмысленный процесс. Ревьюер видит не «поменялся values-файл на три строки», а конкретные изменения в манифестах кластера — какой Deployment получит новый образ, какому ресурсу поднимут лимиты. Это тот же план-перед-применением, что делает Terraform-воркфлоу безопасным, и одна из главных причин, по которой команды вообще берут Helmfile.

Окружения внутри Helmfile

Helmfile умеет окружения «из коробки» — один файл обслуживает dev/staging/prod:

environments:
  dev:
    values:
      - envs/dev.yaml
  prod:
    values:
      - envs/prod.yaml

releases:
  - name: web
    namespace: {{ .Environment.Name }}
    chart: ./charts/webapp
    values:
      - values/{{ .Environment.Name }}.yaml
helmfile -e prod apply    # применить набор релизов для prod
helmfile -e dev diff      # посмотреть разницу для dev

DRY и шаблонизация helmfile

Helmfile сам поддерживает Go-шаблоны, что убирает дублирование между релизами: общие настройки выносят в якоря YAML или шаблонные переменные. Можно описать «базовый релиз» и переопределять лишь отличия — тот же принцип DRY, но на уровне набора релизов, а не одного чарта.

На практике это разворачивается в несколько приёмов. Когда файл с релизами разрастается, его дробят на части и подключают через helmfiles: — например, отдельный файл на инфраструктуру (ingress, мониторинг) и отдельный на продуктовые сервисы. Повторяющиеся куски — общий список values, лейблы, настройки таймаутов — выносят в YAML-якоря или в шаблонные значения из секции environments, чтобы поменять однажды и применить везде. Здесь же кроется и риск: легко увлечься и навертеть в helmfile.yaml столько Go-шаблонов и условий, что файл станет нечитаемым «вторым языком программирования» поверх чартов. Хорошее правило — держать helmfile тонким: он отвечает за то, какие релизы, где и с какими values, а вся логика рендера манифестов остаётся внутри чартов.

Отдельно стоит сказать про секреты в связке с Helmfile. Подходы из предыдущего урока никуда не деваются: Helmfile интегрируется с плагином helm-secrets и умеет подставлять зашифрованные sops-файлы как обычные values, поэтому единый декларативный файл может ссылаться и на открытые, и на зашифрованные значения, не нарушая GitOps-дисциплину.

Где Helmfile, а где Helm

УровеньИнструмент
Один чарт/релиз: шаблоны, valuesHelm
Набор релизов кластера, окружения, порядокHelmfile

Как Helmfile работает под капотом

Helmfile — это оркестратор поверх CLI Helm. Он читает helmfile.yaml, рендерит его собственные шаблоны (подставляя окружение), и для каждого релиза формирует и вызывает соответствующую команду helm upgrade --install с нужными -f, --version, namespace. helmfile diff опирается на плагин helm-diff, сравнивая желаемые манифесты с текущими. То есть Helmfile ничего не делает с кластером напрямую — он лишь детерминированно генерирует и запускает правильные команды Helm в правильном порядке, обеспечивая, что состояние кластера соответствует декларации в git. Это делает его естественным звеном GitOps-пайплайна.

Частые ошибки

  • Тащить логику чарта в Helmfile. Helmfile управляет набором релизов, а шаблоны манифестов — дело чарта.
  • Забыть плагин helm-diff. Без него helmfile diff/apply не покажет разницу.
  • Не фиксировать версии чартов. Как и в Helm, без version набор «уплывёт».

Итог

  • Helmfile декларативно описывает набор релизов кластера в одном файле; apply приводит кластер к нему.
  • Встроенные окружения (-e prod) и шаблонизация дают DRY на уровне набора релизов.
  • Под капотом — оркестратор, генерирующий правильные команды Helm; естественно ложится в GitOps.
Проверьте себя
1. Какую задачу решает Helmfile поверх Helm?
AШаблонизацию одного манифеста
BДекларативное управление набором релизов кластера одним файлом
CШифрование секретов
DСборку Docker-образов
2. Что делает helmfile apply?
AУдаляет все релизы
BСчитает diff и применяет только изменившиеся релизы — как terraform apply
CТолько показывает разницу
DПересобирает чарты
3. Как Helmfile взаимодействует с кластером под капотом?
AНапрямую через API
BГенерирует и вызывает команды helm upgrade --install в нужном порядке
CЧерез kubectl edit
DЧерез Docker