HelmWave – GitOps для твоего Kubernetes
2026-03-10 23:46 Diff

Helm, как и Docker стал де-факто стандартом в индустрии. Тоже самое и с Kubernetes (52% доля в нише). И новость, что Docker is deprecated, вызвало волну обсуждений в сообществе. Настолько все привыкли к Docker.

Для Docker есть замечательный по своей простоте docker-compose, в котором мы можем декларативно описать, что мы хотим от Docker. Для Kubernetes набор yaml-tpl файлов упаковывается в архив. И затем этот архив называется Helm-чартом. Но как это часто бывает приложение не может быть описано лишь одним Helm чартом. Требуется как-то управлять/композить/настраивать/шаблонизировать такие сеты.

Одним из подходов по управлению является Umbrella Chart. Это helm chart который объединяет в себе все другие чарты.

Очевидные минусы данного решения:

  1. Требуется поддерживать дополнительный чарт.
  2. Новый слой согласования имен values переменных.
  3. Umbrella-chart это все тот же чарт, поэтому о шаблонизации values и декларативном разделении на контуры (Окружения) не может быть и речи.
  4. Когда обновляется саб-чарт, нужно идти в umbrella и обновлять еще версию umbrella чарта.

Helmwave возник, как инструмент для декларативного описания всех чартов в одном yaml. Этот пост покажет как можно решить основные проблемы (use-cases) с помощью helmwave.

Что такое HelmWave?

  1. Это бинарь, который устанавливает helm release из helmwave.yml.
  2. Кладешь helmwave.yml в git и применяешь его через CI.
  3. Можно шаблонизировать все c помощью (Go template), начиная от helmwave.yml до values.
  4. Helmwave понимает какие helm-repositories ему понадобятся для деплоя. И вытесняет лишние.

Порядок команд

graph TD; Start(helmwave.yml.tpl) --render--> helmwave.yml; helmwave.yml --planfile--> .helmwave; .helmwave --sync--> Finish(Releases have been deployed!)

Быстрый старт

helmwave.yml.tpl имеет следующий вид:

project: my-project version: 0.5.0 repositories: - name: bitnami url: https://charts.bitnami.com/bitnami .options: &options install: true namespace: my-namespace releases: - name: redis-a chart: bitnami/redis options: <<: *options - name: redis-b chart: bitnami/redis options: <<: *options

Поздравляю, вы задеплоили с помощью helmwave!

$ helm list -n my-namespace NAME NAMESPACE REVISION STATUS CHART APP VERSION redis-a my-namespace 1 deployed redis-11.2.3 6.0.9 redis-b my-namespace 1 deployed redis-11.2.3 6.0.9 $ k get po -n my-namespace NAME READY STATUS RESTARTS AGE redis-a-master-0 1/1 Running 0 64s redis-a-slave-0 1/1 Running 0 31s redis-a-slave-1 1/1 Running 0 62s redis-b-master-0 1/1 Running 0 59s redis-b-slave-0 1/1 Running 0 32s redis-b-slave-1 1/1 Running 0 51s

Переменные окружения

  • $HELMWAVE_TPL_FILE – отвечает за путь к входному файлу для шаблонизации (helmwave.yml.tpl).
  • $HELMWAVE_FILE – указывает путь выходного файла после операции шаблонизации (helmwave.yml).
  • $HELMWAVE_PLAN_DIR – указывает путь к папке, в которой хранится или будет хранится план (.helmwave/).
  • $HELMWAVE_TAGS – массив строк, на основании которого будет проводится планирование.
  • $HELMWAVE_PARALLEL – включает/выключает многопоточность (рекомендуется включать).
  • $HELMWAVE_LOG_FORMAT – позволяет выбрать один из предустановленных форматов вывода.
  • $HELMWAVE_LOG_LEVEL – позволяет управлять детализацией вывода.
  • $HELMWAVE_LOG_COLOR – включает/выключает цвета для вывода.

Use-Cases

Примеры будут производиться, опираясь на gitlab-ci. Но это не помешает вам встроить helmwave в любой другой CI-инструмент.

Чем ниже, тем сложнее будут примеры.

Git tag –> Docker tag

Допустим вы написали какой-то helm чарт для нашего приложения. Его values.yaml по умолчанию имеет вид:

image: repository: registry.gitlab.local/example/app tag: master

Необходимо чтобы image.tag брался из переменной CI

Приступим, создадим 2 файла.

. ├── helmwave.yml.tpl └── values.yml

helmwave.yml.tpl

project: my-project # Имя проекта version: 0.5.0 # Версия helmwave releases: - name: my-release chart: my-chart-repo/my-app values: - values.yml options: install: true namespace: my-namespace

values.yml

image: tag: {{ env "CI_COMMIT_TAG" }}

Git commit --> PodAnnotations

Требуется чтобы deployment обновлялся только если у нас есть новый коммит.

deployment имеет примерно этот вид:

... metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} ...

Поэтому мы можем легко расширить предыдущий пример values.yml

image: tag: {{ requiredEnv "CI_COMMIT_TAG" }} podAnnotations: gitCommit: {{ requiredEnv "CI_COMMIT_SHORT_SHA" | quote }}

Контуры, окружения, environments

Структура каталога

. ├── helmwave.yml.tpl └── values ├── _.yml ├── prod.yml └── stage.yml

helmwave.yml.tpl

project: my-project version: 0.5.0 releases: - name: my-release chart: my-chart-repo/my-app values: # Default - values/_.yml # For specific ENVIRONMENT - values/{{ env "CI_ENVIRONMENT_NAME" }}.yml options: install: true namespace: {{ env "CI_ENVIRONMENT_NAME" }}

values/_.yml – Будет запускаться для любого окружения

image: tag: {{ requiredEnv "CI_COMMIT_TAG" }} podAnnotations: gitCommit: {{ requiredEnv "CI_COMMIT_SHORT_SHA" | quote }}

values/prod.yml – Будет запускаться только для prod

values/stage.yml – Будет запускаться только для stage

Используем внешний yaml и .Release.Store

Store -- это просто хранилище, которое можно задавать в helmwave.yml и передавать дальше в шаблонизацию values.

Допустим, мы хотим связать путь к секрету в vault и путь к проекту в gitlab или вы хотите переопределять путь к image.repository. Это можно удобно сделать через Store.

. ├── helmwave.yml.tpl ├── values │ └── _.yml └── vars └── my-list.yaml

values/_.yml

vault: secret/{{ .Release.Store.path }}/{{ requiredEnv "CI_ENVIRONMENT_NAME" }} image: repository: {{ env "CI_REGISTRY" | default "localhost:5000" }}/{{ .Release.Store.path }}

Добавим произвольный yaml файл.

vars/my-list.yaml

releases: - name: adm-api path: main/product/adm/api - name: api path: main/product/api

helmwave.yml.tpl

project: my-project version: 0.5.0 .options: &options install: true wait: true timeout: 5m releases: {{- with readFile "vars/my-list.yaml" | fromYaml | get "releases" }} {{- range $v := . }} - name: {{ $v | get "name" }} chart: my-project/{{ $v | get "name" }} options: <<: *options store: path: {{ $v | get "path" }} # Set .Release.Store.path tags: - {{ $v | get "name" }} - my values: # Default - values/_.yml # For specific ENVIRONMENT - values/{{ env "CI_ENVIRONMENT_NAME" }}.yml {{ end }} {{- end }}

Запускаем!

$ CI_ENVIRONMENT_NAME=stage helmwave planfile

Появится helmwave.yml и папка .helmwave

$ tree .helmwave .helmwave ├── planfile └── values ├── _.yml.adm-api@.plan └── _.yml.api@.plan $ cat .helmwave/values/_.yml.api@.plan vault: secret/main/product/api/stage image: repository: localhost:5000/main/product/api $ cat .helmwave/values/_.yml.adm-api@.plan vault: secret/main/product/adm/api/stage image: repository: localhost:5000/main/product/adm/api

helmwave.yml

project: my-project version: 0.5.0 .options: &options install: true wait: true timeout: 5m releases: - name: adm-api chart: my/adm-api options: <<: *options store: path: main/product/adm/api tags: - adm-api - my values: # Default - values/_.yml # For specific ENVIRONMENT - values/stage.yml - name: api chart: my/api options: <<: *options store: path: main/product/api tags: - api - my values: # Default - values/_.yml # For specific ENVIRONMENT - values/stage.yml

Отделяем продукты от инфраструктуры

Структура проекта

Создадим в папке values 2 папки:

  • product – здесь будут values для продуктов
  • infrastructure – здесь будет инфраструктурные values

values/infrastructure

  • adminer – веб морда для подключения к базе, полезна в основном только в dev-контурах
  • postgresql – база данных
  • ns-ready – здесь LimitRange, ResourcseQuota, Secrets, NetworkPolicy, etc
  • rabbitmq – общая шина между chat и api

values/product Приложение состоит из 3 микросервисов

  • api
  • chat
  • frontend

И еще нам понадобятся 2 отдельных файла описывающие массив product и массив infrastructure.

Структура проекта:

. ├── helmwave.yml.tpl ├── values │ ├── infrastructure │ │ ├── adminer │ │ │ ├── _.yml │ │ │ ├── dev.yml │ │ │ └── stage.yml │ │ ├── ns-ready │ │ │ └── _.yml │ │ ├── postgresql │ │ │ ├── _.yml │ │ │ └── dev.yml │ │ └── rabbitmq │ │ ├── _.yml │ │ ├── dev.yml │ │ └── stage.yml │ └── product │ ├── _ │ │ ├── _.yml │ │ ├── dev.yml │ │ ├── prod.yml │ │ └── stage.yml │ ├── api │ │ ├── _.yml │ │ ├── dev.yml │ │ ├── prod.yml │ │ └── stage.yml │ ├── chat │ │ └── _.yml │ └── frontend │ ├── _.yml │ ├── dev.yml │ ├── prod.yml │ └── stage.yml └── vars ├── infrastructure.yaml └── products.yaml

vars/infrastructure.yaml —

releases: - name: postgresql repo: bitnami version: 8.6.13 - name: adminer repo: cetic version: 0.1.5 - name: rabbitmq repo: bitnami version: 7.6.6 - name: ns-ready repo: my-project version: 0.1.1

vars/products.yaml

releases: - name: adm-api path: rdw/sbs/adm/api - name: frontend path: my-project/internal/frontend - name: api path: my-project/internal/api - name: chat path: my-project/internal/chat

helmwave.yml.tpl

project: my-project version: 0.5.0 repositories: - name: bitnami url: https://charts.bitnami.com/bitnami - name: cetic url: https://cetic.github.io/helm-charts .options: &options install: true wait: true timeout: 5m atomic: false maxhistory: 10 namespace: {{ requiredEnv "HELM_NS" }} releases: {{- with readFile "vars/products.yaml" | fromYaml | get "releases" }} {{- range $v := . }} - name: {{ $v | get "name" }} chart: my-project/{{ $v | get "name" }} options: <<: *options store: path: {{ $v | get "path" }} tags: - {{ $v | get "name" }} - product values: # all products & all envs - values/product/_/_.yml # all products & an env - values/product/_/{{ requiredEnv "CI_ENVIRONMENT" }}.yml # a product & all envs - values/product/{{ $v | get "name" }}/_.yml # a product & an env - values/product/{{ $v | get "name" }}/{{ requiredEnv "CI_ENVIRONMENT" }}.yml {{ end }} {{- end }} {{- with readFile "vars/infrastructure.yaml" | fromYaml | get "releases" }} {{- range $v := . }} - name: {{ $v | get "name" }} chart: {{ $v | get "repo" }}/{{ $v | get "name" }} options: <<: *options chartpathoptions: version: {{ $v | get "version" }} tags: - {{ $v | get "name" }} - infrastructure values: # a svc & all envs - values/infrastructure/{{ $v | get "name" }}/_.yml # a svc & an env - values/infrastructure/{{ $v | get "name" }}/{{ requiredEnv "CI_ENVIRONMENT" }}.yml {{ end }} {{- end }}

Контуры в Store

Допустим, у нас есть 2 окружения dev и prod. И в prod'e нам не нужна база данных.

vars/infrastructure.yaml

releases: - name: rabbitmq repo: stable version: 6.18.2 envs: - _ # all environments tags: - queue - name: postgresql repo: bitnami version: 8.6.13 envs: - dev # only dev tags: - db # vim: set filetype=yaml: {{- $env := requiredEnv "CI_ENVIRONMENT" }} # Look at this first project: insider version: 0.5.0 repositories: - name: stable url: https://kubernetes-charts.storage.googleapis.com - name: bitnami url: https://charts.bitnami.com/bitnami .options: &options install: true wait: true force: false timeout: 5m atomic: false maxhistory: 10 namespace: {{ requiredEnv "HELM_NS" }} releases: {{- with readFile "vars/infrastructure.yaml" | fromYaml | get "releases" }} {{- range $v := . }} {{- $envs := $v | get "envs" }} {{- if or (has "_" $envs) (has $env $envs) }} - name: {{ $v | get "name" }} chart: {{ $v | get "repo" }}/{{ $v | get "name" }} options: <<: *options chartpathoptions: version: {{ $v | get "version" }} tags: - {{ $v | get "name" }} - infrastructure {{- if $v | hasKey "tags" }} - {{ $v | get "tags" | toYaml }} {{- end }} values: # a svc & all envs - values/infrastructure/{{ $v | get "name" }}/_.yml # a svc & an env - values/infrastructure/{{ $v | get "name" }}/{{ $env }}.yml {{ end }} {{- end }} {{- end }}

База по умолчанию выключена.

Чтобы postgresql включился.

$ CI_ENVIRONMENT=dev helmwave planfile

Giltab-CI Pipelines

Рассмотрим шаблон gitlab-ci с использованием helmwave из проекта g-ci

variables: HELMWAVE_LOG_LEVEL: debug .helmwave-deploy: stage: deploy environment: name: ref/$CI_COMMIT_REF_SLUG image: name: diamon/helmwave:0.5.0 entrypoint: [""] script: - helmwave deploy helmwave deploy: extends: .helmwave-deploy

С использованием include

include: https://gitlab.com/g-ci/deploy/-/raw/master/helmwave.yml helmwave deploy: environment: name: prod