Helmfile
Usually, an application is composed of many microservices, each of them packaged as a helm chart.
When you want to install or upgrade the application as a whole, how would you proceed?
Running 20+ helm upgrade
doesn't seem like a very interesting job. Moreover, charts may:
- have dependencies on others,
- share some common configurations,
- require a different configuration depending on the environment (demo, prod, etc.),
- need to be installed in a specific order (a service may need a database to be running first to start properly).
Enter helmfile!
What is Helmfile¶
Warning
Even though Helmfile is used in production environments across multiple organizations, it is still in its early stage of development, hence versioned 0.x.
helmfile is a declarative spec for deploying helm charts. With it you can:
- declare all the charts and values part of an application in one place (e.g. a git repo!)
- sync it periodically using CI to avoid skewed environments,
- have multiple environments, with different configurations,
- select/filter releases (very handy for debugging and development),
- declare common values applied to all charts,
- declare an order in which helm charts are installed1,
- only upgrade charts that changed (thanks to helm-diff),
- etc.
Using helmfile requires a bit of practice, especially when it comes to values 2, but I came to love it anyhow.
Deploying with helmfile¶
Declaring our rickroller application using helmfile comes down to the following:
releases:
- name: rickroller
chart: ../helm/rickroller
needs: [mongodb]
values:
- replicas: 1
env:
DATABASE_URL: mongodb://myuser:mypass@mongodb/mydb
- name: mongodb
chart: bitnami/mongodb
version: 13.9.1
values:
- auth:
rootPassword: root
usernames: [myuser]
passwords: [mypass]
databases: [mydb]
persistence:
enabled: false
storageClass: longhorn
size: 1Gi
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
Now that we have declaratively defined our app, we can run:
helmfile list
→ list all the releases. Note that the installed column means "it will be installed, not "it is already present in the cluster"helmfile template
→ runhelm template
on all releases, useful for debugginghelmfile write-values
→ compute and print the values that will be passed to each helm release, useful for debugginghelmfile sync
→ runhelm upgrade --install
on all releaseshelmfile apply
→ look at what is already present in the cluster, and runhelm upgrade --install
only on releases that changedhelmfile diff
→ only run the diffhelmfile destroy
→ uninstall all releases
One of the things I use most is the ability to select specific releases when running commands:
-l
,--selector stringArray
Only run using the releases that match labels. Labels can take the form of
foo=bar
orfoo!=bar
. A release must match all labels in a group in order to be used. Multiple groups can be specified at once."--selector tier=frontend,tier!=proxy --selector tier=backend"
will match all frontend, non-proxy releases AND all backend releases. The name of a release can be used as a label:"--selector name=myrelease"
In our example, we could thus only template the rickroller release using:
helmfile -l name=rickroller template
Warning
The labels here refer to helmfile labels that can be added to any release in the state file.
They have nothing to do with Kubernetes .metadata.labels
, and are only used by internally helmfile.
This ability makes it easier for developers to work with big helmfiles, and is a big advantage against
an umbrella chart. As we discussed in the last chapter, umbrella charts are another way to pack many different
helm charts into one project. There is, however, no easy way to work on only one release with umbrella chart,
except by using conditions
(which need to be all turned off, except for the one we are interested in).
Using environments¶
There are many other features of helmfile, for example, the ability to compose state files, define environments, etc.
In our example, let's say we have three environments:
sandbox
(default) → we only want to deploy rickroller without any databasedev
→ we want to deploy mongodb as well, but without any persistence (no StatefulSet)prod
→ we need mongodb to use persistence, it is production!
Here is the helmfile we could use (see helmfile/helmfile-env.yaml
):
environments:
# we will use the default environment as our sandbox
default:
dev:
prod:
# we can also have values, which will be available
# in helmfile templates as .Values
values:
- mongoPassword: very-strong-password
---
releases:
- name: rickroller
chart: ../helm/rickroller
needs: [mongodb]
values:
- replicas: 1
env:
# pass an empty database url in the default environment
DATABASE_URL: {{ ternary "" "mongodb://myuser:mypass@mongodb/mydb"
(eq .Environment.Name "default") }}
- name: mongodb
chart: bitnami/mongodb
version: 13.9.1
# Only install mongodb in non-default environment
installed: {{ ne .Environment.Name "default" }}
values:
- auth:
rootPassword: root
usernames: [myuser]
passwords:
# read the password from environment values, if provided
- {{ .Values | get "mongoPassword" "mypass" }}
databases: [mydb]
persistence:
# enable persistence for prod only
enabled: {{ eq .Environment.Name "prod" }}
storageClass: longhorn
size: 1Gi
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
Warning
The go templates we are using are interpreted and rendered by helmfile, they are not passed to
helm! Thus, the .Values
are not our usual values, they hold the environments.values
defined in the helmfile environments
section. This is quite confusing at first. So remember: we have two layers of go templates!
To pass a template to the helm chart, we would have to write something like this:
valueAcceptingTemplate: {{ `{{ .Release.Name }}` }}
You can now deploy different flavors using the -e <env>
flag. For example:
helmfile -e prod sync
-
See my article helmfile: understand (and visualize !) the order in which releases are deployed for more details. ↩
-
See my article helmfile: a simple trick to handle values intuitively ↩