Taking a Tour of Helmfile
Happy New Year's Eve everyone!
Well, the blog's been live for a few weeks now, and I've left you lovely folks hanging with a single post about how the blog is hosted. Well, what better way to ring in the new year than writing about one of my favorite infrastructure as code languages, Helm.
As every good DevOps engineer should, I maintain a small home lab for various side projects, from home automation, to infrastructure that comes in handy for other projects (e.g. running my own GitLab runners so I don't run out of CI/CD minutes on their free plan). Also, being a big fan of Kubernetes, I run most of my home lab applications on a small K3s cluster run on some Raspberry Pi's and other old hardware I have laying around. I'm planning to do a post that dives in depth on my K3s set up, but for now I want to talk about something that I find more interesting than that: Helmfile.
Now those familiar with K8s have probably heard about a tool called "Helm". Helm is an infrastructure as code lanuage that builds on top of the basic Kube manifest YAML framework and offers nice Go templating, variables, control and reptition structures, and more. Helm has largely become the community's favourite in terms of IaC solutions for K8s, and thus, has helm "charts" available for popular applications like NGINX, MongoDB, Kafka, etc. This makes it easy for the end user to simply grab a helm chart to deploy what they need, configure as they need it, without needing to worry about building YAML around the docker image for the app they need.
Now, you're probably wondering what Helmfile means with relation to Helm. Well, just as Docker Compose is a way to combine and orchestrate docker containers for local development set ups, Helmfile can be thought of as a way to combine an orchestrate Helm releases that depend on each other. This works particularly well for a home lab as you can have a single Helmfile that describes your home lab set up and maintain the state of your home lab centrally with a simple helmfile apply
.
Enough with the hypotheticals though, let's dig into some code! Let me show you the current state of my home lab's Helmfile:
1repositories:
2- name: k8s-at-home
3 url: https://k8s-at-home.com/charts/
4- name: gitlab
5 url: https://charts.gitlab.io
6- name: longhorn
7 url: https://charts.longhorn.io
8
9helmDefaults:
10 kubeContext: default
11
12releases:
13 - name: longhorn
14 namespace: longhorn-system
15 chart: longhorn/longhorn
16
17 - name: home-assistant
18 namespace: home-automation
19 chart: k8s-at-home/home-assistant
20 needs:
21 - longhorn-system/longhorn
22 values:
23 - persistence:
24 config:
25 enabled: true
26 storageClass: longhorn
27
28 - name: adguard-home
29 namespace: home-automation
30 chart: k8s-at-home/adguard-home
31
32 - name: gitlab-runner
33 namespace: default
34 chart: gitlab/gitlab-runner
35 values:
36 - imagePullPolicy: IfNotPresent
37
38 gitlabUrl: https://gitlab.com/
39
40 concurrent: 3
41
42 rbac:
43 create: true
44
45 runners:
46 tags: "home-lab"
47 image: ubuntu:20.04
48 secret: gitlab-runner-secret
49
50 namespace: default
51
52 builds:
53 cpuLimit: 200m
54 cpuLimitOverwriteMaxAllowed: 400m
55 memoryLimit: 256Mi
56 memoryLimitOverwriteMaxAllowed: 512Mi
57 cpuRequests: 100m
58 cpuRequestsOverwriteMaxAllowed: 200m
59 memoryRequests: 128Mi
60 memoryRequestsOverwriteMaxAllowed: 256Mi
61
62 services:
63 cpuLimit: 200m
64 memoryLimit: 256Mi
65 cpuRequests: 100m
66 memoryRequests: 128Mi
67
68 helpers:
69 cpuLimit: 200m
70 memoryLimit: 256Mi
71 cpuRequests: 100m
72 memoryRequests: 128Mi
73
74 resources:
75 limits:
76 memory: 256Mi
77 cpu: 200m
78 requests:
79 memory: 128Mi
80 cpu: 100m
Now, let's unpack some of this code for the sake of learning. First off, we have a "repositories" block. This block describes the various helm repositories we want to pull from. This is especially important these days as most helm charts are kept in their own repo now instead of a monorepo.
Next up, we define some defaults, in my case, I'm simply pointing Helmfile at my home lab's Kubernetes context.
Finally, we have a list of releases that we want this Helmfile to keep track of. Now there's a few pieces to explain here. First off, each release always has 3 components: a name, a namespace and a chart to deploy in that release. Those 3 components are pretty basic to Helm as that's really all you need to deploy the default configuration of a helm chart.
Now, since most of the time folks want to customize their helm chart to suit their K8s set up, or their particular needs, we need to include "values", which is helm speak for basically overriding the chart's defaults with some of your own configuration. You can see examples of this in my Home Assistant and GitLab runner chart where I'm including a values YAML right in my helmfile.yaml to tell Helmfile to override these particular settings.
Another important part to Helmfile is its dependency mechanism. You can see this in my Home Assistant release as well. Basically, I persist my Home Assistant config to a K8s PVC, and that PVC is maintained by the Longhorn storage class. So what that means in practice, is my Home Assistant chart must wait for my Longhorn chart as it depends on the resulting storage class from it. This dependency link is shown in the "needs" block in my Home Assistant release.
Once we have this helmfile.yaml created, we can test it out with a few basic helmfile commands:
helmfile diff
will show what exactly will change between what's currently in K8s and what's in the codehelmfile apply
will actually deploy the changes described in the helmfile against the current state of the K8s cluster's helm releases
This pretty much concludes this very brief intro to Helmfile. We've really just scratched the surface of helmfile though, and there's plenty more to learn about it. For those wanting to do so, you can find the official docs here in the Helmfile project's Readme.
See you next time!