HelmWave v0.5.0 – GitOps для твоего Kubernetes

    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
    └── 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


    replicaCount: 6

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


    replicaCount: 2

    Используем внешний 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 }}
    

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


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

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Используете ли вы helm?

    • 80,0%Да24
    • 16,7%Нет5
    • 3,3%Планируем1
    • 0,0%Перестали (почему? напишите в комментариях)0

    Комментарии 6

      +1
      Спасибо и удачи в развитии!
      Вопрос насчет helmfile — имхо, интересная фича одна — «Parallel helm install/upgrade», почему бы просто не сделать PR в helmfile?
        0
        Попробую ответить на ваш вопрос, в духе «почему helmwave, а не helmfile?»
        helmwave использует вкомпиленный helm. То есть вызов install осуществляется через вызов этой же функции в helm. В то время как helmfile делают shell exec к бинарному файлу helm.

        И из-за того что мы используем те же структуры, helmwave не встретит проблем с helm chart options. В то время как большую часть issues в helmfile это как раз запрос на добавление этих параметров.

        Думаю коллегам из sweetops ничего не мешает вызывать горутины в цикле к своей функции install.
          +1
          Интересный подход, спасибо.
            0

            Получается, наличие встроенного бинаря helm в helmwave — это все различия с helmfile на данный момент?

              0
              Присоединяйтесь в чат (https://t.me/helmwave), там по активней будет

              helmfile не позволяет управлять всеми параметрами релиза
              helmfile не понимает какие репозитории действительно нужно устанавливать
              helmwave — использует все абстрации из helm, так как helm вкомпилен. Это очень быстро. И это меньше занимает памяти. И команд ввода вывода.
              helmfile имеет рекурсивных рендер. Что очень сложно/долго рендерится
              helmfile не имеет планфайла :)
                0
                «Вкомпилен» – имеется ввиду вызов той же функции на go что и в оригинальном helm

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое