A guide to start using Helm
Helm
Table of Contents
Overview
Helm is the package manager for Kubernetes, it provides the ability to provide, share, and use software built for Kubernetes.
Charts
Helm uses a packaging format called charts. A chart is a collection of files that describe a related set of Kubernetes resources. A single chart might be used to deploy something simple, like a memcached pod, or something complex, like a full web app stack with HTTP servers, databases, caches, and so on.
Charts are created as files laid out in a particular directory tree. They can be packaged into versioned archives to be deployed.
The Chart File Structure
A chart is organized as a collection of files inside of a directory. The directory name is the name of the chart (without versioning information). Thus, a chart describing WordPress would be stored in a wordpress/ directory.
Inside of this directory, Helm will expect a structure that matches this:
wordpress/
Chart.yaml # A YAML file containing information about the chart
LICENSE # OPTIONAL: A plain text file containing the license for the chart
README.md # OPTIONAL: A human-readable README file
values.yaml # The default configuration values for this chart
values.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml file
charts/ # A directory containing any charts upon which this chart depends.
crds/ # Custom Resource Definitions
templates/ # A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files.
templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes
The Chart.yaml File
The Chart.yaml file is required for a chart. It contains the following fields:
apiVersion: The chart API version (required)
name: The name of the chart (required)
version: A SemVer 2 version (required)
kubeVersion: A SemVer range of compatible Kubernetes versions (optional)
description: A single-sentence description of this project (optional)
type: The type of the chart (optional)
keywords:
- A list of keywords about this project (optional)
home: The URL of this projects home page (optional)
sources:
- A list of URLs to source code for this project (optional)
dependencies: # A list of the chart requirements (optional)
- name: The name of the chart (nginx)
version: The version of the chart ("1.2.3")
repository: (optional) The repository URL ("https://example.com/charts") or alias ("@repo-name")
condition: (optional) A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
tags: # (optional)
- Tags can be used to group charts for enabling/disabling together
import-values: # (optional)
- ImportValues holds the mapping of source values to parent key to be imported. Each item can be a string or pair of child/parent sublist items.
alias: (optional) Alias to be used for the chart. Useful when you have to add the same chart multiple times
maintainers: # (optional)
- name: The maintainers name (required for each maintainer)
email: The maintainers email (optional for each maintainer)
url: A URL for the maintainer (optional for each maintainer)
icon: A URL to an SVG or PNG image to be used as an icon (optional).
appVersion: The version of the app that this contains (optional). Needn't be SemVer. Quotes recommended.
deprecated: Whether this chart is deprecated (optional, boolean)
annotations:
example: A list of annotations keyed by name (optional).
Chart Dependencies
In Helm, one chart may depend on any number of other charts. These dependencies can be dynamically linked using the dependencies field in Chart.yaml or brought in to the charts/ directory and managed manually.
Managing Dependencies with the dependencies field
The charts required by the current chart are defined as a list in the dependencies field.
dependencies:
- name: apache
version: 1.2.3
repository: https://example.com/charts
- name: mysql
version: 3.2.1
repository: https://another.example.com/charts
- The name field is the name of the chart you want.
- The version field is the version of the chart you want.
- The repository field is the full URL to the chart repository. Note that you must also use helm repo add to add that repo locally.
- You might use the name of the repo instead of URL
$ helm repo add fantastic-charts https://charts.helm.sh/incubator
dependencies:
- name: awesomeness
version: 1.0.0
repository: "@fantastic-charts"
Once you have defined dependencies, you can run helm dependency update and it will use your dependency file to download all the specified charts into your charts/
directory for you.
$ helm dep up foochart
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "local" chart repository
...Successfully got an update from the "stable" chart repository
...Successfully got an update from the "example" chart repository
...Successfully got an update from the "another" chart repository
Update Complete. Happy Helming!
Saving 2 charts
Downloading apache from repo https://example.com/charts
Downloading mysql from repo https://another.example.com/charts
Managing Dependencies manually via the charts/ directory
If more control over dependencies is desired, these dependencies can be expressed explicitly by copying the dependency charts into the charts/ directory.
A dependency should be an unpacked chart directory but its name cannot start with _ or .. Such files are ignored by the chart loader.
For example, if the WordPress chart depends on the Apache chart, the Apache chart (of the correct version) is supplied in the WordPress chart’s charts/ directory:
wordpress:
Chart.yaml
# ...
charts/
apache/
Chart.yaml
# ...
mysql/
Chart.yaml
# ...
The example above shows how the WordPress chart expresses its dependency on Apache and MySQL by including those charts inside of its charts/ directory.
- TIP: To drop a dependency into your charts/ directory, use the helm pull command
Operational aspects of using dependencies
The above sections explain how to specify chart dependencies, but how does this affect chart installation using helm install and helm upgrade?
Suppose that a chart named “A” creates the following Kubernetes objects
- namespace “A-Namespace”
- statefulset “A-StatefulSet”
- service “A-Service”
Furthermore, A is dependent on chart B that creates objects
- namespace “B-Namespace”
- replicaset “B-ReplicaSet”
- service “B-Service”
After installation/upgrade of chart A a single Helm release is created/modified. The release will create/update all of the above Kubernetes objects in the following order:
- A-Namespace
- B-Namespace
- A-Service
- B-Service
- B-ReplicaSet
- A-StatefulSet
This is because when Helm installs/upgrades charts, the Kubernetes objects from the charts and all its dependencies are
aggregated into a single set; then sorted by type followed by name; and then created/updated in that order. Hence a single release is created with all the objects for the chart and its dependencies.
The install order of Kubernetes types is given by the enumeration InstallOrder in kind_sorter.go (see the Helm source file).
Templates and Values
Helm Chart templates are written in the Go template language
, with the addition of 50 or so add-on template functions from the Sprig library and a few other specialized functions.
All template files are stored in a chart’s templates/
folder. When Helm renders the charts, it will pass every file in that directory through the template engine.
Values for the templates are supplied two ways:
- Chart developers may supply a file called values.yaml inside of a chart. This file can contain default values.
- Chart users may supply a YAML file that contains values. This can be provided on the command line with helm install.
When a user supplies custom values, these values will override the values in the chart’s values.yaml file.
Template Files
Template files follow the standard conventions for writing Go templates (see the text/template
Go package documentation for details). An example template file might look something like this:
apiVersion: v1
kind: ReplicationController
metadata:
name: deis-database
namespace: deis
labels:
app.kubernetes.io/managed-by: deis
spec:
replicas: 1
selector:
app.kubernetes.io/name: deis-database
template:
metadata:
labels:
app.kubernetes.io/name: deis-database
spec:
serviceAccount: deis-database
containers:
- name: deis-database
image: /postgres:
imagePullPolicy:
ports:
- containerPort: 5432
env:
- name: DATABASE_STORAGE
value:
The above example, based loosely on https://github.com/deis/charts
, is a template for a Kubernetes replication controller. It can use the following four template values (usually defined in a values.yaml file):
- imageRegistry: The source registry for the Docker image.
- dockerTag: The tag for the docker image.
- pullPolicy: The Kubernetes pull policy.
- storage: The storage backend, whose default is set to “minio”
All of these values are defined by the template author. Helm does not require or dictate parameters.
To see many working charts, check out the CNCF Artifacts Hub.
Predefined Values
Values that are supplied via a values.yaml
file (or via the –set flag) are accessible from the .Values
object in a template. But there are other pre-defined pieces of data you can access in your templates.
The following values are pre-defined, are available to every template, and cannot be overridden. As with all values, the names are case sensitive.
Release.Name
: The name of the release (not the chart)Release.Namespace
: The namespace the chart was released to.Release.Service
: The service that conducted the release.Release.IsUpgrade
: This is set to true if the current operation is an upgrade or rollback.Release.IsInstall
: This is set to true if the current operation is an install.Chart
: The contents of the Chart.yaml. Thus, the chart version is obtainable as Chart.Version and the maintainers are in Chart.Maintainers.Files
: A map-like object containing all non-special files in the chart. This will not give you access to templates, but will give you access to additional files that are present (unless they are excluded using.helmignore
). Files can be accessed usingor using the
function. You can also access the contents of the file as []byte using ``.Capabilities
: A map-like object that contains information about the versions of Kubernetes () and the supported Kubernetes API versions (
)
NOTE: Any unknown Chart.yaml fields will be dropped. They will not be accessible inside of the Chart object. Thus, Chart.yaml cannot be used to pass arbitrarily structured data into the template. The values file can be used for that, though.
Values files
Considering the template in the previous section, a values.yaml
file that supplies the necessary values would look like this:
imageRegistry: "quay.io/deis"
dockerTag: "latest"
pullPolicy: "Always"
storage: "s3"
A values file is formatted in YAML. A chart may include a default values.yaml file. The Helm install command allows a user to override values by supplying additional YAML values:
$ helm install --generate-name --values=myvals.yaml wordpress
When values are passed in this way, they will be merged into the default values file. For example, consider a myvals.yaml
file that looks like this:
storage: "gcs"
When this is merged with the values.yaml
in the chart, the resulting generated content will be:
imageRegistry: "quay.io/deis"
dockerTag: "latest"
pullPolicy: "Always"
storage: "gcs"
Note that only the last field was overridden.
NOTE: The default values file included inside of a chart must be named values.yaml. But files specified on the command line can be named anything.
NOTE: If the –set flag is used on helm install or helm upgrade, those values are simply converted to YAML on the client side.
NOTE: If any required entries in the values file exist, they can be declared as required in the chart template by using the ‘required’ function
Any of these values are then accessible inside of templates using the .Values
object:
apiVersion: v1
kind: ReplicationController
metadata:
name: deis-database
namespace: deis
labels:
app.kubernetes.io/managed-by: deis
spec:
replicas: 1
selector:
app.kubernetes.io/name: deis-database
template:
metadata:
labels:
app.kubernetes.io/name: deis-database
spec:
serviceAccount: deis-database
containers:
- name: deis-database
image: /postgres:
imagePullPolicy:
ports:
- containerPort: 5432
env:
- name: DATABASE_STORAGE
value:
Chart Template Guide
In this section of the guide, we’ll create a chart and then add a first template. The chart we created here will be used throughout the rest of the guide.
To get going, let’s take a brief look at a Helm chart.
Charts
As described in the Charts Guide, Helm charts are structured like this:
mychart/
Chart.yaml
values.yaml
charts/
templates/
...
The
templates/
directory is for template files. When Helm evaluates a chart, it will send all of the files in the templates/ directory through the template rendering engine. It then collects the results of those templates and sends them on to Kubernetes.The
values.yaml
file is also important to templates. This file contains the default values for a chart. These values may be overridden by users during helm install or helm upgrade.The
Chart.yaml
file contains a description of the chart. You can access it from within a template.The
charts/
directory may contain other charts (which we call subcharts). Later in this guide we will see how those work when it comes to template rendering.
A Starter Chart
For this guide, we’ll create a simple chart called mychart, and then we’ll create some templates inside of the chart.
$ helm create mychart
Creating mychart
A Quick Glimpse of mychart/templates/
If you take a look at the mychart/templates/
directory, you’ll notice a few files already there.
NOTES.txt
: The “help text” for your chart. This will be displayed to your users when they run helm install.deployment.yaml
: A basic manifest for creating a Kubernetes deploymentservice.yaml
: A basic manifest for creating a service endpoint for your deployment_helpers.tpl
: A place to put template helpers that you can re-use throughout the chart
And what we’re going to do is… remove them all! That way we can work through our tutorial from scratch. We’ll actually create our own NOTES.txt and _helpers.tpl as we go.
$ rm -rf mychart/templates/*
When you’re writing production grade charts, having basic versions of these charts can be really useful. So in your day-to-day chart authoring, you probably won’t want to remove them.
A First Template
The first template we are going to create will be a ConfigMap. In Kubernetes, a ConfigMap is simply an object for storing configuration data. Other things, like pods, can access the data in a ConfigMap.
Because ConfigMaps are basic resources, they make a great starting point for us.
Let’s begin by creating a file called mychart/templates/configmap.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configmap
data:
myvalue: "Hello World"
TIP: Template names do not follow a rigid naming pattern. However, we recommend using the suffix .yaml for YAML files and .tpl for helpers.
The YAML file above is a bare-bones ConfigMap, having the minimal necessary fields. In virtue of the fact that this file is in the mychart/templates/ directory, it will be sent through the template engine.
It is just fine to put a plain YAML file like this in the mychart/templates/
directory. When Helm reads this template, it will simply send it to Kubernetes as-is.
With this simple template, we now have an installable chart. And we can install it like this:
$ helm install full-coral ./mychart
NAME: full-coral
LAST DEPLOYED: Tue Nov 1 17:36:01 2016
NAMESPACE: default
STATUS: DEPLOYED
REVISION: 1
TEST SUITE: None
Using Helm, we can retrieve the release and see the actual template that was loaded.
$ helm get manifest full-coral
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configmap
data:
myvalue: "Hello World"
The helm get manifest command takes a release name (full-coral) and prints out all of the Kubernetes resources that were uploaded to the server. Each file begins with ---
to indicate the start of a YAML document, and then is followed by an automatically generated comment line that tells us what template file generated this YAML document.
From there on, we can see that the YAML data is exactly what we put in our configmap.yaml
file.
Now we can uninstall our release: helm uninstall full-coral
.
Adding a Simple Template Call
Hard-coding the name
: into a resource is usually considered to be bad practice. Names should be unique to a release. So we might want to generate a name field by inserting the release name.
TIP: The name
: field is limited to 63 characters because of limitations to the DNS system. For that reason, release names are limited to 53 characters. Kubernetes 1.3 and earlier limited to only 24 characters (thus 14 character names).
Let’s alter configmap.yaml
accordingly.
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
The big change comes in the value of the name: field, which is now -configmap
.
A template directive is enclosed in blocks.
The template directive `` injects the release name into the template. 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 Release indicates that we start with the top-most namespace for this scope (we’ll talk about scope in a bit). So we could read .Release.Name
as “start at the top namespace, find the Release object, then look inside of it for an object called Name”.
The Release object is one of the built-in objects for Helm, and we’ll cover it in more depth later. But for now, it is sufficient to say that this will display the release name that the library assigns to our release.
Now when we install our resource, we’ll immediately see the result of using this template directive:
$ helm install clunky-serval ./mychart
NAME: clunky-serval
LAST DEPLOYED: Tue Nov 1 17:45:37 2016
NAMESPACE: default
STATUS: DEPLOYED
REVISION: 1
TEST SUITE: None
You can run helm get manifest clunky-serval
to see the entire generated YAML.
Note that the config map inside kubernetes name is clunky-serval-configmap
instead of mychart-configmap
previously.
At this point, we’ve seen templates at their most basic: YAML files that have template directives embedded in ``. In the next part, we’ll take a deeper look into templates. But before moving on, there’s one quick trick that can make building templates faster: When you want to test the template rendering, but not actually install anything, you can use helm install --debug --dry-run goodly-guppy ./mychart.
This will render the templates. But instead of installing the chart, it will return the rendered template to you so you can see the output:
$ helm install --debug --dry-run goodly-guppy ./mychart
install.go:149: [debug] Original chart version: ""
install.go:166: [debug] CHART PATH: /Users/ninja/mychart
NAME: goodly-guppy
LAST DEPLOYED: Thu Dec 26 17:24:13 2019
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
{}
COMPUTED VALUES:
affinity: {}
fullnameOverride: ""
image:
pullPolicy: IfNotPresent
repository: nginx
imagePullSecrets: []
ingress:
annotations: {}
enabled: false
hosts:
- host: chart-example.local
paths: []
tls: []
nameOverride: ""
nodeSelector: {}
podSecurityContext: {}
replicaCount: 1
resources: {}
securityContext: {}
service:
port: 80
type: ClusterIP
serviceAccount:
create: true
name: null
tolerations: []
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: goodly-guppy-configmap
data:
myvalue: "Hello World"
Using –dry-run will make it easier to test your code, but it won’t ensure that Kubernetes itself will accept the templates you generate. It’s best not to assume that your chart will install just because –dry-run works.
In the Chart Template Guide, we take the basic chart we defined here and explore the Helm template language in detail. And we’ll get started with built-in objects.
Values Files
In the previous section we looked at the built-in objects that Helm templates offer. One of the built-in objects is Values. This object provides access to values passed into the chart. Its contents come from multiple sources:
The values.yaml
file in the chart:
- If this is a subchart, the values.yaml file of a parent chart
- A values file if passed into helm install or helm upgrade with the -f flag (
helm install -f myvals.yaml ./mychart
) - Individual parameters passed with
--set
(such ashelm install --set foo=bar ./mychart
)
The list above is in order of specificity: values.yaml is the default, which can be overridden by a parent chart’s values.yaml
, which can in turn be overridden by a user-supplied values file, which can in turn be overridden by --set
parameters.
Values files are plain YAML files. Let’s edit mychart/values.yaml
and then edit our ConfigMap template.
Removing the defaults in values.yaml, we’ll set just one parameter:
favoriteDrink: coffee
Now we can use this inside of a template:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
Notice on the last line we access favoriteDrink as an attribute of Values: ``.
Let’s see how this renders.
$ helm install geared-marsupi ./mychart --dry-run --debug
install.go:158: [debug] Original chart version: ""
install.go:175: [debug] CHART PATH: /home/bagratte/src/playground/mychart
NAME: geared-marsupi
LAST DEPLOYED: Wed Feb 19 23:21:13 2020
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
{}
COMPUTED VALUES:
favoriteDrink: coffee
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: geared-marsupi-configmap
data:
myvalue: "Hello World"
drink: coffee
Because favoriteDrink
is set in the default values.yaml
file to coffee, that’s the value displayed in the template. We can easily override that by adding a --set
flag in our call to helm install:
$ helm install solid-vulture ./mychart --dry-run --debug --set favoriteDrink=slurm
install.go:158: [debug] Original chart version: ""
install.go:175: [debug] CHART PATH: /home/bagratte/src/playground/mychart
NAME: solid-vulture
LAST DEPLOYED: Wed Feb 19 23:25:54 2020
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
favoriteDrink: slurm
COMPUTED VALUES:
favoriteDrink: slurm
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: solid-vulture-configmap
data:
myvalue: "Hello World"
drink: slurm
Since --set
has a higher precedence than the default values.yaml
file, our template generates drink: slurm.
Values files can contain more structured content, too. For example, we could create a favorite section in our values.yaml
file, and then add several keys there:
favorite:
drink: coffee
food: pizza
Now we would have to modify the template slightly:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
While structuring data this way is possible, the recommendation is that you keep your values trees shallow, favoring flatness. When we look at assigning values to subcharts, we’ll see how values are named using a tree structure.
Template Functions and Pipelines
So far, we’ve seen how to place information into a template. But that information is placed into the template unmodified. Sometimes we want to transform the supplied data in a way that makes it more useable to us.
Let’s start with a best practice: When injecting strings from the .Values object into the template, we ought to quote these strings. We can do that by calling the quote function in the template directive:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
Template functions follow the syntax functionName arg1 arg2…. In the snippet above, quote .Values.favorite.drink calls the quote function and passes it a single argument.
Helm has over 60 available functions. Some of them are defined by the Go template language itself. Most of the others are part of the Sprig template library. We’ll see many of them as we progress through the examples.
While we talk about the “Helm template language” as if it is Helm-specific, it is actually a combination of the Go template language, some extra functions, and a variety of wrappers to expose certain objects to the templates. Many resources on Go templates may be helpful as you learn about templating.
Pipelines
One of the powerful features of the template language is its concept of pipelines. Drawing on a concept from UNIX, pipelines are a tool for chaining together a series of template commands to compactly express a series of transformations. In other words, pipelines are an efficient way of getting several things done in sequence. Let’s rewrite the above example using a pipeline.
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
In this example, instead of calling quote ARGUMENT, we inverted the order. We “sent” the argument to the function using a pipeline ( | ): .Values.favorite.drink | quote . Using pipelines, we can chain several functions together: |
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
Inverting the order is a common practice in templates. You will see .val | quote
more often than quote .val.
Either practice is fine.
When evaluated, that template will produce this:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: trendsetting-p-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
Note that our original pizza has now been transformed to “PIZZA”.
When pipelining arguments like this, the result of the first evaluation (.Values.favorite.drink
) is sent as the last argument to the function. We can modify the drink example above to illustrate with a function that takes two arguments: repeat COUNT STRING
:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
The repeat function will echo the given string the given number of times, so we will get this for output:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: melting-porcup-configmap
data:
myvalue: "Hello World"
drink: "coffeecoffeecoffeecoffeecoffee"
food: "PIZZA"
Using the default function
One function frequently used in templates is the default function: default DEFAULT_VALUE GIVEN_VALUE
. This function allows you to specify a default value inside of the template, in case the value is omitted. Let’s use it to modify the drink example above:
drink:
If we run this as normal, we’ll get our coffee:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: virtuous-mink-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
Now, we will remove the favorite drink setting from values.yaml
:
favorite:
#drink: coffee
food: pizza
Now re-running helm install --dry-run --debug fair-worm ./mychart
will produce this YAML:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fair-worm-configmap
data:
myvalue: "Hello World"
drink: "tea"
food: "PIZZA"
In an actual chart, all static default values should live in the values.yaml
, and should not be repeated using the default command (otherwise they would be redundant). However, the default command is perfect for computed values, which cannot be declared inside values.yaml
. For example:
drink:
In some places, an if
conditional guard may be better suited than default
. We’ll see those in the next section.
Template functions and pipelines are a powerful way to transform information and then insert it into your YAML. But sometimes it’s necessary to add some template logic that is a little more sophisticated than just inserting a string. In the next section we will look at the control structures provided by the template language.
Template Function List
Refer to the official guide: https://helm.sh/docs/chart_template_guide/function_list/
Flow Control
Control structures (called “actions” in template parlance) provide you, the template author, with the ability to control the flow of a template’s generation. Helm’s template language provides the following control structures:
if/else
for creating conditional blockswith
to specify a scoperange
, which provides a"for each"
-style loop
In addition to these, it provides a few actions for declaring and using named template segments:
define
declares a new named template inside of your templatetemplate
imports a named templateblock
declares a special kind of fillable template area
In this section, we’ll talk about if
, with
, and range
. The others are covered in the “Named Templates” section later in this guide.
If/Else
The first control structure we’ll look at is for conditionally including blocks of text in a template. This is the if/else
block.
The basic structure for a conditional looks like this:
# Do something
# Do something else
# Default case
Notice that we’re now talking about pipelines instead of values. The reason for this is to make it clear that control structures can execute an entire pipeline, not just evaluate a value.
A pipeline is evaluated as false if the value is:
- a boolean false
- a numeric zero
- an empty string
- a nil (empty or null)
- an empty collection (map, slice, tuple, dict, array)
Under all other conditions, the condition is true.
Let’s add a simple conditional to our ConfigMap. We’ll add another setting if the drink is set to coffee:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
mug: "true"
Since we commented out drink: coffee
in our last example, the output should not include a mug: "true"
flag. But if we add that line back into our values.yaml
file, the output should look like this:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
Controlling Whitespace
While we’re looking at conditionals, we should take a quick look at the way whitespace is controlled in templates. Let’s take the previous example and format it to be a little easier to read:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
mug: "true"
Initially, this looks good. But if we run it through the template engine, we’ll get an unfortunate result:
$ helm install --dry-run --debug ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/helm.sh/helm/_scratch/mychart
Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key
What happened? We generated incorrect YAML because of the whitespacing above.
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
mug
is incorrectly indented. Let’s simply out-dent that one line, and re-run:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
mug: "true"
When we sent that, we’ll get YAML that is valid, but still looks a little funny:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: telling-chimp-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
Notice that we received a few empty lines in our YAML
. Why? When the template engine runs, it removes the contents inside of ``, but it leaves the remaining whitespace exactly as is.
YAML ascribes meaning to whitespace, so managing the whitespace becomes pretty important. Fortunately, Helm templates have a few tools to help.
First, the curly brace syntax of template declarations can be modified with special characters to tell the template engine to chomp whitespace. `` means whitespace to the right should be consumed. Be careful! Newlines are whitespace!
Make sure there is a space between the -
and the rest of your directive. 3
means “trim left whitespace and print 3” while 3
means “print -3”.
Using this syntax, we can modify our template to get rid of those new lines:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
mug: "true"
Just for the sake of making this point clear, let’s adjust the above, and substitute an * for each whitespace that will be deleted following this rule. An *
at the end of the line indicates a newline character that would be removed
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food: *
**
mug: "true"*
**
Keeping that in mind, we can run our template through Helm and see the result:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: clunky-cat-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
Be careful with the chomping modifiers. It is easy to accidentally do things like this:
food: mug: "true"```
That will produce food: `"PIZZA"mug: "true"` because it consumed newlines on both sides.
For the details on whitespace control in templates, see the Official Go template documentation
Finally, sometimes it's easier to tell the template system how to indent for you instead of trying to master the spacing of template directives. For that reason, you may sometimes find it useful to use the `indent` function (``).
##### Modifying scope using `with`
The next control structure to look at is the with action. This controls variable scoping. Recall that `.` is a reference to the current scope. So .Values tells the template to find the Values object in the current scope.
The syntax for with is similar to a simple if statement:
```shell
# restricted scope
Scopes can be changed. with
can allow you to set the current scope (.
) to a particular object. For example, we’ve been working with .Values.favorite.
Let’s rewrite our ConfigMap to alter the .
scope to point to .Values.favorite
:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
Note that we removed the if conditional from the previous exercise because it is now unnecessary - the block after with
only executes if the value of PIPELINE
is not empty.
Notice that now we can reference .drink
and .food
without qualifying them. That is because the with statement sets .
to point to .Values.favorite.
The .
is reset to its previous scope after ``.
But here’s a note of caution! Inside of the restricted scope, you will not be able to access the other objects from the parent scope using .. This, for example, will fail:
drink:
food:
release:
It will produce an error because Release.Name
is not inside of the restricted scope for .
. However, if we swap the last two lines, all will work as expected because the scope is reset after ``.
drink:
food:
release:
Or, we can use $
for accessing the object Release.Name
from the parent scope. $
is mapped to the root scope when template execution begins and it does not change during template execution. The following would work as well:
drink:
food:
release:
After looking at range, we will take a look at template variables, which offer one solution to the scoping issue above.
Looping with the range action
Many programming languages have support for looping using for loops, foreach loops, or similar functional mechanisms. In Helm’s template language, the way to iterate through a collection is to use the range operator.
To start, let’s add a list of pizza toppings to our values.yaml
file:
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
Now we have a list (called a slice in templates) of pizzaToppings. We can modify our template to print this list into our ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
toppings: |-
-
We can use $
for accessing the list Values.pizzaToppings from the parent scope. $
is mapped to the root scope when template execution begins and it does not change during template execution. The following would work as well:
apiVersion: v1
kind: ConfigMap
metadata:
name: -configmap
data:
myvalue: "Hello World"
drink:
food:
toppings: |-
-
Let’s take a closer look at the toppings
: list. The range function will “range over” (iterate through) the pizzaToppings list. But now something interesting happens. Just like with sets the scope of .
, so does a range operator. Each time through the loop, .
is set to the current pizza topping. That is, the first time, .
is set to mushrooms. The second iteration it is set to cheese, and so on.
We can send the value of .
directly down a pipeline, so when we do ``, it sends .
to title
(title case function) and then to quote
. If we run this template, the output will be:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: edgy-dragonfly-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
toppings: |-
- "Mushrooms"
- "Cheese"
- "Peppers"
- "Onions"
Now, in this example we’ve done something tricky. The toppings: |-
line is declaring a multi-line string. So our list of toppings is actually not a YAML list. It’s a big string. Why would we do this? Because the data in ConfigMaps data is composed of key/value pairs, where both the key and the value are simple strings. To understand why this is the case, take a look at the Kubernetes ConfigMap docs. For us, though, this detail doesn’t matter much.
The | - marker in YAML takes a multi-line string. This can be a useful technique for embedding big blocks of data inside of your manifests, as exemplified here. |
Sometimes it’s useful to be able to quickly make a list inside of your template, and then iterate over that list. Helm templates have a function to make this easy: tuple. In computer science, a tuple is a list-like collection of fixed size, but with arbitrary data types. This roughly conveys the way a tuple is used.
sizes: |-
-
The above will produce this:
sizes: |-
- small
- medium
- large
In addition to lists and tuples, range can be used to iterate over collections that have a key and a value (like a map or dict). We’ll see how to do that in the next section when we introduce template variables.