β a wrapper for , which allows you to describe multiple helm releases in one place, parameterize their charts for several environments, and set the order of their deployment.
You can read about helmfile and examples of its use in the and .
We will explore some less obvious ways to describe releases in helmfile.
Let's assume we have a batch of helm charts (for example, postgres and some backend application) and several environments (multiple Kubernetes clusters, multiple namespaces, or both). We take helmfile, read the documentation, and start describing our environments and releases:
.
βββ 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.yamlWe have created 2 environments: devel, production β each contains its own values for helm chart releases. We will deploy to them like this:
helmfile -n -e applyDifferent versions of helm charts in different environments
What to do if we need to roll out different versions of the backend in different environments? How to parameterize the release version? Environment-specific values come to our aid, accessible via {{ .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 }}
...Different sets of applications in different environments
Great, but what if we do not want to deploy postgres because we know that we shouldn't put a database in k8s, and we have a wonderful separate postgres cluster for production? To solve this problem, we have labels production helmfile -n -e devel apply helmfile -n -e production -l app=backend apply
helmfile -n -e devel apply
helmfile -n -e production -l app=backend applyThat's great, but personally, I would prefer to describe which applications to deploy in the environment not through launch arguments, but in the description of the environments themselves. What can be done? You can place the release descriptions in a separate folder, maintain a list of the necessary releases in the environment description, and "connect" only the required releases, ignoring the others.
.
βββ 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.yamlNote
When using bases: it is necessary to use yaml separator ---, so that it is possible to template releases (and other parts, like helmDefaults) with values from environments
In that case, the postgres release would not even be included in the description for production. Very convenient!
Overridable global values for releases
Of course, it is great that you can specify values for helm charts for each environment, but what if we have several environments described, and we want to set the same value for all affinity, but do not want to configure it as default in the charts themselves that are stored in the repositories.
In this case, we could specify 2 files with values for each release: the first with default values that will define the values of the chart itself, and the second with values for the environment, which will consequently override the defaults.
.
βββ 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"Defining global values for helm charts of all releases at the environment level
Suppose we have multiple ingress resources created in several releases β we could manually specify for each chart hosts:, but in our case the domain is the same, so why not extract it into a global variable and simply substitute its value in the charts? For this, the files with values that we want to parameterize must have the extension .gotmpl, so that helmfile knows it should pass through the templater.
.
βββ 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 }}Note
It is obvious that ingress in the Postgres chart is quite dubious, so this is presented in the article merely as a spherical example in a vacuum and to avoid introducing a new release just to describe ingress.
Substituting secrets from environment values.
Similarly to the example above, encrypted values can also be substituted using Instead of creating a separate secrets file for each release to define encrypted values for the chart, we can simply define values in the release's default.yaml.gotmpl that will be taken from variables set at the environment level. Values that we don't need to keep secret can be safely overridden in the release values for the specific environment.
.
βββ 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.domainNote
By the way, getOrNil β a special function for Go templates in helmfile, which, even if .Values.secrets does not exist, will not throw an error, but instead will allow using the function default to substitute a default value.
Conclusion
The things described may seem quite obvious, but information on conveniently describing deployments across multiple environments using helmfile is very sparse, and I love IaC (Infrastructure-as-Code) and want a clear description of the deployment state.
In conclusion, I would like to add that the default environment variables can also be parameterized with the OS environment variables of a certain runner from which the deployment will be initiated, and thus dynamic environments can be obtained.
helmfile.yaml
environments:
default:
values:
- global:
clusterDomain: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
ingressDomain: {{ env "INGRESS_DOMAIN" }}Source: habr.com
