Helm Charts

Helm Charts

In this blog, we will see what are Helm charts.

Helm is rather easy to use as a command-line tool. You just tell it to install this, uninstall that, upgrade something, roll back to a previous state, and so on, and it proceeds to do all the heavy lifting behind the scenes. It’s basically an automation tool where we, the human operators, specify our desired end result, the destination. And then it doesn’t matter if 5, 10, 20, or 50 actions are necessary to achieve that end-result, get to that destination, Helm will go through all the required steps without bothering us with the details. But since in the command line we don’t give this tool a lot of info except Hey, I want this installed, how does it know how to achieve this goal?

Helm knows how to do its job with the help of what are called charts. Charts are like an instruction manual for it. By reading and interpreting their contents, it then knows exactly what it has to do to fulfill a user’s request.

As far as we, the human operators are concerned, Helm charts are just a bunch of text files. Each specific file, named in a specific way, has a well-defined purpose. For example, as discussed before, in the values.yaml file we’ll find parameters that we can pass to the chart so that everything gets installed with the configuration options set as we desire. In Chart.yaml we’ll find info about the chart itself, like version numbers for the components we used, dependencies, emails of the maintainers, a description about what the chart does, and so on.

Chart Dependencies

There are also directories (folders) we will find in this structure. For example, in the charts folder, we will find other charts that our main chart may depend on. Something like WordPress could depend on a MariaDB/MySQL chart, so a database server can be made available before your other chart components get installed. This nesting of charts in charts makes our life a lot easier. If this wouldn’t be possible, we would have to first install the database chart, then maybe some other charts, one by one, until, finally, we could install our main app. In practice, for something like WordPress, we would have to enter three commands such as:

helm install some_database_server ...
helm install wordpress_with_http_server ...
helm install memcached ...

But by nesting charts, we can now install everything that is needed, with a single command instead of three. This also means a single command to upgrade all of these components and a single command to uninstall all of them (instead of three).

Besides the fact that with dependency charts we can install 10 different things with a single command, what’s more, important is that it also makes it easier to write instructions in our chart to make sure that all components can magically interconnect so that our app package works perfectly without any extra effort on our part (after installing).

Chart Templates

The most important directory in the Helm charts, however, is the templates folder. At the end of the day, Helm does all its magic and installs an app into our cluster by deploying many different Kubernetes objects, one by one. And it is in the templates folder where it finds the core information about how to do this. For example, for a chart to deploy a WordPress website, it would need to launch an Nginx deployment, a service, maybe a configmap, add some secrets to store passwords, and so on. These are all regular Kubernetes objects. Helm reads files in the templates directory, passes them through its templating engine, and finally generates the object’s declaration (or manifest), the regular kind of output that you would see in a .yaml file that you would add to Kubernetes with a kubectl apply -f deployment.yaml command.

Here’s an example of what files a WordPress helm chart contains in the templates folder:

controlplane ~/wordpress/templates ➜  ll
total 120
drwxr-xr-x 2 root root  4096 Jan 27 08:55 ./
drwxr-xr-x 5 root root  4096 Jan 27 08:55 ../
-rw-r--r-- 1 root root  5721 Jan 26 18:24 NOTES.txt
-rw-r--r-- 1 root root  9774 Jan 26 18:24 _helpers.tpl
-rw-r--r-- 1 root root   716 Jan 26 18:24 config-secret.yaml
-rw-r--r-- 1 root root 19749 Jan 26 18:24 deployment.yaml
-rw-r--r-- 1 root root   770 Jan 26 18:24 externaldb-secrets.yaml
-rw-r--r-- 1 root root   117 Jan 26 18:24 extra-list.yaml
-rw-r--r-- 1 root root  1276 Jan 26 18:24 hpa.yaml
-rw-r--r-- 1 root root   727 Jan 26 18:24 httpd-configmap.yaml
-rw-r--r-- 1 root root  2785 Jan 26 18:24 ingress.yaml
-rw-r--r-- 1 root root  1145 Jan 26 18:24 metrics-svc.yaml
-rw-r--r-- 1 root root  1216 Jan 26 18:24 networkpolicy-backend-ingress.yaml
-rw-r--r-- 1 root root  1300 Jan 26 18:24 networkpolicy-egress.yaml
-rw-r--r-- 1 root root  3440 Jan 26 18:24 networkpolicy-ingress.yaml
-rw-r--r-- 1 root root   883 Jan 26 18:24 pdb.yaml
-rw-r--r-- 1 root root  2007 Jan 26 18:24 postinit-configmap.yaml
-rw-r--r-- 1 root root  1251 Jan 26 18:24 pvc.yaml
-rw-r--r-- 1 root root  1098 Jan 26 18:24 secrets.yaml
-rw-r--r-- 1 root root   826 Jan 26 18:24 serviceaccount.yaml
-rw-r--r-- 1 root root  2073 Jan 26 18:24 servicemonitor.yaml
-rw-r--r-- 1 root root  2705 Jan 26 18:24 svc.yaml
-rw-r--r-- 1 root root  1649 Jan 26 18:24 tls-secrets.yaml

These filenames indicate the same Kubernetes objects we are used to launching with kubectl apply -f commands. We can see a pvc.yaml file, indicating a Persistent Volume Claim, a deployment.yaml file, indicating a Deployment, and so on. But if we open one of these files, we see they’re written in a pretty weird way. Here’s a section from a deployment.yaml file:

        - name: wordpress
          image: {{ template "wordpress.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
          {{- if .Values.command }}
          command: {{- include "common.tplvalues.render" ( dict "value" .Values.command "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.args }}
          args: {{- include "common.tplvalues.render" ( dict "value" .Values.args "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}

Instead of a line like imagePullPolicy: Always we see the weird-looking line imagePullPolicy: {{ .Values.image.pullPolicy | quote }}. This is the templating language for Helm. The syntax is specific to the Go programming language. In this case, the line imagePullPolicy: {{ .Values.image.pullPolicy | quote }} assigns to imagePullPolicy the value that users provided in their values.yaml file.

User-Defined Preferences in values.yaml File

Here’s the relevant part of the values.yaml file:

## @section WordPress Image parameters

## Bitnami WordPress image
## ref: https://hub.docker.com/r/bitnami/wordpress/tags/
## @param image.registry WordPress image registry
## @param image.repository WordPress image repository
## @param image.tag WordPress image tag (immutable tags are recommended)
## @param image.pullPolicy WordPress image pull policy
## @param image.pullSecrets WordPress image pull secrets
## @param image.debug Enable image debug mode
  registry: docker.io
  repository: bitnami/wordpress
  tag: 5.7.2-debian-10-r13
  ## Specify a imagePullPolicy
  ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
  ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images
  pullPolicy: IfNotPresent
  ## Optionally specify an array of imagePullSecrets.
  ## Secrets must be manually created in the namespace.
  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
  ## e.g:
  ## pullSecrets:
  ##   - myRegistryKeySecretName
  pullSecrets: []
  ## Enable debug mode
  debug: false

So whatever value we assign to pullPolicy: here, it gets passed along to the template file, Helm interprets everything and turns a line like imagePullPolicy: {{ .Values.image.pullPolicy | quote }} to imagePullPolicy: "Always" if the user selected Always as his preferred policy in his values.yaml file. In a nutshell, Helm takes files from the “templates” folder, passes them through the templating engine (while also extracting user-defined preferences in the values.yaml file ), and then generates the regular yaml data that gets sent to Kubernetes to create whatever object. 

Instead of having fixed values here, as we do in the regular yaml files we usually deploy into Kubernetes, we have templatized values. Sounds mysterious? Let’s see what this means; it’s actually easy to understand, but the technical term is a bit cryptic.

Helm Templating

The team that builds WordPress can also create charts, so users can install this software product easily in Kubernetes clusters. But this presents a problem. You can’t make a WordPress install, with some fixed settings, that would be perfect for thousands of people. But, instead, you can create a template, a sort of default install, with adjustable settings that everyone can then easily tweak according to their own needs.

The templating language that Helm uses is incredibly versatile and allows us to create very intelligent templates. Besides the simple stuff like taking user-supplied values from the values.yaml file, and use it in these templates to customize the Kubernetes objects that get created, we can even make our templates dynamically generate values themselves. For example, we can add conditions: in case the user has something specific in his/her cluster, we can also make the chart automatically adjust some installation parameters. We can also take user-supplied values and transform them in some way. We can add default values in case the user doesn’t supply any values. One can dynamically generate values based on dynamic things, like today’s date.

Checkout the Helm for the Absolute Beginners course here

Checkout the Complete Kubernetes learning path here