helmfile — it’s like a helm for your helm!

Naseem Ullah
3 min readApr 1, 2019

Hi,

So at Transit we’ve been migrating our workloads to GKE. Naturally, we’ve been using helm to package them.

Until recently, we had a pretty OK bash script to install desired releases to various clusters, but wanted a more declarative (and readable!) approach.

My buddy (and CNCF ambassador extraordinaire) Archy told me about helmfile so we checked it out and a few hours of YAML later our bash script was history and we were using helmfile in production to manage all our releases.

Let’s take a look at what works for us.

Environments

So you can declare environments in helmfile, e.g.:

environments:
production:
staging:
test:

Helmfile structure

There’s a couple of ways to split up your helmfiles.
E.g. you could have 1 helmfile per environment, but then again helmfile comes with a -e flag for that.

We went with the approach of having a helmfile.d directory with multiple helmfiles in it, each representing a namespace:

helmfile.d/
├── default.yaml
├── logging.yaml
├── monitoring.yaml
├── rt-data.yaml
├── sqlproxy.yaml
└── tracing.yaml

We will take a look at what one of these looks like, but first check out the values files structure.

Values

It’s worth noting that just like there are many approaches to structuring your helmfile(s), there is a lot of flexibility with regards to how to declare your values. In fact you can have it such that you do not even need separate values files, but instead just declare values directly in the helmfile(s).
However, in our case, we simply re used the values files we had already made for our now deprecated bash script. So the structure is /values/<namespace>/<release>/<env>.yaml as seen here:

values
├── default
│ └── my-nginx-ingress-release
│ ├── test.yaml
│ ├── production.yaml
│ └── staging.yaml
├── logging
│ ├── my-elasticsearch-release
│ │ ├── test.yaml
│ │ ├── production.yaml
│ │ └── staging.yaml
│ └── my-kibana-release
│ ├── test.yaml
│ ├── production.yaml
│ └── staging.yaml
├── monitoring
│ ├── my-grafana-release
│ │ ├── production.yaml
│ │ └── staging.yaml
│ ├── my-prometheus-release
│ │ ├── production.yaml
│ │ └── staging.yaml
│ └── my-prometheus-operator-release
│ └── test.yaml
├── rt-data
│ └── my-rt-server-release
│ ├── test.yaml
│ ├── production.yaml
│ └── staging.yaml
├── sqlproxy
│ └── my-gcloud-sqlproxy-release
│ ├── test.yaml
│ ├── production.yaml
│ └── staging.yaml
└── tracing
└── my-jaeger-release
├── test.yaml
├── production.yaml
└── staging.yaml

Ok great. So now there was something to consider, we did not want every release installed in every environment (notice the values files in the monitoring namespace).

What release goes where

Have a look at the monitoring.yaml helmfile from our helmfile.d directory and thanks to YAML’s readability you‘ll see how we can declare in which environment(s) a given release should be installed (also check out the sweet Release Template (templates:)feature which is covered in the docs).

templates:
monitoring: &monitoring
chart: stable/{{`{{ .Release.Name }}`}}
missingFileHandler: Error
namespace: monitoring
values:
- ../values/monitoring/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}.yaml
releases:- name: my-grafana-release
installed: {{ eq .Environment.Name "staging" "production" }}
<<: *monitoring
- name: my-prometheus-release
installed: {{ eq .Environment.Name "staging" "production" }}
<<: *monitoring
- name: my-prometheus-operator-release
installed: {{ eq .Environment.Name "test" }}
<<: *monitoring

Notice the use of Go templating for the value of installed? Pretty sweet!

Conclusion

Helmfile rocks, a big thank you to everyone working on this amazing tool with special shoutouts to Mumoshu who was very quick to resolve two issues that were getting in the way of the above approach.

So if you are looking for a helm for your helm, swing by https://github.com/roboll/helmfile

--

--