Helm
Helm is a package manager for Kubernetes. The packages themselves are called Charts. Installed chart is called a Release.
Why?
- Typically, K8s applications require multiple objects: deployments, services,
ingresses, storage, configs, etc. Usually, these are defined in separate YAML
files.
- we’d like to treat these objects “atomically” - they are all parts of one app
- the order of objects creation might matter (e.g. config before deployment)
- versioning is not straightforward - we have to remember the differences between two versions and manually apply them
With Helm, we get the following:
- We treat the app as a single entity, “forgetting” that it consists of multiple objects - an app in a package
- Packages are versioned, making it much easier to upgrade/downgrade apps
- Templates allow extracting variables from YAML files for customizability
- Management of dependencies
Helm Charts
A Chart is a package.
Structure
A chart is an archive containing a single folder:
- the name of the folder is the name of the chart.
- the
Chart.yaml
contains metadata about the chart (like a version, dependencies, etc.) - dependencies can be also included as sub-charts, inside of the
charts
folder, or listed in therequirements.yaml
(see note below) - the
templates
folder contains customizable YAML files (or just “normal” K8s-ready YAMLs if customization is not needed). - the documentation should be placed in the
README.md
file - the message to be displayed after chart installation should be placed in the
templates/NOTES.txt
file. It is a template, so it can make use of placeholders to dynamically include some values. - the template values can be specified in the
values.yaml
file. It contains the default values. - the
values.schema.json
file defines the structure of thevalues.yaml
file as JSON schema - the
.helmignore
file allows us to specify files that should be ignored by Helm during templates processing - the
_helpers.tpl
file is often used to store user-defined functions/snippets
An example of Chart.yaml
:
Umbrella Chart
Some charts might be more complex, and they might contain a few major
components, like a frontend, a backend, and a database. Each of these is a
separate part of the application, with its own set of YAMLs. We can define them
as separate charts, and then place them inside of the charts
directory of the
“umbrella” chart - a chart that brings the sub-components together.
Each sub-chart may contain its own values.yaml
file. The umbrella chart’s
value.yaml
may overwrite the sub-chart’s values.
The values defined in the sub-charts can also be made available to the parent
child. The sub-chart has to export
values, and the parent’s values.yaml
has
to import-values
. There is also a way that does not require the sub-chart to
define values inside of export
. This functionality, in general, is rarely
used, so I’m not describing it here.
Types
Not all charts are apps. Some charts act as libraries of functions for other
charts to consume. We can denote it by specifying type: library
in the
Chart.yaml
file. Applications use type: application
instead.
Versioning
The Chart.yaml
file specifies multiple version strings:
apiVersion
- v2 is for Helm 3 (obviously)appVersion
- version of the app to be installedversion
- version of the chart itself
An example: We could be preparing a chart for Discourse. The version of
Discourse to be deployed could be “2.8.0.beta1”. That would be the appVersion
.
Chart version could be 0.0.1
- version
. If I change something in the charts
(e.g. enhance the deployment properties somehow), I’d increment the version
property. The appVersion
would not change (unlesss a new version of Discourse
came up and I wanted to include it in my chart as well).
Can I change appVersion
without changing the version
? I think that I
shouldn’t be able to do so.
Can I upgrade a release to a chart with major version change? A major change means breaking changes, so I am not able to upgrade?
Releases
A Release is an installed chart on some cluster. Usually, we install a chart once, creating a single release. However, we could also install a chart multiple times creating multiple instances in a single cluster (e.g. “dev” and “test” environments or multiple instaces of some app, like a DB, for completely different purposes).
Release Revision
We can update a deployed release. It can be due to:
- desire to upgrade to a different version of the chart
- change in some values of the release
We can upgrade existing release to a new revision with helm upgradae {release} {chart}
. We can also go back in history with helm rollback {release} {revision}
. We can print the history of revisions with helm history {release}
.
Templating
Helm allows us to prepare YAML files with placeholders for values - these are templates. Thanks to that, releases can be customized. Some values specified by the chart author can be replaced, while others could use the default values.
The placeholders in templates are wrapped in {{}}
.
The placeholders in the templates are replaced with values when running Helm CLI
commands (like install
or upgrade
).
Example
Template (for a service):
values.yaml
:
Sources of Data
Values for the templates can be supplied from various sources:
.Values
:values.yaml
- chart’s author places default values there- command line parameters (e.g.
helm install --set foo=bar
) - overwrites data fromvalues.yaml
- any other YAML file (then we need to specify it in the
helm install -f some-file.yaml
command) - overrided values invalues.yaml
Chart.yaml
- placeholder starts from.Chart.
- other files - placeholder start from
.Files.Get {file-name}
- Template file itself - placeholder starts from
.Template.
. - Release runtime data - template starts from
.Release.
(e.g.Release.Name
,Release.Revision
). - Cluster metadata - template starts from
.Capabilities
(e.g.Capabilities.KubeVersion
)
The values.yaml
can be as convoluted as needed (it can contain objects, arrays).
In the templates we can refer to these values. Examples:
.Values.service.name
.Values.service.names[0].displayName
Schema
If we define the schema in the values.schema.json
file, Helm will validate the
values.yaml
file before doing any actual operation (like install
).
With scope
In our template, we could have lots of repetitions in our placeholders (e.g. all
of them starting with .Values.config
). In such a case, we could use with
to
specify that prefix just once:
Dry Run
We can see how the templates will be turned into manifests with the helm template {chart-name}
command (it doesn’t even require K8s cluster connection).
We can also use the helm install {release-name} {char-name} --dry-run --debug
.
Logic
We may use functions in the templates when some additional logic is needed. There are 2 syntaxes:
- function -
quote(value)
- arguments separated by commas
- pipeline -
value | quote
- arguments separated by spaces, and placed after
the function -
first_arg | fn other_arg
- arguments separated by spaces, and placed after
the function -
Functions come from:
- Go templates
- Sprig project
- Helm itself
Function Examples
default
- returns a value if exists, if not, returns some provided defaultquote
- adds quotesupper
- transforms to upper-caseb64enc
- encodes to base64trunc
- truncates value to specified amount of characters (useful for labels, which are limited to 63 characters)
Operators are also functions:
eq
ne
gt
and
- etc.
Conditionals
It translates to: “if (adminEmail AND (serviceAccountJson OR existingSecret))”
Example of usage:
We can build the manifest differently depending on some values.
Loops
An example:
values.yaml
:
Variables
Variables allow us to define values within templates and name them. They are useful when using the “with” scoping or loops, since within these we cannot refer to anything outside of their scope. However, we can refer to variables.
Named Templates
We can create a _helpers.tpl
(just a convention name) file to store various
snippets that we want to reuse (like some conditionals, loops, or just manifest
parts). The contents should be wrapped in the define
function which allows us
to name the function.
To use these named templates we need to include
them in other templates. We
could also use Go’s template
instead to include it, but it is not as
functional (we can’t pipe the output to another function).
Dependencies
Chart’s dependencies can be included in the following ways:
- by placing chart’s folders in
/charts
of the main chart - by placing archives of charts in
/charts
of the main chart dependencies
array in theChart.yaml
file.dependencies
array in therequirements.yaml
file - OBSOLETE! TheChart.yaml
file is recommended nowadays.
The helm dependency update {chart-name}
fetches latest versions of
dependencies matching ranges defined in the Chart.yaml
. Helm downloads
dependencies as tgz
archives and places them in the /charts
directory.
Conditions
Dependencies can be installed conditionally, based on configuration values
(values.yaml
). An example:
Another way is to tag dependencies with tag
and set that tag to true/false
in the values.yaml
. It’s useful if multiple dependencies shoud be
enabled/disabled at once.
Repositories
There are repositories of charts. We can add them to Helm CLI for it to be able to fetch charts. There is an official repository at https://kubernetes-charts.storage.googleapis.com.
By default, Helm CLI does not have any repositories configured.
Repos can be added with helm repo add repo-name repo-url
.
A repository is just an HTTP server containing the charts and the index.yaml
file describing charts.
Publishing
Before publishing our charts, we need to package them into archives. It can be
done with the helm package {chart-name}
command. It results in a .tgz
archive being created.
Internals
Helm stores details of releases in the K8s cluster itself - as secrets. They are stored in the same namespace as the application/release.
Three-Way Merge Patch
Helm applies upgrades/downgrades by looking at the following:
- last installed chart version
- chart version to be installed
- current state of the app
It’s possible that the release has been modified outside of Helm control (e.g., manually via kubectl or some operator). Even in such situations, Helm is able to persist these manual changes, as long as they don’t conflict with the chart to be installed. Before applying the change, it marges the target state with the “external” modifications.
Namespaces
By default, Helm installs resources into the “default” namespace. However, we can specify another namespace while installing.
Commands
Install a Chart
helm install {release-name} {chart-name}
Uninstall a Chart
To remove a release, together with Helm-managed secrets metadata, run helm uninstall {release-name}
.
Get Manifest
We can have a look at the YAMLs of the deployed release with helm get manifest {release-name}
.