Skip to content

Latest commit

 

History

History
444 lines (342 loc) · 10.7 KB

helm-chart-named-templates.md

File metadata and controls

444 lines (342 loc) · 10.7 KB

Helm Chart Named Templates

Learning Goals

  • Use Helm named templates
  • Use context aware named templates
  • Use Helm named templates in pipelines

Introduction

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 Named Templates

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.

Templates Documentation

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.

Exercise

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.

Overview

  • 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.

Step-by-Step

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 the define line:
...
{{ if .resources -}}
resources:
{{- .resources | toYaml | nindent 2 -}}
{{ else }}
...
  • Add a {{- end -}} to delimit the if statement:
    ...
    memory: "1000Mi"
{{- end -}}
{{- end -}}

💡 We end up having two {{- end -}} at the end of the file because we have to delimit both the template define and the if 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 your values.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.

Extra Exercises

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 and sentencesName 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.

Food for thought

  • When should you use templates in Helm?
  • What is a good scope for a template?
  • Can you make a chart too configurable?