Helm

Running instance of a chart with a specific config is called a release.
Helm tracks an installed chart in the Kubernetes cluster using releases. This allows us to install a single chart multiple times with different releases in a cluster. Releases are stored as Secrets by default in the namespace of the release directly.
We can share charts as archives through repositories. It is basically a location where packages charts can be stored and shared. There is a distributed community chart repository by the name Artifact Hub where we can collaborate. We can also create our own private chart repositories. We can add any number of chart repositories to work with.
Example
helm create hello-worldThe resulting structure will be
hello-world /ram
  Chart.yaml. <= This is the main file that contains the description of our chart
  values.yaml <= Default values for our chart 
  templates / <= Where Kubernetes resources are defined as templates
  charts /    <= Optional directory that may contain sub-charts
  .helmignore <= Define patterns to ignore when packagingHelm makes use of the Go template language and extends that to something called Helm template language. During the evaluation, every file inside the template directory is submitted to the template rendering engine. This is where the template directive injects actual values into the templates.
Template language
How to run below experiment: in generated chart by command helm create remove all files from templates folder and add only below content to file templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"{{ .Release.Name }} 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. 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".
Chart local render
helm install --debug --dry-run goodly-guppy-fake-name ./YOUR_CHART_FOLDERIf rendering fails try to use following command
helm install --dry-run --disable-openapi-validation moldy-jaguar ./mychartThere are a few commands that can help you debug.
helm lintis your go-to tool for verifying that your chart follows best practiceshelm template --debugwill test rendering chart templates locally.helm install --dry-run --debugwill also render your chart locally without installing it, but will also check if conflicting resources are already running on the cluster. Setting--dry-run=serverwill additionally execute anylookupin your chart towards the server.helm get manifest: This is a good way to see what templates are installed on the server.
Built-in objects
🔗More details. This can be references as e.g. {{ .Release.Name }}
Release
Values. Empty by default, comes from
values.yamlfile and from user-supplied filesChart: The contents of the
Chart.yamlfileSubcharts
Files
Capabilities: This provides information about what capabilities the Kubernetes cluster supports.
Template: Contains information about the current template that is being executed
The built-in values always begin with a capital letter.
Values files
This object provides access to values passed into the chart. Its contents come from multiple sources (in order of precedence)
The
values.yamlfile in the chartIf this is a subchart, the
values.yamlfile of a parent chartA values file is passed into
helm installorhelm upgradewith the-fflag (helm install -f myvals.yaml ./mychart)Individual parameters are passed with
--set(such ashelm install --set foo=bar ./mychart)
Assuming values.yaml has following content
favoriteDrink: coffee
favorite:
  food: pizzaWe can use it inside of the template
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favoriteDrink }}
  food: {{ .Values.favorite.food }}If you need to delete a key from the default values, you may override the value of the key to be null 
Template Functions and Pipelines
Template functions follow the syntax functionName arg1 arg2... 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ quote .Values.favorite.drink }}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.
Pipelines
Like in Unix
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink | repeat 5 | quote }}
  food: {{ .Values.favorite.food | upper | quote }}Results in
  drink: "coffeecoffeecoffeecoffeecoffee"
  food: "PIZZA"There is a useful command default 
drink: {{ .Values.favorite.drink | default "tea" | quote }}the default command is perfect for computed values, which cannot be declared inside values.yaml. For example:
drink: {{ .Values.favorite.drink | default (printf "%s-tea" (include "fullname" .)) }}Flow Control
Helm's template language provides the following control structures:
if/elsefor creating conditional blockswithto 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:
definedeclares a new named template inside of your templatetemplateimports a named templateblockdeclares a special kind of fillable template area
if else
{{ if PIPELINE }}
  # Do something
{{ else if OTHER PIPELINE }}
  # Do something else
{{ else }}
  # Default case
{{ end }}apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  {{ if eq .Values.favorite.drink "coffee" }}mug: "true"{{ end }}Controlling Whitespace
When the template engine runs, it removes the contents inside of {{ and }}, but it leaves the remaining whitespace exactly as is.
The curly brace syntax of template declarations can be modified with special characters to tell the template engine to chomp whitespace. {{- (with the dash and space added) indicates that whitespace should be chomped left, while -}} means whitespace to the right should be consumed. Be careful! Newlines are whitespace!
Lets 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: {{ .Release.Name }}-configmap
data:
  food: {{ .Values.favorite.food | upper | quote }}*
**{{- if eq .Values.favorite.drink "coffee" }}
  mug: "true"*
**{{- end }}The result will be
apiVersion: v1
kind: ConfigMap
metadata:
  name: clunky-cat-configmap
data:
  food: "PIZZA"
  mug: "true"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 ({{ indent 2 "mug:true" }}).
with
{{ with PIPELINE }}
  # restricted scope
{{ end }}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: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- with .Values.favorite }} <= set scope to .Values.favorite
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  release: {{ .Release.Name }} <= this will fail as scope is unreachable
  release: {{ $.Release.Name }} <= this will work as $ is mapped to the root scope
  {{- end }}The block after with only executes if the value of PIPELINE is not empty.
range
Assuming values.yaml
pizzaToppings:
  - mushrooms
  - cheeseapiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  toppings: |-
    {{- range .Values.pizzaToppings }}
    - {{ . | title | quote }}
    {{- end }}    range changes the scope, thats why we access the value by . in - {{ . | title | quote }} 
Note. title (title case function)
The result will be
apiVersion: v1
kind: ConfigMap
metadata:
  name: edgy-dragonfly-configmap
data:
  toppings: |-
    - "Mushrooms"
    - "Cheese"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
Variables
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- $relname := .Release.Name -}} <= variable is assigned
  {{- with .Values.favorite }}
  food: {{ .food | upper | quote }}
  release: {{ $relname }} 
  {{- end }}Variable values does not respect the present scope, so we can use .Release.Name inside of scope .Values.favorite 
Variables are particularly useful in range loops. They can be used on list-like objects to capture both the index and the value:
  toppings: |-
    {{- range $index, $topping := .Values.pizzaToppings }}
      {{ $index }}: {{ $topping }}
    {{- end }}    Note that range comes first, then the variables, then the assignment operator, then the list. This will assign the integer index (starting from zero) to $index and the value to $topping. Running it will produce:
  toppings: |-
      0: mushrooms
      1: cheese
      2: peppers
      3: onions     For data structures that have both a key and a value, we can use range to get both. For example, we can loop through .Values.favorite like this:
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}Now on the first iteration, $key will be drink and $val will be coffee, and on the second, $key will be food and $val will be pizza. Running the above will generate this:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: eager-rabbit-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"There is one variable that will always point to the root context: - $ -. This can be very useful when you are looping in a range and you need to know the chart's release name.
An example illustrating this:
{{- range .Values.tlsSecrets }}
---
apiVersion: v1
kind: Secret
metadata:
  name: {{ .name }}
  labels:
    # Many helm templates would use `.` below, but that will not work,
    # however `$` will work here
    app.kubernetes.io/name: {{ template "fullname" $ }}
    # I cannot reference .Chart.Name, but I can do $.Chart.Name
    helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"
    app.kubernetes.io/instance: "{{ $.Release.Name }}"
    # Value from appVersion in Chart.yaml
    app.kubernetes.io/version: "{{ $.Chart.AppVersion }}"
    app.kubernetes.io/managed-by: "{{ $.Release.Service }}"
type: kubernetes.io/tls
data:
  tls.crt: {{ .certificate }}
  tls.key: {{ .key }}
{{- end }}Named templates
template names are global. If you declare two templates with the same name, whichever one is loaded last will be the one used. Because templates in subcharts are compiled together with top-level templates, you should be careful to name your templates with chart-specific names.
One popular naming convention is to prefix each defined template with the name of the chart: {{ define "mychart.labels" }}
Files whose name begins with an underscore (_) are assumed to not have a k8smanifest inside. These files are not rendered to Kubernetes object definitions, but are available everywhere within other chart templates for use.
Define and template
The define action allows us to create a named template inside of a template file. Its syntax goes like this:
{{- define "MY.NAME" }}
  # body of template here
{{- end }}For example, we can define a template to encapsulate a Kubernetes block of labels:
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}Now we can embed this template inside of our existing ConfigMap, and then include it with the template action:
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" }}Conventionally, Helm charts put these templates inside of a partials file, usually _helpers.tpl. Let's move this function there:
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
    chart: {{ .Chart.Name }}
{{- end }}By convention, define functions should have a simple documentation block ({{/* ... */}}) describing what they do.
Scopes in template
When a named template (created with define) is rendered, it will receive the scope passed in by the template call. In our example, we included the template like this:
{{- template "mychart.labels" }}No scope was passed in, so within the template we cannot access anything in .. This is easy enough to fix, though. We simply pass a scope to the template:
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" . }}include function
Because template is an action, and not a function, there is no way to pass the output of a template call to other functions; the data is simply inserted inline.
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
{{ include "mychart.app" . | indent 4 }} <= include can be used with pipelines
data:
{{ include "mychart.app" . | indent 2 }}Last updated
Was this helpful?