Introduction to Named Templates in Helm
In Helm, Named Templates are a powerful tool that allows you to define reusable pieces of code. These code blocks can be invoked multiple times throughout a Helm chart, reducing redundancy, and enhancing maintainability.
The concept of Named Templates introduces the principle of Don't Repeat Yourself (DRY) into Helm charts. By defining a common piece of code once and then reusing it wherever required, you create a more efficient, easier-to-maintain structure.
Named Templates are typically defined in a special file named _helpers.tpl
that resides within the templates/
directory of a Helm chart. However, they can technically be defined in any file within the templates/
directory.
The definition of a Named Template looks like this:
{{- define "template-name" -}}
# Your template content here
{{- end -}}
Here, "template-name"
is the name of the template. This is how you will reference the template elsewhere in your Helm chart. The content of the template is placed between the {{- define "template-name" -}}
and {{- end -}}
tags.
For example, if you have a common set of labels that are used in multiple Kubernetes objects within your Helm chart, you might define a Named Template for them like so:
{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
helm.sh/chart: {{ include "mychart.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
In this example, mychart.labels
is the name of the template, and it defines four labels that can be reused in other templates in the Helm chart.
The scope of the variables within the Named Template is limited to the template itself. However, you can pass in variables from the parent scope using the .
(dot) notation, as shown above. The .
represents the current scope, which contains all of the top-level objects like .Release
and .Values
.
Creating Named Templates
Creating a Named Template in Helm involves several steps. Here's a step-by-step guide:
Step 1: Create a Helm Chart
If you don't already have a Helm chart, create one using the helm create
command. This will generate a skeleton chart with all the necessary files.
helm create mychart
Step 2: Navigate to the Templates Directory
Go to the templates/
directory that has been created in your Helm chart.
cd mychart/templates/
Step 3: Open the _helpers.tpl
file
The _helpers.tpl
file is where you'll typically store Named Templates. Open this file in your preferred text editor.
Step 4: Define a Named Template
At the end of the _helpers.tpl
file, define a new Named Template using the define
action. For instance:
{{- define "mychart.standardLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
helm.sh/chart: {{ include "mychart.chart" . }}
{{- end -}}
Here, "mychart.standardLabels" is the name of the Named Template.
Step 5: Save the _helpers.tpl
file
Save the changes you've made to the _helpers.tpl
file.
Step 6: Use the Named Template in Your Chart
You can now use the Named Template in any other template within your Helm chart. To use the Named Template, include it with the include
function, like this:
apiVersion: v1
kind: Service
metadata:
name: {{ include "mychart.name" . }}
labels:
{{- include "mychart.standardLabels" . | nindent 4 }}
spec:
...
Step 7: Verify Your Templates
Use the helm lint
command to check your chart for possible issues:
helm lint ../
Step 8: Test Your Chart
Use the helm install --dry-run --debug
command to test your chart without actually deploying it:
helm install --dry-run --debug myrelease ../
Replace "myrelease" with the release name of your choice.
Using Named Templates
Once you've defined a Named Template, you can use it in any other template within your Helm chart by using the include
function. This function allows you to inject the output of the Named Template into your Kubernetes manifest files.
Here's how you can use a Named Template in your Helm charts:
- Identify the Template Name: Before you can use a Named Template, you need to know its name. In the case of our previous example, the Named Template's name was "mychart.standardLabels".
- Call the Template with the Include Function: Use the
include
function to call the Named Template from another template. You'll pass two arguments to theinclude
function: the name of the Named Template, and the current scope (represented by.
). The scope contains all of the top-level objects available in your Helm chart, such as.Chart
,.Release
, and.Values
.
For example, if you want to use the "mychart.standardLabels" Named Template in a Service object, you would do it like this:
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-service
labels:
{{- include "mychart.standardLabels" . | nindent 4 }}
spec:
...
In this example, the include
function injects the output of the "mychart.standardLabels" Named Template into the labels
field of the Service object. The nindent 4
function ensures the output lines up correctly with the YAML format by adding a new line and four spaces of indentation before the output.
Passing Values to Named Templates
1. Using include function
Named Templates in Helm can accept values passed to them when they're called using the include
function. The values are passed in the form of the scope object. The scope object in Helm can contain a variety of top-level objects such as .Chart
, .Release
, .Values
, and so on.
When you call a Named Template with the include
function, you pass it a scope object. This object is often represented by the .
(dot) character. Here's what it looks like in practice:
{{- include "mychart.standardLabels" . }}
In this case, .
is the scope object, and it represents the current context or scope, which includes all of the top-level objects in your chart. You can think of it like the this
keyword in many programming languages.
But what if you want to pass some additional values to your Named Template? In that case, you can create a new scope object. This object will still have access to the top-level objects, and it can also contain additional values.
For instance, suppose you have a Named Template that creates a label with the name of a component, and you want to use this template for multiple components. You could define the template like this:
{{- define "mychart.componentLabel" -}}
app.kubernetes.io/component: {{ .component }}
{{- end -}}
And then you could use the template for a Service and a Deployment like this:
# For the Service
metadata:
labels:
{{- include "mychart.componentLabel" (dict "component" "webserver") | nindent 4 }}
# For the Deployment
metadata:
labels:
{{- include "mychart.componentLabel" (dict "component" "database") | nindent 4 }}
In this example, the dict
function is used to create a new scope object that includes a component
field. This field is then accessed within the Named Template to set the app.kubernetes.io/component
label. This approach allows you to reuse the same Named Template with different values, making your Helm charts more flexible and DRY (Don't Repeat Yourself).
2. Using template directive
Passing values to Named Templates allows you to create more dynamic templates that can adapt based on the input they receive. When you call a Named Template, you pass a scope object that can contain multiple values. This scope object is typically represented by the .
character, which includes all of the top-level objects in your Helm chart, such as .Chart
, .Release
, and .Values
.
Now let's consider an example where we define a Named Template and then call it using the template
directive:
Define the Named Template: We'll create a Named Template that generates a greeting message based on the input it receives.
{{- define "mychart.greeting" -}}
Hello, {{ .name }}!
{{- end }}
In this example, the Named Template expects the scope object to have a name
field.
Call the Named Template with the template
directive: We can then call this Named Template using the template
directive and pass a custom scope object to it:
message: {{ template "mychart.greeting" (dict "name" "Alice") }}
In this case, we use the dict
function to create a new scope object that contains a name
field. The template
directive then calls the "mychart.greeting" Named Template with this scope object, and the resulting greeting message is directly output into the parent template.
Just like the include
function, the template
directive allows you to pass values to Named Templates, but it directly outputs the result of the Named Template into the parent template. This makes it a valuable tool for creating dynamic Helm charts.
Should you use include or template to call Named Templates?
The include
and template
functions in Helm are similar in that they both allow you to call Named Templates and pass a scope to them. However, there are a few differences that might lead you to choose one over the other depending on the context.
1. Output Handling:
include
: The include
function returns the output of the template as a string. This is useful if you need to further process or manipulate the output. For example, you could pipe the output to another function, or assign it to a variable.
# Using the output as a string
message: "{{ include "mychart.greeting" . }}"
# Piping the output to another function
message: {{ include "mychart.greeting" . | quote }}
template
: The template
function directly writes the output of the template to the output stream. This is useful when you don't need to manipulate the output and you want it to be included directly in your parent template.
# Directly writing to the parent template
message: {{ template "mychart.greeting" . }}
2. Error Handling:
include
: If theinclude
function encounters an error when trying to render a template (e.g., due to a syntax error), it will return an empty string and the error. This means that your template might still render, but with parts of it missing or blank.template
: If thetemplate
function encounters an error, it stops the execution and the error is returned to the user. This means if there is an error in your Named Template, it will stop your whole Helm chart from rendering, which might be what you want if the Named Template is critical to your application.
include
over template
in Helm templates simply so that the output formatting can be handled better for YAML documents.
Control Structures in Named Templates
Control structures in Helm's template language provide powerful ways to manipulate the output of your Named Templates. Helm uses the Go template language, which includes control structures like if
, else
, with
, and range
.
1. if
and else
: These are used to conditionally render parts of your template. For instance, you might want to include certain Kubernetes resources or configurations only when certain values are set.
Here's an example of if
and else
in a Named Template:
{{- define "mychart.customLabel" -}}
{{- if .Values.customLabel }}
customLabel: {{ .Values.customLabel }}
{{- else }}
customLabel: "default"
{{- end }}
{{- end }}
In this example, if the .Values.customLabel
is set, the template will output the custom label value. Otherwise, it outputs a default label.
2. with
: The with
action is similar to if
, but it also changes the context within its block. This is useful when you're accessing a value multiple times.
Here's an example of with
in a Named Template:
{{- define "mychart.labels" -}}
{{- with .Values.labels }}
app: {{ .app }}
environment: {{ .environment }}
{{- end }}
{{- end }}
In this example, .Values.labels
is checked. If it's non-empty, the context within the with
block is changed to .Values.labels
, and you can directly access its subfields .app
and .environment
.
3. range
: The range
action allows you to iterate over arrays or maps. It's useful when you want to generate multiple similar lines, like when creating multiple environment variables from a list.
Here's an example of range
in a Named Template:
{{- define "mychart.environmentVars" -}}
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value }}
{{- end }}
{{- end }}
In this example, if .Values.env
is an array of objects where each object has a name
and value
field, the template will generate a list of environment variables.
Common Functions in Named Templates
Helm templates use the Go template language and come with several built-in functions that can be used to manipulate the output. Below are some commonly used functions that you may find helpful in Named Templates:
default
: The default
function provides a default value if a variable is not set or is empty. Here's an example:
{{- define "mychart.image" -}}
image: {{ default "nginx:latest" .Values.image }}
{{- end }}
In this example, if .Values.image
is not set, the template will use "nginx:latest"
as the default value.
quote
: The quote
function wraps a string in double quotes. It's useful when you want to ensure a value is always treated as a string. Here's an example:
{{- define "mychart.label" -}}
version: {{ .Chart.Version | quote }}
{{- end }}
In this example, .Chart.Version
is converted to a string by wrapping it in double quotes.
tpl
: The tpl
function allows you to render strings as templates. This is useful when you want to use a value from the .Values
file as a template that can reference other values. Here's an example:
{{- define "mychart.message" -}}
message: {{ tpl .Values.message . }}
{{- end }}
In this example, if .Values.message
is "We are using {{ .Chart.Name }} chart"
, the tpl
function would render this string as a template and substitute {{ .Chart.Name }}
with the actual chart name.
nindent
: The nindent
function is similar to indent
, but it adds a new line before the indented text. This can be particularly useful in YAML files, where indentation is significant and you might want to insert a block of text with a specific indentation.
{{- define "mychart.labels" -}}
labels:
{{- include "mychart.commonLabels" . | nindent 4 }}
{{- end }}
required
: The required
function allows you to declare a particular value as required for template rendering. If a required value is not set, Helm will stop and return an error message that you provide.
{{- define "mychart.image" -}}
image: {{ required "You must provide your own image by setting the image value" .Values.image }}
{{- end }}
Example Usage with Debugging Tips
For the sake of this example, I'll assume that you're trying to create a simple chart for deploying a Node.js application. Here's how you can do it:
Create a new Helm chart:
helm create mychart
This will create a new directory named "mychart" with a structure like this:
mychart/ ├── charts ├── Chart.yaml ├── templates │ ├── deployment.yaml │ ├── _helpers.tpl │ ├── hpa.yaml │ ├── ingress.yaml │ ├── NOTES.txt │ ├── serviceaccount.yaml │ ├── service.yaml │ └── tests │ └── test-connection.yaml └── values.yaml 3 directories, 10 files
Edit the _helpers.tpl
file and define a new template that calculates a value.
{{/* Generate a random string */}}
{{- define "mychart.randomString" -}}
{{- printf "%s-%s" .Release.Name (randAlphaNum 5) | lower | trunc 63 | trimSuffix "-" -}}
{{- end -}}
Edit deployment.yaml
, service.yaml
, configmap.yaml
and secret.yaml
(create these last two if they don't already exist), and use the named template to populate some of their fields.
For example, in deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "mychart.randomString" . }}
...
In NOTES.txt
, you can use the named template like this:
1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "mychart.name" . }},app.kubernetes.io/instance={{ template "mychart.randomString" . }}" -o jsonpath="{.items[0].metadata.name}") ...
Finally, you can install the chart into a Kubernetes cluster with a command like this:
$ helm install my-release ./mychart
NAME: my-release
LAST DEPLOYED: Wed Jun 28 17:50:48 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=my-release-w0qb4" -o jsonpath="{.items[0].metadata.name}")
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=my-release" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
After you have deployed your Helm chart, you can inspect the resources that were created to ensure your variable was used correctly. For example, if you used the variable to set the name of a Kubernetes service, you could use the following command to see the service:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11m
my-release-wrvlf ClusterIP 10.111.183.135 <none> 80/TCP 64s
mywebapp ClusterIP 10.100.150.26 <none> 80/TCP 6m13s
Debugging Tips
When working with complex Helm charts, debugging can sometimes be a challenge. Here are some tips:
- Use
helm lint
: This command can help you catch issues early on. It checks that your chart follows Helm's best practices. - Use
helm template
: This command renders your templates, which can help you catch issues before deploying. You can use it to check that your Named Templates are correctly defined and are producing the expected output. - Use
--debug
and--dry-run
: These flags forhelm install
andhelm upgrade
will show you the rendered templates and simulate an install or upgrade, respectively. They won't make any changes to your Kubernetes cluster, but they will show you exactly what Helm would do, which can be very useful for debugging. - Inspect Kubernetes events and logs: If your application isn't behaving as expected, check the events and logs of your Kubernetes resources to see if they provide any clues. Use
kubectl describe
to get a list of events for a resource, andkubectl logs
to get the logs.
Summary
This article covered in-depth how to create and utilize Named Templates in Helm3, including how to define Named Templates, how to call them using both the include
and template
functions, how to pass values to them, and how to use control structures within them. Advanced topics such as creating reusable Helm chart libraries and using Named Templates across multiple Helm charts were also discussed.
Key Takeaways
- Named Templates in Helm3 provide a way to define reusable chunks of code, which can help in maintaining complex Helm charts.
- Named Templates are created using the
define
directive and are usually stored in the_helpers.tpl
file. - The
include
andtemplate
functions are used to call Named Templates. The main difference between them lies in their handling of output and error. - Helm's Go templating language supports a rich set of functions, such as
default
,quote
,tpl
, and others, which can be used within Named Templates. - Helm3 supports the creation of reusable library charts, which can be utilized across multiple application charts.
Further Reading