- Use Helm named templates
- Use context aware named templates
- Use Helm named templates in pipelines
When writing helm templates, we might have chunks of template code that we reuse, instead of copy-pasting, we can use the named templates
feature of golang text templating, to create reusable chunks.
To differentiate between the helm .yaml
files and golang text templating, we will refer to these as templates
and named templates
respectively.
Helm has support for templating chunks of code that you want to reuse.
Named templates are defined with the define
action and the name of the template, and delimited with an end
:
{{ define "myFirstTemplate" }}
foo: bar
{{ end }}
Named templates are injected into your yaml with the template
action and the name of the template:
{{ template "myFirstTemplate" }}
A named templates can have it's own values, but these must be injected when calling the template.
{{ define "myTemplateWithArgs" }}
foo: {{ .Values.bar }}
{{ end }}
When invoking the named template, we must pass an object containing the values, if we want to make the entire .Values
available, we specify the entire context with a 'dot' .
:
{{ template "myTemplateWithArgs" . }}
We can also specify a specific subset for the template to use:
{{ template "myTemplateWithArgs" .Values.myArgs }}
You can place helm templates anywhere in your templates/
directory, but by convention, templates are usually placed in templates/_helpers.tpl
.
Helm will not try to render templates/_helpers.tpl
as part of your chart.
You can use templates in pipelines, but to do so you must use the include
keyword instead of template
:
{{ include "myTemplateWithArgs" .Values.myArgs | indent 4 }}
When using include
you must specify the context for the template to use: {{ include <templateName> <context> }}
💡 This is a limitation of golang templates, you can read more about in the documentation.
In this exercise we will create a named template to default the resource requests and limits for our deployments.
Then we will conditionally override the defaults by using each of the deployments contextual resource definitions.
- Template resources map for deployments
- Conditionally override the template
You can use your Helm chart from the previous exercise as the starting point for this exercise.
Alternatively there is a Helm chart that picks up from the last exercise in helm-katas/helm-chart-named-templates/start
that you can use.
If you get stuck, or you want to see how the final chart looks, there is a solved version of the chart in helm-katas/helm-chart-named-templates/done
.
Steps:
Template resources map for deployments
In a previous exercise we learned how to parameterize the resources
map of our deployments.
Now we would like to have a sensible default for our pod resources, with the ability to override the default on a per service basis.
To do this we will use a named template
.
- Let's create a template file:
touch sentence-app/templates/_helpers.tpl
💡 You can create the file in any way you want, it just has to be placed in the
templates
directory.
- Open the file in your text editor and add the following code:
{{- define "resources" -}}
resources:
requests:
cpu: 0.50
memory: "500Mi"
limits:
cpu: 1.0
memory: "1000Mi"
{{- end -}}
This is just a simple template that will insert the above yaml map.
Let's use it in your sentences deployment.
- edit the file
sentence-app/templates/sentences-deployment.yaml
and change:
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
resources:
{{- .Values.sentences.resources | toYaml | nindent 10 }}
To:
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
{{ template "resources" }}
- Render the template:
helm template sentence-app --show-only templates/sentences-deployment.yaml
---
# Source: sentence-app/templates/sentences-deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
resources:
requests:
cpu: 0.50
memory: "500Mi"
limits:
cpu: 1.0
memory: "1000Mi"
So far so good, but we have to fix the indentation.
In order do to that we will change the template
to an include
in your sentences deployment.
By doing that we can use a pipeline and the nindent
function.
- Change the content of the deployment to the following:
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
{{- include "resources" . | nindent 8 }}
💡 Also notice that we use
{{-
to remove whitespace before the template is injected.
- Render the template again:
helm template sentence-app --show-only templates/sentences-deployment.yaml
---
# Source: sentence-app/templates/sentences-deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
resources:
requests:
cpu: 0.50
memory: "500Mi"
limits:
cpu: 1.0
memory: "1000Mi"
There we go!
Conditionally override the template
But now our sentences deployment will always use the resources map specified in the template.
Let us add a condition so that we can override it:
- Edit your
_helpers.tpl
and add the following if statement below thedefine
line:
...
{{ if .resources -}}
resources:
{{- .resources | toYaml | nindent 2 -}}
{{ else }}
...
- Add a
{{- end -}}
to delimit theif
statement:
...
memory: "1000Mi"
{{- end -}}
{{- end -}}
💡 We end up having two
{{- end -}}
at the end of the file because we have to delimit both the templatedefine
and theif
statement.
The final _helpers.tpl
should look like this:
{{- define "resources" -}}
{{ if .resources -}}
resources:
{{- .resources | toYaml | nindent 2 -}}
{{ else }}
resources:
requests:
cpu: 0.50
memory: "500Mi"
limits:
cpu: 1.0
memory: "1000Mi"
{{- end -}}
{{- end -}}
Now we have modified our template, so that it expects a context
that potentially contains a resources
map.
This means that if the context indeed contains a resources
map, then it will be rendered to yaml and returned, if not the default resources map is returned.
Next we edit our sentences deployment to pass the .Values.sentences
context to the template:
- Change:
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
{{- include "resources" . | nindent 8 }}
To:
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
{{- include "resources" .Values.sentences | nindent 8 }}
Since we defined a resources
map in the values.yaml
a few exercises ago, when we render the template we should see these values being used instead of the template one:
- Render the template:
helm template sentence-app --show-only templates/sentences-deployment.yaml
---
# Source: sentence-app/templates/sentences-deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
resources:
limits:
cpu: 0.5
memory: 500Mi
requests:
cpu: 0.25
memory: 100Mi
- You can try to delete the
resources
map from yourvalues.yaml
and render the template again:
helm template sentence-app --show-only templates/sentences-deployment.yaml
---
# Source: sentence-app/templates/sentences-deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
containers:
- ...
resources:
requests:
cpu: 0.50
memory: "500Mi"
limits:
cpu: 1.0
memory: "1000Mi"
Which means that the template will be used.
The neat thing here is that we can use the same template in the age
and name
deployments.
Now if we do not specify any resource limitations, the defaults will be used, but we can override those simply by adding limitations to the values.yaml
.
If you have more time, or you want to practice using templates a bit more then you can do the extra steps:
Extras
Now that we have created a parameterized, conditional template for the resources map of the deployments in our charts, let's also apply it to the two other deployments in the chart:
- Change the
resources
map of the sentences-age and sentences-name deployments to use the template:
By changing the:
- ...
resources:
requests:
cpu: 0.25
limits:
cpu: 0.25
To:
- ...
{{- include "resources" .Values.sentences | nindent 8 }}
When you make the change you should use the values map of the two other deployments: sentencesAge
and sentencesName
instead of the sentences
map in the when you pass the context to the template: .Values.sentences
.
💡 Note that we use camel case for the deployment names when referencing the values object, as dashes
-
are not allowed in golang object names.
- Render the templates and verify that the age and name deployments use the default values from the
resources
template.
Now we can try to add some custom values to the values.yaml
for the age and name deployments:
- Add a map for the
sentencesAge
andsentencesName
deployments in your values file:
...
sentencesAge:
resources:
requests:
cpu: 1.25
memory: "200Mi"
limits:
cpu: 1.50
memory: "1200Mi"
sentencesName:
resources:
requests:
cpu: 2.25
memory: "500Mi"
limits:
cpu: 2.50
memory: "2200Mi"
- Render the template again, and note that all of the deployments should have different resources specifications.
You can play around with setting some different values for each of the deployments.
- When should you use templates in Helm?
- What is a good scope for a template?
- Can you make a chart too configurable?