Helm Named Templates in Kubernetes [In-Depth Tutorial]


Kubernetes Tutorial

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:

  1. 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".
  2. 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 the include 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 the include 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 the template 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.
NOTE:
It is considered preferable to use 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:

  1. Use helm lint: This command can help you catch issues early on. It checks that your chart follows Helm's best practices.
  2. 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.
  3. Use --debug and --dry-run: These flags for helm install and helm 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.
  4. 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, and kubectl 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

  1. Named Templates in Helm3 provide a way to define reusable chunks of code, which can help in maintaining complex Helm charts.
  2. Named Templates are created using the define directive and are usually stored in the _helpers.tpl file.
  3. The include and template functions are used to call Named Templates. The main difference between them lies in their handling of output and error.
  4. 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.
  5. Helm3 supports the creation of reusable library charts, which can be utilized across multiple application charts.

 

Further Reading

Helm Named Templates

 

Deepak Prasad

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment