Про сам helmfile і приклади його використання можна почитати в
Ми ж познайомимося з неочевидними способами описати релізи в helmfile
Припустимо, у нас є пачка helm-чартів (для прикладу нехай буде postgres і якийсь backend додаток) і кілька оточень (кілька kubernetes кластерів, кілька namespace'ів або кілька і того, і іншого). Беремо helmfile, читаємо документацію і починаємо описувати наші оточення та релізи:
.
├── envs
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yaml
helmfile.yaml
environments:
devel:
production:
releases:
- name: postgres
labels:
app: postgres
wait: true
chart: stable/postgresql
version: 8.4.0
values:
- envs/{{ .Environment.Name }}/values/postgres.yaml
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
version: 1.0.5
needs:
- postgres
values:
- envs/{{ .Environment.Name }}/values/backend.yaml
У нас вийшло 2 оточення: розробити, виробництво - У кожному знаходяться свої значення для helm чартів релізів. Ми будемо деплоїти в них так:
helmfile -n <namespace> -e <env> apply
Різні версії helm чартів у різних оточеннях
Що робити, якщо нам треба викочувати різні версії бекенда в різні оточення? Як налаштувати версію релізу? На допомогу приходять значення оточення, доступні через {{ .Values }}
helmfile.yaml
environments:
devel:
+ values:
+ - charts:
+ versions:
+ backend: 1.1.0
production:
+ values:
+ - charts:
+ versions:
+ backend: 1.0.5
...
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
- version: 1.0.5
+ version: {{ .Values.charts.versions.backend }}
...
Різний набір додатків у різних оточеннях
Відмінно, але якщо нам не треба в production
викочувати postgres, тому що ми знаємо, що не треба базу даних пхати в k8s і для прода у нас є чудовий окремий кластер postgres? Для вирішення цієї проблеми ми маємо лейбли (labels)
helmfile -n <namespace> -e devel apply
helmfile -n <namespace> -e production -l app=backend apply
Це здорово, але особисто я вважаю за краще описувати, які програми розгортати в оточенні не за допомогою аргументів запуску, а в описі самих оточень. Що робити? Можна помістити опис релізів в окрему папку, в описі оточення завести список потрібних релізів і "підчіпляти" тільки потрібні релізи, ігноруючи інші
.
├── envs
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
+ ├── releases
+ │ ├── backend.yaml
+ │ └── postgres.yaml
└── helmfile.yaml
helmfile.yaml
environments:
devel:
values:
- charts:
versions:
backend: 1.1.0
- apps:
- postgres
- backend
production:
values:
- charts:
versions:
backend: 1.0.5
- apps:
- backend
- releases:
- - name: postgres
- labels:
- app: postgres
- wait: true
- chart: stable/postgresql
- version: 8.4.0
- values:
- - envs/{{ .Environment.Name }}/values/postgres.yaml
- - name: backend
- labels:
- app: backend
- wait: true
- chart: private-helm-repo/backend
- version: {{ .Values.charts.versions.backend }}
- needs:
- - postgres
- values:
- - envs/{{ .Environment.Name }}/values/backend.yaml
+ ---
+ bases:
+ {{- range .Values.apps }}
+ - releases/{{ . }}.yaml
+ {{- end }}
releases/postgres.yaml
releases:
- name: postgres
labels:
app: postgres
wait: true
chart: stable/postgresql
version: 8.4.0
values:
- envs/{{ .Environment.Name }}/values/postgres.yaml
releases/backend.yaml
releases:
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
version: {{ .Values.charts.versions.backend }}
needs:
- postgres
values:
- envs/{{ .Environment.Name }}/values/backend.yaml
замітка
Під час використання bases:
необхідно обов'язково використовувати yaml роздільник ---
, щоб можна було шаблонізувати releases (та інші частини, типу helmDefaults) значеннями з environments
У такому разі реліз postgres навіть не потрапить до опису для production. Дуже зручно!
Перевизначені глобальні значення для релізів
Звичайно, здорово, що можна для кожного оточення задавати значення для helm чартів, але якщо у нас описано кілька оточень, і ми хочемо, припустимо, задати однаковий для всіх affinity
, але не хочемо налаштовувати його за умовчанням у самих чартах, які зберігаються в ріпах.
У такому разі ми могли б для кожного релізу задати 2 файли з values: перший з дефолтними значеннями, які будуть визначати значення чарту, а другий зі значеннями для оточення, який у свою чергу вже буде визначати дефолтні.
.
├── envs
+ │ ├── default
+ │ │ └── values
+ │ │ ├── backend.yaml
+ │ │ └── postgres.yaml
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
├── releases
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yaml
releases/backend.yaml
releases:
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
version: {{ .Values.charts.versions.backend }}
needs:
- postgres
values:
+ - envs/default/values/backend.yaml
- envs/{{ .Environment.Name }}/values/backend.yaml
envs/default/values/backend.yaml
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- backend
topologyKey: "kubernetes.io/hostname"
Визначення глобальних значень для helm чартів усіх релізів на рівні оточення
Допустимо, у нас у кількох релізах створюються кілька ingress — ми могли б вручну для кожного чарту визначити hosts:
, але в нашому випадку домен один і той же, то чому ж його не винести в якусь глобальну змінну і просто підставляти її значення в чарти? Для цього файли з values, які ми хочемо параметризувати, повинні будуть мати розширення .gotmpl
Щоб helmfile знав, що його треба прогнати через шаблонизатор.
.
├── envs
│ ├── default
│ │ └── values
- │ │ ├── backend.yaml
- │ │ ├── postgres.yaml
+ │ │ ├── backend.yaml.gotmpl
+ │ │ └── postgres.yaml.gotmpl
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
├── releases
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yaml
helmfile.yaml
environments:
devel:
values:
- charts:
versions:
backend: 1.1.0
- apps:
- postgres
- backend
+ - global:
+ ingressDomain: k8s.devel.domain
production:
values:
- charts:
versions:
backend: 1.0.5
- apps:
- backend
+ - global:
+ ingressDomain: production.domain
---
bases:
{{- range .Values.apps }}
- releases/{{ . }}.yaml
{{- end }}
envs/default/values/backend.yaml.gotmpl
ingress:
enabled: true
paths:
- /api
hosts:
- {{ .Values.global.ingressDomain }}
envs/default/values/postgres.yaml.gotmpl
ingress:
enabled: true
paths:
- /
hosts:
- postgres.{{ .Values.global.ingressDomain }}
замітка
Очевидно, що ingress у чарті postgres — це щось вкрай сумнівне, тому в статті це наведено просто як сферичний приклад у вакуумі і для того, щоб не вводити в статтю якийсь новий реліз лише заради опису ingress
Підстановка секретів (secrets) із значень оточення
За аналогією з наведеним вище прикладом можна підставляти і зашифровані за допомогою
.
├── envs
│ ├── default
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ ├── devel
│ │ ├── values
│ │ │ ├── backend.yaml
│ │ │ └── postgres.yaml
+ │ │ └── secrets.yaml
│ └── production
│ ├── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
+ │ └── secrets.yaml
├── releases
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yaml
helmfile.yaml
environments:
devel:
values:
- charts:
versions:
backend: 1.1.0
- apps:
- postgres
- backend
- global:
ingressDomain: k8s.devel.domain
+ secrets:
+ - envs/devel/secrets.yaml
production:
values:
- charts:
versions:
backend: 1.0.5
- apps:
- backend
- global:
ingressDomain: production.domain
+ secrets:
+ - envs/production/secrets.yaml
---
bases:
{{- range .Values.apps }}
- releases/{{ . }}.yaml
{{- end }}
envs/devel/secrets.yaml
secrets:
elastic:
password: ENC[AES256_GCM,data:hjCB,iv:Z1P6/6xBJgJoKLJ0UUVfqZ80o4L84jvZfM+uH9gBelc=,tag:dGqQlCZnLdRAGoJSj63rBQ==,type:int]
...
envs/production/secrets.yaml
secrets:
elastic:
password: ENC[AES256_GCM,data:ZB/VpTFk8f0=,iv:EA//oT1Cb5wNFigTDOz3nA80qD9UwTjK5cpUwLnEXjs=,tag:hMdIUaqLRA8zuFBd82bz6A==,type:str]
...
envs/default/values/backend.yaml.gotmpl
elasticsearch:
host: elasticsearch
port: 9200
password: {{ .Values | getOrNil "secrets.elastic.password" | default "password" }}
envs/devel/values/backend.yaml
elasticsearch:
host: elastic-0.devel.domain
envs/production/values/backend.yaml
elasticsearch:
host: elastic-0.production.domain
замітка
До речі, getOrNil
— спеціальна функція для go шаблонів у helmfile, яка, навіть якщо .Values.secrets
не існуватиме, не викине помилку, а дозволить в результаті за допомогою функції default
підставити значення за замовчуванням
Висновок
Описані речі здаються досить очевидними, але інформація щодо зручного опису деплою в кілька оточень за допомогою helmfile дуже мізерна, а я люблю IaC(Infrastructure-as-Code) і хочу мати чіткий опис стейту деплою.
Насамкінець хочу додати, що змінні для оточення default можна в свою чергу параметризувати змінними оточення ОС якогось раннера, з якого буде запускатися деплою, і таким чином отримати динамічні оточення
helmfile.yaml
environments:
default:
values:
- global:
clusterDomain: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
ingressDomain: {{ env "INGRESS_DOMAIN" }}
Джерело: habr.com