HelmWave – GitOps для твоего Kubernetes | OTUS
🍁Осенние скидки в Otus!
-5% с промокодом hellootus на все курсы октября и ноября до 31.10!
Забрать скидку!

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

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

e7eyjo8j1iuuw5rlntzez5bylbc_1-1801-479199.png

Для 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 ему понадобятся для деплоя. И вытесняет лишние.

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

wl9azeaf_jyhwa_vddo7rlbxzny_1-1801-eb8e09.png

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
    ├── [email protected]
    └── _[email protected]

$ cat .helmwave/values/[email protected]                            
vault: secret/main/product/api/stage                                                               

image:
  repository: localhost:5000/main/product/api

$ cat .helmwave/values/[email protected]                                  
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

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто
Запланируй обучение с выгодой!
Получи скидку 5% на курсы октября и ноября по промокоду hellootus →