Helm, как и Docker стал де-факто стандартом в индустрии. Когда мы обсуждаем Kubernetes (52%). И новость, что Docker is deprecated вызвало волну обсуждений в сообществе. Настолько все привыкли к Docker.
Для Docker есть замечательный по своей простоте docker-compose, в котором мы можем декларативно описать, что мы хотим от Docker.
Для Kubernetes набор yaml-tpl файлов упаковывается в архив. И затем этот архив называется Helm-чартом. Но как это часто бывает приложение не может быть описано лишь одним Helm чартом. Требуется как-то управлять/композить/настраивать/шаблонизировать такие сеты.
Одним из подходов по управлению является Umbrella Chart. Это helm chart который объединяет в себе все другие чарты.
Очевидные минусы данного решения:
- Требуется поддерживать дополнительный чарт
- Новый слой согласования имен values переменных.
- Umbrella-chart это все тот же чарт, поэтому о шаблонизации values и декларативном разделении на контуры (Окружения) не может быть и речи.
- Когда обновляется саб-чарт, нужно идти в umbrella и обновлять еще версию umbrella чарта.
Helmwave возник, как инструмент для декларативного описания всех чартов в одном yaml.
Этот пост покажет как можно решить основные проблемы (use-cases) с помощью helmwave.
Что такое HelmWave?
- Это бинарь, который устанавливает helm release из
helmwave.yml
. - Кладешь
helmwave.yml
в git и применяешь его через CI. - Можно шаблонизировать все c помощью (Go template), начиная от
helmwave.yml
до values. - 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 deploy
Поздравляю, вы задеплоили с помощью 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 help
- $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
L-- 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
L-- values
+-- _.yml
+-- prod.yml
L-- 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
replicaCount: 6
values/stage.yml
– Будет запускаться только для stage
replicaCount: 2
Используем внешний yaml и .Release.Store
Store это просто хранилище, которое можно задавать в helmwave.yml и передавать дальше в шаблонизацию values.
Допустим мы хотим связать путь к секрету в vault и путь к проекту в gitlab или вы хотите переопределять путь к image.repository
. Это можно удобно сделать через Store.
.
+-- helmwave.yml.tpl
+-- values
¦ L-- _.yml
L-- vars
L-- 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
L-- values
+-- _.yml.adm-api@.plan
L-- _.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
¦ ¦ ¦ L-- stage.yml
¦ ¦ +-- ns-ready
¦ ¦ ¦ L-- _.yml
¦ ¦ +-- postgresql
¦ ¦ ¦ +-- _.yml
¦ ¦ ¦ L-- dev.yml
¦ ¦ L-- rabbitmq
¦ ¦ +-- _.yml
¦ ¦ +-- dev.yml
¦ ¦ L-- stage.yml
¦ L-- product
¦ +-- _
¦ ¦ +-- _.yml
¦ ¦ +-- dev.yml
¦ ¦ +-- prod.yml
¦ ¦ L-- stage.yml
¦ +-- api
¦ ¦ +-- _.yml
¦ ¦ +-- dev.yml
¦ ¦ +-- prod.yml
¦ ¦ L-- stage.yml
¦ +-- chat
¦ ¦ L-- _.yml
¦ L-- frontend
¦ +-- _.yml
¦ +-- dev.yml
¦ +-- prod.yml
¦ L-- stage.yml
L-- vars
+-- infrastructure.yaml
L-- 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 }}
База по умолчанию выключена
$ helmwave planfile
Чтобы 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
P.S.
Helmwave source, заходите ставьте звёзды на github
G-CI
Приходите к нам в telegram с любыми вопросами!
eugene08
Спасибо и удачи в развитии!
Вопрос насчет helmfile — имхо, интересная фича одна — «Parallel helm install/upgrade», почему бы просто не сделать PR в helmfile?
ZhilyaevDmitriy Автор
Попробую ответить на ваш вопрос, в духе «почему helmwave, а не helmfile?»
helmwave использует вкомпиленный helm. То есть вызов install осуществляется через вызов этой же функции в helm. В то время как helmfile делают shell exec к бинарному файлу helm.
И из-за того что мы используем те же структуры, helmwave не встретит проблем с helm chart options. В то время как большую часть issues в helmfile это как раз запрос на добавление этих параметров.
Думаю коллегам из sweetops ничего не мешает вызывать горутины в цикле к своей функции install.
eugene08
Интересный подход, спасибо.
teamfighter
Получается, наличие встроенного бинаря helm в helmwave — это все различия с helmfile на данный момент?
ZhilyaevDmitriy Автор
Присоединяйтесь в чат (https://t.me/helmwave), там по активней будет
helmfile не позволяет управлять всеми параметрами релиза
helmfile не понимает какие репозитории действительно нужно устанавливать
helmwave — использует все абстрации из helm, так как helm вкомпилен. Это очень быстро. И это меньше занимает памяти. И команд ввода вывода.
helmfile имеет рекурсивных рендер. Что очень сложно/долго рендерится
helmfile не имеет планфайла :)
ZhilyaevDmitriy Автор
«Вкомпилен» – имеется ввиду вызов той же функции на go что и в оригинальном helm