Helm Charts and Template Basics — Part 2

Image for post
Image for post

By Alwyn Botha, Alibaba Cloud Community Blog author.

This tutorial explains how the deployment.yaml Helm template gets converted from template to YAML manifest.

It does not follow the traditional programming reference guide: statements and functions listed in alphabetical order.

It explains template syntax as needed to explain deployment.yaml top to bottom.

Using values in templates

Here is the complete deployment.yaml for reference:

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myhelm1.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}

terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}

command: ['sh', '-c', 'sleep 60']

{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

Extract of this deployment.yaml :

app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
replicas: {{ .Values.replicaCount }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}

.Release.Name and .Release.Service are built in objects — complete list at https://docs.helm.sh/chart_template_guide/ ( Unfortunately there are no links to specific parts of their VERY long web pages — you have to scroll down to find it ) … Built-in Objects

Below is how the final deployment.yaml gets rendered if you use:

helm install .\myhelm1\ --name test5 --dry-run --debug

For this top 50% part of the tutorial you do not run any commands, just read. ( Even debug has too much output if all we want is to investigate the syntax of a 3 line template. )

( After you have read the full tutorial once you may want to read it again, but this time editing the template and value files and running helm install with debug to play / learn )

app.kubernetes.io/instance: test5
app.kubernetes.io/managed-by: Tiller
app.kubernetes.io/instance: test5
- name: myhelm1
image: "radial/busyboxplus:base"
imagePullPolicy: IfNotPresent

These 3 values below are pulled in from the values.yaml file:

image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}

Extract of values.yaml

image:
repository: radial/busyboxplus
tag: base
pullPolicy: IfNotPresent

- name: {{ .Chart.Name }} is from the Chart.yaml file. Its contents is shown below.

apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: myhelm1
version: 0.1.0

Probably half of all templates are just such simple values replacements.

You have seen that within YAML files we have template directives embedded in {{ and }}.

You must have blank space after opening {{ and a blank space before closing }}.

Those leading dots are needed.

The values that are passed into a template can be thought of as namespaced objects, where a dot (.) separates each namespaced element.

The leading dot before Chart indicates that we start with the top-most namespace. Read .Chart.name as “start at the top namespace, find the Chart object, then look inside of it for an object called name”.

with

**values.yaml** extract: 

Gets rendered as:

nodeSelector:
disktype: ssd
gpu: Nvidia

VERY important: spaces have syntax meaning in YAML.

The rendered two selectors have 8 spaces before them since in deployment.yaml we have {{- toYaml . | nindent 8 }}

nindent 8 indents it 8 spaces.

with is a loop construct. With the values in .Values.nodeSelector: convert it toYaml.

That dot after toYaml is the current value of .Values.nodeSelector in the loop. It must be there.

Consider it similar to sum(1,34,454) … as toYaml(.) … it is the value of the parm passed.

The pipe symbol | works as you are familiar in Linux shell.

affinity: and tolerations: with work exactly the same.

Unfortunately these examples do not show how with is also a current scope modifier. That is explained well at MODIFYING SCOPE USING WITH

Except for include you now fully understand how the whole deployment.yaml gets rendering by using values from values.yaml, Chart.yaml, and Built-in Objects.

The full service.yaml below:

You now fully understand it as well.

apiVersion: v1
kind: Service
metadata:
name: {{ include "myhelm1.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

Helm variables and range

Extract of first and last 3 lines of ingress.yaml

{{- if .Values.ingress.enabled -}}
{{- $fullName := include "myhelm1.fullname" . -}}
{{- $ingressPaths := .Values.ingress.paths -}}
... rest of yaml ....
{{- end }}
{{- end }}
{{- end }}

All the content of ingress.yaml is wrapped in a big if … starting at line 1 and ending at very last line. If ingress enabled is false NO yaml content gets generated — as we want it to be.

Line 2 and 3 demonstrates how to declare Helm template variables.

Note the hyphens at {{ and }}

Those hyphens/dashes eat whitespace characters. {{- eats all whitespaces to the left

-}} means whitespace to the right should be consumed — including the newline — the line is entirely removed.

Extract of values.yaml

ingress:
enabled: false
hosts:
- chart-example.local
tls:
- secretName: chart-example-tls
hosts:
- chart-example.local-1
- chart-example.local-2
- chart-example.local-3

Extract of deployment.yaml :

{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}

Gets rendered as:

spec:
tls:
- hosts:
- "chart-example.local-1"
- "chart-example.local-2"
- "chart-example.local-3"
secretName: chart-example-tls

Note how the range loop generates the list of hosts. The quote surrounds each host with quotes.

There is also a range .Values.ingress.tls loop that loops only once. Giving this loop 3 values will demonstrate how it will range over the values.

Extract of **values.yaml** 

Gets rendered as:

tls:
- hosts:
- "chart-example.local-1-a"
- "chart-example.local-2-a"
- "chart-example.local-3-a"
secretName: chart-example-tls-a
- hosts:
- "chart-example.local-1-b"
- "chart-example.local-2-b"
secretName: chart-example-tls-b
- hosts:
- "chart-example.local-1-c"
- "chart-example.local-2-c"
- "chart-example.local-3-c"
- "chart-example.local-4-c"
secretName: chart-example-tls-c

importance of -

Original template with hyphens.

{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}

template with hyphens removed:

{{ if .Values.ingress.tls }}
tls:
{{ range .Values.ingress.tls }}
- hosts:
{{ range .hosts }}
- {{ . | quote }}
{{ end }}
secretName: {{ .secretName }}
{{ end }}
{{ end }}

Renders as:

tls:
- hosts:

You need to have the space-and-line-end eating hyphens.

_helpers.tpl

We now understand how values are used to generate all our templates.

One exception: name: {{ include “myhelm1.fullname” . }} — the include.

We now explore the include

We use include to include other templates into our YAML templates.

The file _helpers.tpl is the standard way to define several short template snippets we want to include in other templates.

Here is the first named template in _helpers.tpl :

{{- define "myhelm1.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

On the first line we give the template snippet a name. We then refer to this name to include it.

The second line gives “myhelm1.name” a default value: .Chart.Name

If the default value does not exist “myhelm1.name” gets the value of .Values.nameOverride

trunc 63 truncates it to 63 characters in length.

trimSuffix “-” removes ONE trailing — if one exists.

but

trimSuffix “ — “ removes only two trailing — if it exists.

( It does not work as in some programming languages where trim will remove all trailing characters )

app.kubernetes.io/name: {{ include “myhelm1.name” . }}

render as

app.kubernetes.io/name: myhelm1

Next: the template code

helm.sh/chart: {{ include “myhelm1.chart” . }}

renders as

helm.sh/chart: myhelm1–0.1.0

This is the template function :

{{- define "myhelm1.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

printf “%s-%s” .Chart.Name .Chart.Version concatenates .Chart.Name and .Chart.Version — plus it places a hyphen between the two.

replace “+” “_” replaces plus characters with underscores.

Now that you can understand those two one-liner functions you should be able to understand the 10-liner define “myhelm1.fullname” with ease.

If you have any programming experience you will see the if/else works as expected:

if condition
do something
else
do something else
end

The only difference is the {{ and }} template syntax.

learn Helm template syntax fast

The official Helm documentation contains detailed reference information about charts and templates.

The Chart Developer’s Guide : https://docs.helm.sh/developing_charts/

The Chart Template Developer’s Guide : https://docs.helm.sh/chart_template_guide/

It may take a day to read each page. The best way to learn is via interactive use.

This part of the tutorial explains how to learn Helm interactively. It is an ugly but fast hack.

The hack involves:

  • edit as few files as possible
  • only show as few rendered template lines as possible
  • do no live installs, only debug dry-runs

Let’s convert our current chart files to this as quickly as possible — remember ugly and hack is FAST.

Edit your values.yaml file to look like below:

replicaCount: 1

Ensure ./myhelm1/.helmignore contains these lines at bottom:

NOTES.txt
test-connection.yaml
service.yaml
ingress.yaml

Make deployment.yaml content as below:

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}

terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}

ALL we need is some ( even INVALID ) yaml to play with.

Do a dry run:

helm install .\myhelm1\  --name test5 --dry-run --debug

Output too long as shown below:

PS C:\k8> helm install .\myhelm1\  --name test5 --dry-run --debug
[debug] Created tunnel using local port: '50327'

Get rid of the first few ‘useless’ lines via grep.

helm install .\myhelm1\  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

See below. This is all we need: some values to play with and some yaml template content to play with template syntax.

USER-SUPPLIED VALUES:
{}

Now go wild and learn some syntax:

See below deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec: #-------------------->> learn spacing << ------------------------
replicas1: {{ .Values.replicaCount }}
replicas2: {{ .Values.replicaCount }}
replicas3: {{ .Values.replicaCount }}
replicas4: '{{ .Values.replicaCount }}'
replicas5: "{{ .Values.replicaCount }}"
replicas6: "{{ .Values.replicaCount }}"
replicas7: "{{ .Values.replicaCount }}"
replicas: "{{ .Values.replicaCount }}'
replicas: '{{ .Values.replicaCount }}"
replicas: {{ .Values.replicaCount }}"
replicas: "{{ .Values.replicaCount }}

Output:

spec: #-------------------->> learn spacing << ------------------------
replicas1: 1
replicas2: 1
replicas3: 1
replicas4: '1'
replicas5: "1"
replicas6: "1"
replicas7: "1"
template:
spec:
containers:
- name: myhelm1
image1: "radial/busyboxplus:base"
image2: "radial/busyboxplus base"
image3: "radial/busyboxplusbase"
image4: radial/busyboxplusbase

See how much syntax you learned in seconds.

Make mistakes so you can learn what errors really mean. You also learn when error message helps and when it misleads.

Edit deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec: #
replicas1: {{ .Values.replicaCount }}

Dry run:

helm install .\myhelm1\  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

READ, understand and fix error, resubmit.

Error: parse error in "myhelm1/templates/deployment.yaml": template: myhelm1/templates/deployment.yaml:21: unexpected . after term "."

READ, understand and fix error, resubmit.

Error: render error in "myhelm1/templates/deployment.yaml": template: myhelm1/templates/deployment.yaml:20:36: executing "myhelm1/templates/deployment.yaml" at 
<.Valu.image.pullPoli...>: can't evaluate field image in type interface {}

Some different syntax experiments below:

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec: #
replicas1: {{ .Values.replicaCount }}

See the extra 3 lines at bottom — use those — characters to remove it. Remove all 3 lines.

Helm is not as interactive as Python but this way you can nearly make it so.

Gets rendered as:

containers:
- name: myhelm1
image1: "radial/busyboxplus:base"

Another trick. See imagePullPolicy 1 to 3 looks the same. What did we do? You can ugly hack titles like this:

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: {{ include "myhelm1.name" . }}
helm.sh/chart: {{ include "myhelm1.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec: #
replicas1: {{ .Values.replicaCount }}

Output:

- name: myhelm1
image1: "radial/busyboxplus:base"

Note comments does not help. Template syntax inside comments gets interpreted.

Remove the {{ and }} always works. Then you can look at your source template code and the result on the following line in the output.

Best way to show titles of what is been tested seem to be to use { { and { } instead of {{ and }} .. policy 1 below.

{ and } also works in titles and look very similar to read syntax of {{ and }}

imagePullPolicy1: { { quote .Values.image.pullPolicy } }
imagePullPolicy1: {{ quote .Values.image.pullPolicy }}

this gets rendered as :

imagePullPolicy1: { { quote .Values.image.pullPolicy } }
imagePullPolicy1: "IfNotPresent"

learn from other templates

There are nearly 300 superb Helm chart and template examples at the official Helm repository at https://github.com/helm/charts

You want to learn from the best: these people.

You will see that after just these 2 tutorials you are already able to understand more than 80% of all the template coding. ( But these 2 tutorials covered maybe 10 percent of the syntax ).

You now have 4 independent, different ways of learning charts and templates:

  • the official Helm reference docs
  • these 2 practical tutorials
  • 300 superb Helm chart examples
  • the fast hack method that you can use to cut-paste and speed learn those 3 sources via quick interactive exercises

Helm reference docs are low-level focussed on detail.

Look for structural chart design insights while looking at the Helm chart repository.

From https://github.com/helm/charts/blob/master/stable/lamp/templates/NOTES.txt

See how the lamp chart only displays certain notes help text when .Values.ingress.enabled

{{- if .Values.ingress.enabled }}

Most other charts use this idea since charts are infinitely flexible: many features to enable or disable.

Another example use of displaying NOTES.txt based on what the user enabled:

From https://github.com/helm/charts/blob/master/stable/lamp/templates/NOTES.txt

1. You can now connect to the following services:

Another frequently used method is to warn users if a chart is set up incorrectly or insecurely.

From https://github.com/helm/charts/blob/master/stable/mongodb/templates/NOTES.txt

{{- if contains .Values.service.type "LoadBalancer" }}
{{- if not .Values.mongodbRootPassword }}
-------------------------------------------------------------------------------
WARNING

Perfect example of how to handle .Values.service.type “NodePort” or “LoadBalancer” or “ClusterIP”

From https://github.com/helm/charts/blob/master/stable/mongodb/templates/NOTES.txt

To connect to your database from outside the cluster execute the following commands:

These examples are from spending less than 3 minutes looking at some chart examples. You can easily spend a day there ( and it will be worth it … if you make notes ).

how to use .Files.Get

At https://docs.helm.sh/chart_template_guide/ … Accessing Files Inside Templates … ( cannot link to that paragraph directly ) there are several examples of how to include files inside templates.

The Helm repo has 80 examples of how to use .Files.Get

https://github.com/helm/charts/search?utf8=%E2%9C%93&q=.Files.Get&type=

In the first 10 results I found 5 different uses of .Files.Get.

To learn more about Helm, visit https://github.com/helm/charts/tree/master/stable

Original Source

Written by

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store