preview


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 с любыми вопросами!