In the first post of this small series I wrote about the basic features of Helm, and how it works. Now it’s time to dive deeper into creating your own charts, with a focus on how to write templates. In addition, we’ll look at how overriding values works, which is essential knowledge when working with chart dependencies.
Writing templates
First of all a disclaimer: This blog post will only scratch the surface of the large topic of writing templates for Helm. A far more extensive introduction to this can be found in the official Helm docs.
Templates in Helm are based on Go template language. The basic templating functionality available there is significantly extended using the Sprig template library, and very few Helm-specific enhancements.
In this post, I’m not going into the syntax of templates, as you can far better read it up in the linked docs. Let’s concentrate on the concepts beyond template syntax. This starts with the way to access values, which is done via built-in objects.
Built-in objects
Built-in objects are a way to access several types of values, some of which are directly configured by you, while others are generated dynamically by Helm or taken from other parts of the chart.
The three main built-in objects you’ll most likely use are: Chart
, Release
and Values
.
As the name suggests, the chart built-in provides access to chart metadata, e.g. chart name and version. The same applies to the Release
built-in, which does the same for a release you create when installing the chart.
The by far most important built-in is the Values
object. It gives you access to all the values configured in the values.yaml
of your chart, it’s sub-charts, and any values files or values provided directly on the command line.
Control structures
Without going into details, you should know that Go templates provide the typical control structures you’ll find in nearly all templating languages: if / else
, and range
(loop).
Here’s a simple example:
{{- if .Values.deployment.volumes }}
volumes:
{{- range .Values.deployment.volumes }}
- name: {{ .name }}
secret:
secretName: {{ .secretName }}
{{- end }}
{{- end }}
Additionally, there is also the with
block, which is about narrowing the scope of the context for better readability and more expressive template blocks.
{{- with .Values.deployment }}
strategy:
rollungUpdate:
maxUnavailable: {{ .maxUnavailable }}
maxSurge: {{ .maxSurge }}
revisionHistoryLimit: {{ .revisionHistoryLimit }}
minReadySeconds: {{ .minReadySeconds }}
{{- end }}
Functions
The Go template language provides some basic functions, and the Sprig library add lots more. Here are some examples of functions available to you when writing templates:
default
: allows to provide default values in case of absence of a valuequote
: quotes the value, e.g. for environment variablesb64enc
: base64-encrypt the value, e.g. required in secretstrim
: trim leading & training whitespaces
Pipelines
While functions are valuable themselves, the full power of using them shows up with the possibility to create pipelines from them. Just like you might be used to from command line tools, you “pipe” the output from one function into another, enabling sophisticated transformations. Here’s an example again:
database.readonly: {{ .Values.database.readOnly | default false | quote | base64enc }}
Checking the results
Once you have your templates done, and often already during the process of writing them, you’ll probably want to try out if your templates work as intended.
To do this, Helm offers two different commands which let you check the results: helm lint
and helm template
.
helm lint
checks your charts for possible issues, emitting errors for issues which will make installing the chart fail, and warnings for less critical issues, e.g. when conventions & best practices are violated.
helm template
is actually rendering your template locally (without requiring tiller
), and therefore provides the resulting manifests as output, so you could check directly if the outcome of the rendering matches your expectations.
Tips & tricks
On top of some nice tricks you can find in the official Helm docs, there is one thing a colleague of mine came up with which deals with overriding values when using sub-charts.
Consider you have a sub-chart you build your chart upon, and customize and enhance it. This sub-chart defined a value list like this:
ports:
- name: http
externalPort: 80
internalPort: 8080
- name: management
externalPort: 81
internalPort: 8081
This is the “standard way” you’ll find in several examples.
In the template, e.g. a service.yaml
, it would be used like this:
{{- range .Values.ports }}
- port: {{ .externalPort }}
targetPort: {{ .internalPort }}
type: TCP
name: {{ .name }}
{{- end }}
Now think about how you’re going to add a third port in your own chart.
If you try to add an additional value to the list in your local values.yaml
- like this:
ports:
- name: admin
externalPort: 5000
internalPort: 5000
then you’ll experience that the result is most likely not what you expected. The resulting service will only contain one port, the one you declared in you chart, but none from the sub-chart. This is due to the fact that lists are neither overridden completely, nor merged (which would cause other difficulties).
So how are we going to solve this? Usually the first thing that comes to your mind will be to include the values from the sub-chart into your list. But you’ll discard this immediately, as this is bad case of duplication, and calls for trouble that something changes in the sub-chart.
My colleague came up with the following solutions, which - at least for us - works quite well:
Instead of defining the values as a list, define them as a hash like this:
ports:
http:
externalPort: 80
internalPort: 8080
management:
externalPort: 81
internalPort: 8081
Your template will get a little more complex, as you’ll need to get access to the key and the value of the hash:
{{- range $key, $value .Values.ports }}
- port: {{ $value.externalPort }}
targetPort: {{ $value.internalPort }}
type: TCP
name: {{ $key }}
{{- end }}
If you now add your fancy admin port setting in the same way within your chart, like this:
ports:
admin:
externalPort: 5000
internalPort: 5000
then your result will contain the expected three ports http
, management
and admin
.
Outlook
In this part, we had a quick look at the basics of writing templates for your charts, and learned about how to override list-like values the easy way. The final part of this mini-series, which will be written by my colleague Alex, will cover another interesting topic: How to keep your secrets a secret, i.e. managing values for secrets with encryption. It’s a very interesting topic, so tune in again…