- https://deis.com/blog/2016/getting-started-authoring-helm-charts/
- https://docs.bitnami.com/kubernetes/how-to/
- https://github.com/kubernetes/helm/blob/master/docs/charts.md
- https://docs.helm.sh/chart-template-guide/
- http://helm.readthedocs.io/en/latest/architecture/
The official docs do a good job of explaining the basic concepts. Some important points are shown in the table below:
Helm Concept | Description | Important point |
---|---|---|
Chart (unpackaged) | A folder with files that follow the Helm chart guidelines. | Can be deployed directly to a cluster |
Chart (packaged) | A tar.gz archive of the above |
Can be deployed directly to a cluster |
Chart name | Name of the package as defined in Chart.yaml |
Part of package identification |
Templates | A set of Kubernetes manifests that form an application | Go templates can be used |
Values | Settings that can be parameterized in Kubernetes manifests | Used for templating of manifests |
Chart version | The version of the package/chart | Part of package identification |
App version | The version of the application contained in the chart | Independent from chart version |
Release | A deployed package in a Kubernetes cluster | Multiple releases of the same chart can be active |
Release name | An arbitrary name given to the release | Independent from name of chart |
Release Revision | A number that gets incremented each time an application is deployed/upgraded | Unrelated to chart version |
Repository | A file structure (HTTP server) with packages and an index.yaml file |
Helm charts can be deployed without being fetched from a repository first |
Installing | Creating a brand-new release from a Helm chart (either unpackaged, packaged or from a repo) | |
Upgrading | Changing an existing release in a cluster | Can be upgraded to any version (even the same) |
Rolling back | Going back to a previous revision of a release | Helm handles the rollback, no need to re-rerun pipeline |
Pushing | Storing a Helm package on a repository | ### Helm repositories are optional |
Using Helm repositories is a recommended practice, but completely optional. You can deploy a Helm chart to a Kubernetes cluster directly from the filesystem. The quick start guide actually shows this scenario.
Helm can install a chart either in the package (.tgz
) or unpackaged form (tree of files) to a Kubernetes cluster right away.
Chart will be automatically packaged | Fetching | Downloading a Helm package from a repository to the local filesystem | |
Using Helm repositories is a recommended practice, but completely optional. You can deploy a Helm chart to a Kubernetes cluster directly from the filesystem. The quick start guide actually shows this scenario.
Helm can install a chart either in the package (.tgz
) or unpackaged form (tree of files) to a Kubernetes cluster right away.
Each Helm chart has the ability to define two separate versions:
- The version of the chart itself (
version
field inChart.yaml
). - The version of the application contained in the chart (
appVersion
field inChart.yaml
).
These are unrelated and can be bumped up in any manner that you see fit. You can sync them together or have them increase independently. There is no right or wrong practice here as long as you stick into one. We will see some versioning strategies in the next section.
The most basic way to use Helm is by having a single chart that holds a single application. The single chart will contain all the resources needed by your application such as deployments, services, config-maps etc.
However, you can also create a chart with dependencies to other charts (a.k.a. umbrella chart) which are completely external using the requirements.yaml
file. Using this strategy is optional and can work well in several organizations. Again, there is no definitive answer on right and wrong here, it depends on your team process.
{% include image.html lightbox="true" file="/images/kubernetes-helm/best-practices/chart-structure.png" url="/images/kubernetes-helm/best-practices/chart-structure.png" alt="Possible Helm structures" caption="Possible Helm structures" max-width="70%" %}
.
├── Chart.yaml --> metadata info
├── README.md
├── requirements.yaml --> define dependencies
├── templates
│ ├── spark-master-deployment.yaml --> configuration with template supported
│ ├── spark-worker-deployment.yaml
│ └── spark-zeppelin-deployment.yaml
│ └── NOTES.txt --> display when run "helm chart"
│ └── _helpers.tpl --> template handler
└── values.yaml --> variable list, will be interpolated on templates file during deployment
│
└── charts
├── apache/
├── Chart.yaml
- Chart.yaml
name: The name of the chart (required)
version: A SemVer 2 version (required)
description: A single-sentence description of this project (optional)
keywords:
- A list of keywords about this project (optional)
home: The URL of this project's home page (optional)
sources:
- A list of URLs to source code for this project (optional)
maintainers: # (optional)
- name: The maintainer's name (required for each maintainer)
email: The maintainer's email (optional for each maintainer)
engine: gotpl # The name of the template engine (optional, defaults to gotpl)
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). This needn't be SemVer.
deprecated: Whether or not this chart is deprecated (optional, boolean)
tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional)
- requirements.yaml
Adding an
alias
for a dependency chart would put a chart in dependencies using alias as name of new dependency.Condition
- The condition field holds one or more YAML paths (delimited by commas). If this path exists in the top parent's values and resolves to a boolean value, the chart will be enabled or disabled based on that boolean value. Only the first valid path found in the list is evaluated and if no paths exist then the condition has no effect.Tags
- The tags field is a YAML list of labels to associate with this chart. In the top parent's values, all charts with tags can be enabled or disabled by specifying the tag and a boolean value.Conditions
(when set in values) always overridetags
dependencies:
- name: apache
version: 1.2.3
repository: http://example.com/charts
alias: new-subchart-1
condition: subchart1.enabled, global.subchart1.enabled
tags:
- front-end
- subchart1
- name: mysql
version: 3.2.1
repository: http://another.example.com/charts
alias: new-subchart-2
condition: subchart2.enabled,global.subchart2.enabled
tags:
- back-end
- subchart1
helm list --all
helm repo (list|add|update)
helm search
helm inspect <chart-name>
hem install --set a=b -f config.yaml <chart-name> -n <release-name> # --set take precedented, merge into -f
helm status <deployment-name>
helm delete <deployment-name>
helm inspect values <chart-name>
helm upgrade -f config.yaml <deployment-name> <chart-name>
helm rollback <deployment-name> <version>
helm create <chart-name>
helm package --app-version <appVersion> <chart-name>
helm lint <chart-name>
helm dep up <chart-name> # update dependency
helm get manifest <deployment-name> # prints out all of the Kubernetes resources that were uploaded to the server
helm install --debug --dry-run <deployment-name> # it will return the rendered template to you so you can see the output
- --set outer.inner=value is translated into this:
outer:
inner: value
- --set servers[0].port=80,servers[0].host=example:
servers:
- port: 80
host: example
- --set name={a, b, c} translates to:
name:
- a
- b
- c
- --set name=value1,value2:
name: "value1,value2"
- --set nodeSelector."kubernetes.io/role"=master
nodeSelector:
kubernetes.io/role: master
- --set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt] --set livenessProbe.httpGet=null
livenessProbe:
- httpGet:
- path: /user/login
- port: http
initialDelaySeconds: 120
+ exec:
+ command:
+ - cat
+ - docroot/CHANGELOG.txt
- --timeout
- --wait
- --no-hooks
- --recreate-pods
Values that are supplied via a
values.yaml
file (or via the --set flag) are accessible from the.Values
object in a template
Release.Name:
Release.Time:
Release.Namespace: The namespace the chart was released to.
Release.Service: The service that conducted the release. Usually this is Tiller.
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.
Release.Revision: The revision number. It begins at 1, and increments with each helm upgrade.
Chart: The contents of the Chart.yaml. Thus, the chart version is obtainable as "Chart.Version" and the maintainers are in "Chart.Maintainers".
Files: Files can be accessed using {{index .Files "file.name"}} or using the "{{.Files.Get name}}" or "{{.Files.GetString name}}" functions. You can also access the contents of the file as []byte using "{{.Files.GetBytes}}"
Capabilities: "({{.Capabilities.KubeVersion}}", Tiller "({{.Capabilities.TillerVersion}}", and the supported Kubernetes API versions "({{.Capabilities.APIVersions.Has "batch/v1")"
{{.Files.Get config.ini}}
{{.Files.GetBytes}} useful for things like images
{{.Template.Name}}
{{.Template.BasePath}}
- default value
{{default "minio" .Values.storage}}
//same
{{ .Values.storage | default "minio" }}
- put a quote outside
heritage: {{.Release.Service | quote }}
# same result
heritage: {{ quote .Release.Service }}
- global variable
global:
app: MyWordPress
// could be access as "{{.Values.global.app}}"
- Includes a template called
mytpl.tpl
, thenlowercases
the result, then wraps that indouble quotes
value: {{include "mytpl.tpl" . | lower | quote}}
required
function declares an entry for.Values.who
is required, and will print anerror message
when that entry is missing
value: {{required "A valid .Values.who entry required!" .Values.who }}
- The
sha256sum
function can be used together with theinclude
function to ensure a deployments template section is updated if another spec changes
kind: Deployment
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
[...]
-
The annotation
"helm.sh/resource-policy":
keep instructsTiller
to skip this resource during a helmdelete
operation -
In the
templates/
directory, any file that begins with anunderscore(_)
isnot
expected to output a Kubernetes manifest file. So by convention,helper templates
andpartials
are placed in a_helpers.tpl
file.
Read more
- include these annotation inside hook yaml file, for e.g
templates/post-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
annotations:
# This is what defines this resource as a hook. Without this line, the
# job is considered part of the release.
"helm.sh/hook": post-install, post-upgrade
"helm.sh/hook-weight": "-5"
Read more
Read more
Read more
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }}
{{- if eq .Values.favorite.drink "lemonade" }}
mug: true
{{- end }} # notice the "-" in the left, if will help eliminate newline before variable
with
can allow you to set the currentscope
(.) to a particular object
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }} # instead of writing ".Values.favorite.drink"
Inside
of therestricted scope
, you willnot
be able to access the other objects from theparent scope
# predefined variable
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
toppings: |-
{{- range $i, $val := .Values.pizzaTopping }}
- {{ . | title | quote }} # upper first character, then quote
{{- end }}
sizes: |-
{{- range tuple "small" "medium" "large" }}
- {{ . }}
{{- end }} # make a quick list
It follows the form
$name
. Variables are assigned with a special assignment operator::=
data:
myvalue: "Hello World"
{{- $relname := .Release.Name -}}
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ $relname }}
{{- end }}
# use variable in range
toppings: |-
{{- range $index, $topping := .Values.pizzaToppings }}
{{ $index }}: {{ $topping }}
{{- end }}
#toppings: |-
# 0: mushrooms
# 1: cheese
# 2: peppers
# 3: onions
{{- range $key,$value := .Values.favorite }}
{{ $key }}: {{ $value }}
{{- end }} # instead of specify the key, we can actually loop through the values.yaml file and print values
There is one variable that is always global - $ - this variable will always point to the root context
...
labels:
# Many helm templates would use `.` below, but that will not work,
# however `$` will work here
app: {{ template "fullname" $ }}
# I cannot reference .Chart.Name, but I can do $.Chart.Name
chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"
release: "{{ $.Release.Name }}"
heritage: "{{ $.Release.Service }}"
...
template names are
global
# _helpers.tpl
{{/* Generate basic labels */}}
{{- define "my_labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
version: {{ .Chart.Version }}
name: {{ .Chart.Name }}
{{- end }}
When a named template (created with define) is rendered, it will receive the scope passed in by the template call.
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "my_labels" . }} # Notice the final dot, it will pass the global scope inside template file. Without it version & name will not be �generated.
{{- include "my_labels" . | indent 2 }} # similar to "template" directive, have the ability to control indentation
referable to use
include
overtemplate
. Becausetemplate
is anaction
, and not afunction
, there is no way to pass the output of a template call to other functions; the data is simply insertedinline
.
# file located at parent folder
# config1.toml: |-
# message = config 1 here
# config2.toml: |-
# message = config 2 here
# config3.toml: |-
# message = config 3 here
data:
{{- $file := .Files }} # set variable
{{- range tuple "config1.toml" "config2.toml" "config3.toml" }} # create list
{{ . }}: |- # config file name
{{ $file.Get . }} # get file's content
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
name: conf
data:
+{{ (.Files.Glob "foo/*").AsConfig | indent 2 }}
---
apiVersion: v1
kind: Secret
metadata:
name: very-secret
type: Opaque
data:
+{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}
+token: |-
+ {{ .Files.Get "config1.toml" | b64enc }}
# Force type
age: !!str 21
port: !!int "80"
# Fake first line to preserve integrity
coffee: | # �no strip
# Commented first line
Latte
Cappuccino
Espresso
coffee: |- # strip off trailing newline
Latte
Cappuccino
Espresso
coffee: |+ # preserve trailing newline
Latte
Cappuccino
Espresso
another: value
myfile: | # insert static file
{{ .Files.Get "myfile.txt" | indent 2 }}
coffee: > �# treat as one long line
Latte
Cappuccino
Espresso