Deploying packaged Kubernetes apps with Helm

What is Helm ? How Helm works ? How can we use Helm to deploy and manage packaged Kubernetes applications ? Let's get familiar with Helm and learn how to create and distribute our own Helm packages.

Deploying packaged Kubernetes apps with Helm
Photo by Kira auf der Heide / Unsplash

What is Helm

Helm is a tool that can be used to create and install ready to use, configurable and sharable packages of Kubernetes resources manifests for specific applications.

Those packaged Kubernetes resources manifests are called Helm Charts. Helm Charts are distributed via Charts repositories.

ArtifactHub is a web-based application that can be used to explore Helm Charts from many public Helm Charts repositories.

The installation of a specific Helm Chart inside a Kubernetes cluster creates a Helm Release.

How Helm works

Helm configuration settings and files

When the Helm command line utility is invoked to manage (install, uninstall, get status...) Helm releases, by default, it uses the $HOME/.kube/config configuration file to discover currently configured Kubernetes clusters endpoints and get the necessary info to securely communicate with their API servers.

The same configuration file is by default used by 'kubectl' and therefore, configuration setups performed with 'kubectl config' will by default be reused by Helm.

The environment variable $KUBECONFIG, or the '--kubeconfig' command line flag can be used to change the default path to Kubernetes clusters configuration file.

To list the other available configuration environment variables and command line flags for configuring Helm, and also, the default directory paths where Helm stores its data, configurations and caches, use 'helm --help'.

Helm Releases states

Helm releases states are stored as Kubernetes 'secrets' resources inside target clusters. Each time a Helm release is created or updated, a new 'secret' resource version is created in the release namespace.

Helm gets the releases history from those 'secrets' and use them for performing releases rollbacks. Deleting those 'secret' resources will make Helm forget anything about the previously managed releases.

Here are example Helm state 'secrets' resources for two versions of a Helm release named 'grafana':

$ kubectl get secret
NAME                            TYPE                 DATA   AGE
sh.helm.release.v1.grafana.v1   helm.sh/release.v1   1      3m1s
sh.helm.release.v1.grafana.v2   helm.sh/release.v1   1      5s

Installing Helm

  • Helm releases assets can be found here
  • To install Helm on Linux, do the following:
$ helm_version=v3.15.3 # choose version
$ linux_arch=amd64 # choose OS arch

# Download Helm binary
$ wget https://get.helm.sh/helm-${helm_version}-linux-${linux_arch}.tar.gz

# Install Helm binary
$ sudo tar xzvf helm-${helm_version}-linux-${linux_arch}.tar.gz -C /usr/local/bin/

# Verify
$ helm version

Managing Helm Charts repositories

# Adding Helm Charts repositories
$ repo_name=grafana ; repo_url=https://grafana.github.io/helm-charts
$ helm repo add $repo_name $repo_url 
"grafana" has been added to your repositories

# Listing added repositories
$ helm repo list
NAME    URL                                  
grafana https://grafana.github.io/helm-charts

# Synchronize/update local repo info from remote
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "grafana" chart repository
Update Complete. ⎈Happy Helming!⎈

# Removing repositories
$ helm repo remove $repo_name
"grafana" has been removed from your repositories

Searching for Helm charts

# Searching through public artifacthub.io repositories
$ search_keyword=grafana
$ helm search hub $search_keyword
URL                                                     CHART VERSION           APP VERSION             DESCRIPTION                                       
https://artifacthub.io/packages/helm/grafana/gr...      8.3.6                   11.1.0                  The leading tool for querying and visualizing t...
https://artifacthub.io/packages/helm/saurabh6-g...      0.2.0                   1.1                     This is a Helm Chart for Grafana Setup.
(...)

# Searching through locally added repositories

$ search_keyword=grafana/grafana

## Search for lastest versions of Charts matching the search keyword
$ helm search repo $search_keyword
NAME                            CHART VERSION   APP VERSION     DESCRIPTION                                       
grafana/grafana                 8.3.6           11.1.0          The leading tool for querying and visualizing t...
grafana/grafana-agent           0.42.0          v0.42.0         Grafana Agent                                     
grafana/grafana-agent-operator  0.4.1           0.42.0          A Helm chart for Grafana Agent Operator           
grafana/grafana-sampling        0.1.1           v0.40.2         A Helm chart for a layered OTLP tail sampling a...
(...)

## Search for all versions of Charts matching the search keyword
$ helm search repo $search_keyword -l
NAME                            CHART VERSION   APP VERSION     DESCRIPTION                                       
grafana/grafana                 8.3.6           11.1.0          The leading tool for querying and visualizing t...
grafana/grafana                 8.3.5           11.1.0          The leading tool for querying and visualizing t...
(...)
grafana/grafana-agent           0.42.0          v0.42.0         Grafana Agent                                     
grafana/grafana-agent           0.41.0          v0.41.1         Grafana Agent
(...)
grafana/grafana-agent-operator  0.4.1           0.42.0          A Helm chart for Grafana Agent Operator           
grafana/grafana-agent-operator  0.4.0           0.41.1          A Helm chart for Grafana Agent Operator
(...)

# Download Charts files

$ helm pull --untar $chart_url

## Download from locally added repositories
$ helm pull --untar grafana/grafana
$ ls grafana/
Chart.yaml  ci  dashboards  README.md  templates  values.yaml

Managing Helm releases

Show Helm Charts default values

  • Helm Charts 'values' are variables/parameters we can set in order to customize the manifests that will be deployed by the Charts, during installation or updates
  • To get all the possible 'values' that can be provided to Charts, do the following:
$ chart_name=grafana/grafana
$ helm show values $chart_name
global:
  # -- Overrides the Docker registry globally for all images
  imageRegistry: null
  (...)
  # global:
  #   imagePullSecrets:
  #   - pullSecret1
  #   - pullSecret2
  imagePullSecrets: []

rbac:
  create: true
(...)

The output of the previous command could be redirected to a 'values.yaml' file, that will then be customized and used as input values file during Charts releases creation.

Get Helm releases values, manifests, hooks and notes

We can get the following data from a Helm release:

  • values: user-supplied values
  • hooks: Helm hooks in use
  • manifest: the manifests of resources created by the release
  • notes: the content of the Charts NOTES.txt file

We could also get all for all currently used values and manifests, plus hooks currently in use plus the content of the Charts NOTES.txt file.

Here are examples:

$ chart_name=grafana/grafana
$ release_name=grafana

$ helm get values $release_name [-n $namespace]
USER-SUPPLIED VALUES:
null

$ helm get manifest $release_name
---
# Source: grafana/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
automountServiceAccountToken: false
metadata:
  labels:
    helm.sh/chart: grafana-8.4.4
    app.kubernetes.io/name: grafana
    app.kubernetes.io/instance: grafana
    app.kubernetes.io/version: "11.1.3"
    app.kubernetes.io/managed-by: Helm
  name: grafana
  namespace: default
---
# Source: grafana/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: grafana
(...)

$ helm get hooks $release_name
(...)
---
# Source: grafana/templates/tests/test-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-test
  namespace: default
  annotations:
    "helm.sh/hook": test
    "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
  labels:
    helm.sh/chart: grafana-8.4.4
    app.kubernetes.io/name: grafana
    app.kubernetes.io/instance: grafana
    app.kubernetes.io/version: "11.1.3"
    app.kubernetes.io/managed-by: Helm
data:
  run.sh: |-
    @test "Test Health" {
      url="http://grafana/api/health"

      code=$(wget --server-response --spider --timeout 90 --tries 10 ${url} 2>&1 | awk '/^  HTTP/{print $2}')
      [ "$code" == "200" ]
    }
---
(...)

$ helm get all $release_name
COMPUTED VALUES:
admin:
  existingSecret: ""
  passwordKey: admin-password
  userKey: admin-user
adminUser: admin
affinity: {}
alerting: {}
assertNoLeakedSecrets: true
automountServiceAccountToken: true
autoscaling:
  behavior: {}
  enabled: false
  maxReplicas: 5
  minReplicas: 1
  targetCPU: "60"
  targetMemory: ""
containerSecurityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
    - ALL
  seccompProfile:
    type: RuntimeDefault
(...)

Create or update Helm releases from a Chart

helm upgrade --install $release_name $chart_name --create-namespace [-n $namespace] [--version $chart_version] [-f values.yaml] [--set key=value] [--wait] 
  • The '-n' and '--version' options can be used to repectively specify the release namespace and Chart version to use
  • The '--create-namespace' option is used to automatically create the target namespace for the release if it does not exist
  • The '-f' or '--values' option can be used to set custom values for the Chart. That option can be used multiple times with multiple values files. Values from lastest specified files will have higher priorities
  • Chart values can also be specified directly at the command line using '--set key1=value1,key2=value2...'. Values specified with the '--set' option have higher priority over those specified with '-f' or '--values'. The backslash character can be used to escape some characters.
    Example: --set nodeSelector."kubernetes\.io/role"=master
  • The '--wait' flag can be used to wait for all pods to be at a ready state. Helm will wait the amount of time specified by the '--timeout' flag (Default value: '5 minutes')

List and get the status of Helm releases

$ helm list [-n $namespace]
NAME    NAMESPACE       REVISION        UPDATED                       STATUS          CHART           APP VERSION
grafana default         2               <release_creation_date>       deployed        grafana-8.3.6   11.1.0

$ release_name=grafana
$ helm status $release_name [-n $namespace]
NAME: grafana
(...)
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace default grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
(...)

View Helm releases history

$ release_name=grafana
$ helm history $release_name [-n $namespace]
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
1               <release_creation_date>         superseded      grafana-8.3.6   11.1.0          Install complete
2               <release_creation_date>         deployed        grafana-8.3.6   11.1.0          Upgrade complete

Rollback Helm releases

# Rolling back

$ release_name=grafana

# Rollback to previous release
$ helm rollback $release_name [-n $namespace]

# Rollback to specific revision of a release
# Revision numbers are shown in release history
$ revision_number=1
$ helm rollback $release_name $revision_number [-n $namespace]
Rollback was a success! Happy Helming!

# Show release created after rollback
$ helm history $release_name
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
(...)
3               <release_creation_date>         deployed        grafana-8.3.6   11.1.0          Rollback to 1

Uninstall Helm releases

$ release_name=grafana
$ helm uninstall $release_name [--keep-history -n $namespace]
release "grafana" uninstalled

The optional '--keep-history' flag can be used to keep deletion records. Doing that allows showing deleted releases with the '--uninstalled' or '--all' flags when listing releases with the 'helm list' command.

$ helm list --uninstalled
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
grafana default         1               <release_creation_date>                 uninstalled     grafana-8.4.4   11.1.3

Creating Helm charts

Helm Charts structure

Here is the basic structure of Helm Charts sources:

$ tree mychart/
mychart/
├── charts
├── Chart.yaml
├── NOTES.txt
├── templates
│   ├── deployment.yaml
│   └── _helpers.tpl
└── values.yaml
  • Prefer using '.yaml' extension over '.yml' when developing Helm Charts
  • To quickly generate an example Chart that can be used as development base, use the following command:
helm create $chart_directory_path

The Chart.yaml file

The 'Charts.yaml' file contains the Charts metadata. Here is an example content:

apiVersion: v2
version: 0.0.1 # Sementic Versioning (SemVer)
name: myapp
description: Helm Chart for myapp
type: application # or 'library'
appVersion: 10.2.3

All the available fields for that file are listed here. The 'type' field indicates the type of the chart we are creating. There are two types of charts: 'application' or 'library'.

Application charts are the classic charts we use for deploying Kubernetes apps. That's the one we are going to create in this post.

Library charts are useful only during charts development for adding new utilities or functions into the rendering pipeline. They do not contain any Kubernetes resources templates and therefore cannot be deployed.

The fields apiVersion, name and version are mandatory.

The charts directory

The 'charts' directory is used to store dependencies charts packages. The required dependencies charts are declared using the 'dependencies' field inside the Chart.yaml file.

Custom 'values' for the dependencies Charts are set under the key corresponding to the name of the dependencies. Here is an example:

# File: Charts.yaml

(...)
dependencies:
  - name: ingress-nginx
    repository: https://helm.nginx.com/stable
  (...)
(...)

# File: values.yaml

nginx:
  # Charts values from the ingress-nginx Helm Charts repository:
  # https://github.com/kubernetes/ingress-nginx/blob/main/charts/ingress-nginx/values.yaml
  namespaceOverride: mynamespace
  controller:
    name: mycontroller
  (...)

The templates directory

The 'templates' directory contains the '.yaml' Kubernetes resources manifests files. Those files contain the definition of the Kubernetes resources that could be deployed using the Chart.

The Go templating engine syntax and functions can be used inside those files. Sprig functions and additional Helm built-in objects can also be used. For a list of all functions available inside templated Kubernetes resources manifests, have a look at Helm chart template functions list.

Note that template files prefixed with the underscore (_) sign won't generate an interpreted output, but their content could be used inside other templated manifests. This is the case for the '_helpers.tpl' file for instance, that is used for storing reusable templates blocks. Here is an example:

# File: templates/_helpers.tpl

{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride }}
{{- end -}}

# File: templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "name" . }}
  (...)

The NOTES.txt file

The content of the 'NOTES.txt' file will be printed to standard output after a release has been created from the Chart. That file is also compatible with the Go templating language. Here is an example content:

=> Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
  {{- end }}
{{- end }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "..name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

The values.yaml file

The 'values.yaml' file contains the Chart default configuration parameters that could be set by the Chart users to customize their releases. Releases customizations could be for instance specifying a custom name for all the releases resources, enabling/disabling specific resources or features, setting custom values for specific fields of certain resources. Here is an example content:

# values.yaml
nameOverride: myapp
image:
  repository: selenium/node-firefox
  tag: 3.141.59
replicaCount: 3
autoscaling:
  enabled: false
(...)

All the Charts parameters specified as values (via values.yaml or the '--set' Helm CLI flag) are exported into the Helm '.Values' object that is accessible in templates files. Here is an example of using Charts values parameters inside a deployment resource manifest template:

# File: templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  (...)
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  (...)

Helm Charts templating basics

  • To render Kubernetes resources manifests from a Helm Chart directory, use:
    • helm template $path_to_chart_directory --debug
  • Templating directives and functions are put inside double curly braces {{ }}
  • Here is how we can use comments inside templates files, single or multiline: {{/* my comment */}}
  • The - sign is used at the begining of the double curly braces like this {{- }} or at the end {{ -}} in order to respectively remove all whitespaces characters before or after the templating directives. Here are examples:
# Without '-', whitespaces before and after 
# the double curly braces '{{ }}' are preserved

# File: templates/test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ "myapp" }}
  labels:
    {{ "app.kubernetes.io/name: myapp" }}

# Result
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app.kubernetes.io/name: myapp
    
# Removing all whitespaces before the label

# File: templates/test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ "myapp" }}
  labels:
    {{- "app.kubernetes.io/name: myapp" }}
    
# Result
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:app.kubernetes.io/name: myapp
  • The {{- }} is commonly used for templating blocks defined inside the '_helpers.tpl' file. It is also commonly used inside Kubernetes resources templates, combined with the nindent function, that add a new line and the desired number of spaces for indentation. Here is an example:
# Removing all whitespaces before the label

# File: templates/test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ "myapp" }}
  labels:
    {{- "app.kubernetes.io/name: myapp" }}
    
# Result
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:app.kubernetes.io/name: myapp
  
# Adding a new line + 4 spaces for indentation

# File: templates/test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ "myapp" }}
  labels:
    {{- "app.kubernetes.io/name: myapp" | nindent 4 }}
    
# Result
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app.kubernetes.io/name: myapp
  • The {{ -}} is most of the time used when defining templating blocks inside the '_helpers.tpl' file. Here is an example:
# File: templates/_helpers.tpl
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride }}
{{- end }}
  • The dot {{ . }} is an object containing all the values and other Helm built-in objects or a portion of the values depending on the location from where it is called from inside a template file. Here is an example where we get all the objects:
# File: values.yaml
(...)
autoscaling:
  enabled: true
env:
  - name: HUB_HOST
    value: "selenium-hub"
  - name: HUB_PORT
    value: "4444"
  - name: NODE_MAX_INSTANCES
    value: "1"
  - name: NODE_MAX_SESSION
    value: "1"
(...)

# File: templates/test.yaml
{{ toYaml . }}

# Result
$ helm template . --debug
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /home/gmkziz/helm-charts/mychart

---
# Source: myapp/templates/test.yaml
Capabilities:
  APIVersions:
  - v1
  (...)
  HelmVersion:
    git_commit: 414ff28d4029ae8c8b05d62aa06c7fe3dee2bc58
    (...)
  KubeVersion:
    Major: "1"
    (...)
  IsRoot: true
  apiVersion: v2
  appVersion: 10.2.3
  description: Helm Chart for myapp
  name: myapp
  type: application
  version: 0.0.1
Files:
  (...)
Release:
  IsInstall: true
  (...)
Subcharts: {}
Template:
  BasePath: myapp/templates
  Name: myapp/templates/test.yaml
Values:
  autoscaling:
    enabled: true
  env:
  - name: HUB_HOST
    value: selenium-hub
  - name: HUB_PORT
    value: "4444"
  - name: NODE_MAX_INSTANCES
    value: "1"
  - name: NODE_MAX_SESSION
    value: "1"
  (...)
  • Now, let's change the scope with a range block and see that the {{ . }} contains only objects inside that scope:
# File: values.yaml
(...)
autoscaling:
  enabled: true
env:
  - name: HUB_HOST
    value: "selenium-hub"
  - name: HUB_PORT
    value: "4444"
  - name: NODE_MAX_INSTANCES
    value: "1"
  - name: NODE_MAX_SESSION
    value: "1"
(...)

# File: templates/test.yaml
{{- range .Values.env }}
{{ toJson . }}
{{- end }}

# Result
$ helm template . --debug
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /home/gmkziz/helm-charts/mychart

---
# Source: myapp/templates/test.yaml
{"name":"HUB_HOST","value":"selenium-hub"}
{"name":"HUB_PORT","value":"4444"}
{"name":"NODE_MAX_INSTANCES","value":"1"}
{"name":"NODE_MAX_SESSION","value":"1"}
  • Objects we get when using {{ . }} depend on the scope or context we are currently in
  • The scope from where we get all the Helm built-in objects is called the root scope
  • We can get the root scope objects from anywhere inside template files by using the $ sign. Example:
# File: values.yaml
(...)
autoscaling:
  enabled: true
env:
  - name: HUB_HOST
    value: "selenium-hub"
  - name: HUB_PORT
    value: "4444"
  - name: NODE_MAX_INSTANCES
    value: "1"
  - name: NODE_MAX_SESSION
    value: "1"
(...)

# File: templates/test.yaml
{{- with .Values.autoscaling  }}
{{ toJson . }}
{{ toJson $.Values.env }}
{{- end }}

# Result
$ helm template . --debug
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /home/gmkziz/helm-charts/mychart

---
# Source: myapp/templates/test.yaml
{"enabled":true}
[{"name":"HUB_HOST","value":"selenium-hub"},{"name":"HUB_PORT","value":"4444"},{"name":"NODE_MAX_INSTANCES","value":"1"},{"name":"NODE_MAX_SESSION","value":"1"}]

Conditions and variables in Helm Charts

  • Comparison functions can be found here and here
# File: templates/deployment.yaml

# if autoscaling.enabled is not set to true,
# set the number of replicas from the replicaCount value
apiVersion: apps/v1
kind: Deployment
metadata:
  (...)
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  (...)

# Set resource type depending on the podController value
apiVersion: apps/v1
{{- if eq .Values.podController "Deployment" }}
kind: Deployment
{{- else if eq .Values.podController "Statefulset" }}
kind: Statefulset
{{- else }}
kind: Deployment
{{- end }}
metadata:
  (...)
  
# File: templates/ingress.yaml

{{/* Defining variables */}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  (...)

{{/* Referencing variables */}}
(...)
spec:
  (...)
  rules:
    - host: myhost.local
      http:
        paths:
          - path: /
            backend:
              service:
                name: myservice
                port:
                  number: {{ $svcPort }}

Commonly used Helm Charts actions

  • define
  • include
  • with
  • range

Commonly used Helm Charts functions

Helm template functions list

  • nindent
  • toYaml
  • trunc
  • trimSuffix
  • replace
  • default
  • printf

Templating Helm Charts resources names

A common way to define Charts resources names is this:

  • '.Values.fullnameOverride' if defined, otherwise
  • a combination of '.Release.Name' and '.Values.nameOverride' if '.Values.nameOverride' is defined and does not contain '.Release.Name', otherwise
  • A combination of '.Release.Name' and '.Chart.Name' if '.Chart.Name' does not contain '.Release.Name', otherwise
  • '.Release.Name'

The resources names should also be truncated at 63 characters because of a limitation of some of the Kubernetes name fields (DNS naming spec) and the - suffix trimmed.

Here is an example templating block for defining resources names:

# File: templates/_helpers.tpl

{{- define "fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

Templating Helm Charts resources labels

Here are common labels keys we can use in our Charts:

  • helm.sh/chart: a combination of the Chart name and version
  • app.kubernetes.io/version: the Chart application version
  • app.kubernetes.io/managed-by: Helm
  • app.kubernetes.io/name: the name of the Chart application
  • app.kubernetes.io/instance: the name of the release

app.kubernetes.io/name and app.kubernetes.io/instance are commonly used as selector labels for Deployments and StatefulSets type resources.

Here are labels usage examples in Helm Charts:

# File: templates/_helpers.tpl
# Define reusable templating blocks

{{/* 
Return .Values.nameOverride if defined, 
otherwise .Chart.name
*/}}
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride }}
{{- end -}}

{{- define "labels" -}}
helm.sh/chart: {{ .Chart.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{ include "selectorLabels" . }}
{{- end }}

{{- define "selectorLabels" -}}
app.kubernetes.io/name: {{ include "name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

# File: templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  (...)
  labels:
    {{- include "labels" . | nindent 4 }}
spec:
  (...)
  selector:
    matchLabels:
      {{- include "selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "selectorLabels" . | nindent 8 }}
(...)

Creating and packaging a simple Helm Chart

Let's create an example Helm chart that will be used to create a deployment type resource. Here is the content of the Chart folder:

$ tree helm-chart-example/
helm-chart-example/
├── charts
├── Chart.yaml
├── NOTES.txt
├── templates
│   ├── deployment.yaml
│   └── _helpers.tpl
└── values.yaml
  • Content of the Chart.yaml file
apiVersion: v2
version: 0.0.1
name: myapp
description: Helm Chart for myapp
type: application
appVersion: 10.2.3
  • Content of the _helpers.tpl file
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride }}
{{- end -}}

{{/* 
The fullname tempating block will be 
used to set resources names
*/}}
{{- define "fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := include "name" . }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name }}
{{- end }}
{{- end }}
{{- end }}

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

{{- define "labels" -}}
helm.sh/chart: {{ include "chart" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{ include "selectorLabels" . }}
{{- end }}

{{- define "selectorLabels" -}}
app.kubernetes.io/name: {{ include "name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
  • Content of the deployment.yaml file
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "fullname" . }}
  labels:
    {{- include "labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.volumes }}
      volumes:
        {{- toYaml . | nindent 8 }}
      {{- end }}  
      containers:
      - name: {{ include "name" . }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
        {{- with .Values.volumeMounts }}
        volumeMounts:
          {{- toYaml . | nindent 8 }}
        {{- end }}  
        {{- with .Values.env }}
        env:
          {{- toYaml . | nindent 8 }}
        {{- end }}
        {{- with .Values.resources }}
        resources:
          {{- toYaml . | nindent 10 }}
        {{- end }}
  • Content of the values.yaml file
nameOverride: myapp2
fullnameOverride: 
image:
  repository: selenium/node-firefox
  tag: 3.141.59
replicaCount: 3
autoscaling:
  enabled: true
volumes:
- name: dshm
  emptyDir:
    medium: Memory
volumeMounts:
  - name: dshm
    mountPath: /dev/dshm
env:
  - name: HUB_HOST
    value: "selenium-hub"
  - name: HUB_PORT
    value: "4444"
  - name: NODE_MAX_INSTANCES
    value: "1"
  - name: NODE_MAX_SESSION
    value: "1"
resources:
  limits:
    memory: "2000Mi"
  requests:
    memory: "2000Mi"
  • To output the resulting manifests contents to stdout from the Chart, we use:
# From inside the helm-chart-example directory
$ helm template --debug .
  • To create a Chart package, we use:
$ helm package helm-chart-example
Successfully packaged chart and saved it to: /home/gmkziz/helm-charts/myapp-0.0.1.tgz

Creating a release using the example Helm Chart

$ helm show values myapp-0.0.1.tgz > values.yaml
nameOverride: primary
fullnameOverride: 
image:
  repository: selenium/node-firefox
  tag: 3.141.59
replicaCount: 3
(...)

# Set the custom Charts values as desired inside the values.yaml file,
# then:

$ helm upgrade --install myapp -f values.yaml myapp-0.0.1.tgz 
NAME: myapp
(...)
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

$ helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
myapp   default         1               <release_creation_date>                 deployed        myapp-0.0.1     10.2.3

$ kubectl get deploy
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
myapp-primary   1/1     1            1           84s

$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
myapp-primary-65596d9948-k7m2z   1/1     Running   0          79s

$ kubectl get deploy -o yaml
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "1"
      meta.helm.sh/release-name: myapp
      meta.helm.sh/release-namespace: default
    creationTimestamp: "***"
    generation: 1
    labels:
      app.kubernetes.io/instance: myapp
      app.kubernetes.io/managed-by: Helm
      app.kubernetes.io/name: primary
      app.kubernetes.io/version: 10.2.3
      helm.sh/chart: myapp
    name: myapp-primary
    namespace: default
    resourceVersion: "990"
    uid: bf577704-2d19-4b43-88ac-a7450c32ced1
  spec:
    progressDeadlineSeconds: 600
    replicas: 1
    revisionHistoryLimit: 10
    selector:
      matchLabels:
        app.kubernetes.io/instance: myapp
        app.kubernetes.io/name: primary
    strategy:
      rollingUpdate:
        maxSurge: 25%
        maxUnavailable: 25%
      type: RollingUpdate
    template:
      metadata:
        creationTimestamp: null
        labels:
          app.kubernetes.io/instance: myapp
          app.kubernetes.io/name: primary
      spec:
        containers:
        - env:
          - name: HUB_HOST
            value: selenium-hub
          - name: HUB_PORT
            value: "4444"
          - name: NODE_MAX_INSTANCES
            value: "1"
          - name: NODE_MAX_SESSION
            value: "1"
          image: selenium/node-firefox:3.141.59
          imagePullPolicy: IfNotPresent
          name: primary
          resources:
            limits:
              memory: 2000Mi
            requests:
              memory: 2000Mi
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
          - mountPath: /dev/dshm
            name: dshm
        dnsPolicy: ClusterFirst
        restartPolicy: Always
        schedulerName: default-scheduler
        securityContext: {}
        terminationGracePeriodSeconds: 30
        volumes:
        - emptyDir:
            medium: Memory
          name: dshm
  status:
    availableReplicas: 1
    conditions:
    - lastTransitionTime: "***"
      lastUpdateTime: "***"
      message: Deployment has minimum availability.
      reason: MinimumReplicasAvailable
      status: "True"
      type: Available
    - lastTransitionTime: "***"
      lastUpdateTime: "***"
      message: ReplicaSet "myapp-primary-74fdbf6bd" has successfully progressed.
      reason: NewReplicaSetAvailable
      status: "True"
      type: Progressing
    observedGeneration: 1
    readyReplicas: 1
    replicas: 1
    updatedReplicas: 1
kind: List
metadata:
  resourceVersion: ""