— nakładka dla , która umożliwia opisanie wielu wdrożeń helm w jednym miejscu, parametryzację ich chartów dla kilku środowisk, a także określenie kolejności ich wdrożenia.
O samym helmfile i przykładach jego zastosowania można poczytać w i .
Zapoznamy się z nieoczywistymi sposobami opisywania wydań w helmfile.
Załóżmy, że mamy zbiór chartów helm (na przykład postgres i jakieś aplikacje backendowe) oraz kilka środowisk (kilka klastrów kubernetes, kilka namespace'ów lub jedno i drugie). Bierzemy helmfile, czytamy dokumentację i zaczynamy opisywać nasze środowiska i wydania:
.
├── envs
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yamlhelmfile.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.yamlUdało nam się stworzyć 2 środowiska: devel, production — w każdym z nich znajdują się własne wartości dla chartów helm wydań. Będziemy wdrażać je w następujący sposób:
helmfile -n -e applyRóżne wersje chartów helm w różnych środowiskach
Co zrobić, jeśli musimy wdrażać różne wersje backendu w różnych środowiskach? Jak parametryzować wersję wydania? Z pomocą przychodzą wartości środowiska, dostępne za pośrednictwem {{ .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 }}
...Różny zestaw aplikacji w różnych środowiskach
Świetnie, ale co jeśli nie musimy wdrażać production postgres, ponieważ wiemy, że nie powinniśmy tej bazy danych mieć w k8s, a dla produkcji mamy wspaniały osobny klaster postgres? Aby rozwiązać ten problem, mamy etykiety (labels)
helmfile -n -e devel apply
helmfile -n -e production -l app=backend applyTo świetnie, ale osobiście wolę opowiedzieć, jakie aplikacje wdrażać w środowisku nie za pomocą argumentów uruchomieniowych, ale w opisie samych środowisk. Co robić? Można umieścić opisy wydań w osobnym folderze, w opisie środowiska stworzyć listę potrzebnych wydań i "podpiąć" tylko potrzebne wydania, ignorując pozostałe.
.
├── 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.yamlreleases/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.yamlNotatka
Podczas korzystania z bases: należy obowiązkowo używać separatora yaml ---, aby można było szablonować wydania (i inne części, takie jak helmDefaults) wartościami z środowisk
W takim przypadku wydanie postgres nawet nie trafi do opisu dla produkcji. Bardzo wygodne!
Nadpisywalne globalne wartości dla wydań
Oczywiście, świetnie, że można dla każdego środowiska ustawiać wartości dla chartów helm, ale co jeśli mamy opisane kilka środowisk i chcemy, powiedzmy, ustawić to samo dla wszystkich affinity, ale nie chcemy ustawiać go domyślnie w samych chartach, które są przechowywane w repozytoriach.
W takim przypadku moglibyśmy dla każdego wydania ustawić 2 pliki z wartościami: pierwszy z domyślnymi wartościami, które będą określać wartości samego chartu, a drugi z wartościami dla środowiska, który z kolei już będzie nadpisywał domyślne.
.
├── 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.yamlreleases/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.yamlenvs/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"Definiowanie globalnych wartości dla chartów helm wszystkich wydań na poziomie środowiska
Załóżmy, że w kilku wydaniach tworzymy kilka ingress – moglibyśmy ręcznie określić dla każdego chartu hosts:, ale w naszym przypadku domena jest ta sama, więc dlaczego nie zdefiniować jej jako globalnej zmiennej i po prostu podstawiać jej wartość do chartów? W tym celu pliki z wartościami, które chcemy parametryzować, powinny mieć rozszerzenie .gotmpl, aby helmfile wiedział, że należy je przeprowadzić przez szablon.
.
├── 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.yamlhelmfile.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 }}Notatka
Jasne jest, że ingress w wykresie postgres jest czymś bardzo wątpliwym, dlatego w artykule przedstawiono go jedynie jako sferyczny przykład w próżni i po to, aby nie wprowadzać w artykuł jakiegoś nowego wydania tylko w celu opisu ingress.
Podstawianie sekretów (secrets) z wartości środowiskowych.
Podobnie jak w powyższym przykładzie, możemy podstawiać również zaszyfrowane przy pomocy wartości. Zamiast tworzyć oddzielny plik secrets dla każdej wersji, w którym definiujemy zaszyfrowane wartości dla wykresu, możemy po prostu określić w domyślnym pliku default.yaml.gotmpl wartości, które będą pobierane z zmiennych zdefiniowanych na poziomie środowisk. A wartości, których nie musimy nikogo ukrywać, możemy spokojnie nadpisać w wartości wydania w konkretnym środowisku.
.
├── 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.yamlhelmfile.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.domainenvs/production/values/backend.yaml
elasticsearch:
host: elastic-0.production.domainNotatka
Przy okazji, getOrNil — specjalna funkcja dla szablonów go w helmfile, która, nawet jeśli .Values.secrets nie będzie istniała, nie wyrzuci błędu, a pozwoli na użycie funkcji default do podstawienia wartości domyślnej
Podsumowanie
Opisane rzeczy zdają się być dość oczywiste, ale informacje na temat wygodnego opisu wdrożenia w kilku środowiskach za pomocą helmfile są bardzo skąpe, a ja lubię IaC (Infrastructure-as-Code) i chcę mieć klarowny opis stanu wdrożenia.
Na zakończenie chciałbym dodać, że zmienne dla środowiska domyślnego można z kolei parametryzować zmiennymi środowiska systemu operacyjnego pewnego wykonawcy, z którego będzie uruchamiane wdrożenie, i w ten sposób uzyskać dynamiczne środowiska.
helmfile.yaml
environments:
default:
values:
- global:
clusterDomain: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
ingressDomain: {{ env "INGRESS_DOMAIN" }}Źródło: habr.com
