Chart Development Tips and Tricks

This guide covers some of the tips and tricks Helm chart developers havelearned while building production-quality charts.

Know Your Template Functions

Helm uses Go templates for templatingyour resource files. While Go ships several built-in functions, we haveadded many others.

First, we added almost all of the functions in theSprig library. We removed twofor security reasons: env and expandenv (which would have given chart authorsaccess to Tiller's environment).

We also added two special template functions: include and required. The includefunction allows you to bring in another template, and then pass the results to othertemplate functions.

For example, this template snippet includes a template called mytpl, thenlowercases the result, then wraps that in double quotes.

  1. value: {{ include "mytpl" . | lower | quote }}

The required function allows you to declare a particularvalues entry as required for template rendering. If the value is empty, the templaterendering will fail with a user submitted error message.

The following example of the required function declares an entry for .Values.whois required, and will print an error message when that entry is missing:

  1. value: {{ required "A valid .Values.who entry required!" .Values.who }}

When using the include function, you can pass it a custom object tree built from the current context by using the dict function:

  1. {{- include "mytpl" (dict "key1" .Values.originalKey1 "key2" .Values.originalKey2) }}

Quote Strings, Don't Quote Integers

When you are working with string data, you are always safer quoting thestrings than leaving them as bare words:

  1. name: {{ .Values.MyName | quote }}

But when working with integers do not quote the values. That can, inmany cases, cause parsing errors inside of Kubernetes.

  1. port: {{ .Values.Port }}

This remark does not apply to env variables values which are expected to be string, even if they represent integers:

  1. env:
  2. -name: HOST
  3. value: "http://host"
  4. -name: PORT
  5. value: "1234"

Using the 'include' Function

Go provides a way of including one template in another using a built-intemplate directive. However, the built-in function cannot be used inGo template pipelines.

To make it possible to include a template, and then perform an operationon that template's output, Helm has a special include function:

  1. {{- include "toYaml" $value | nindent 2 }}

The above includes a template called toYaml, passes it $value, andthen passes the output of that template to the nindent function. Usingthe {{- … | nindent n }} pattern makes it easier to read the includein context, because it chomps the whitespace to the left (including theprevious newline), then the nindent re-adds the newline and indentsthe included content by the requested amount.

Because YAML ascribes significance to indentation levels and whitespace,this is one great way to include snippets of code, but handleindentation in a relevant context.

Using the 'required' function

Go provides a way for setting template options to control behaviorwhen a map is indexed with a key that's not present in the map. Thisis typically set with template.Options("missingkey=option"), where optioncan be default, zero, or error. While setting this option to error willstop execution with an error, this would apply to every missing key in themap. There may be situations where a chart developer wants to enforce thisbehavior for select values in the values.yml file.

The required function gives developers the ability to declare a value entryas required for template rendering. If the entry is empty in values.yml, thetemplate will not render and will return an error message supplied by thedeveloper.

For example:

  1. {{ required "A valid foo is required!" .Values.foo }}

The above will render the template when .Values.foo is defined, but will failto render and exit when .Values.foo is undefined.

Using the 'tpl' Function

The tpl function allows developers to evaluate strings as templates inside a template.This is useful to pass a template string as a value to a chart or render external configuration files.Syntax: {{ tpl TEMPLATE_STRING VALUES }}

Examples:

  1. # values
  2. template: "{{ .Values.name }}"
  3. name: "Tom"
  4.  
  5. # template
  6. {{ tpl .Values.template . }}
  7.  
  8. # output
  9. Tom

Rendering a external configuration file:

  1. # external configuration file conf/app.conf
  2. firstName={{ .Values.firstName }}
  3. lastName={{ .Values.lastName }}
  4.  
  5. # values
  6. firstName: Peter
  7. lastName: Parker
  8.  
  9. # template
  10. {{ tpl (.Files.Get "conf/app.conf") . }}
  11.  
  12. # output
  13. firstName=Peter
  14. lastName=Parker

Creating Image Pull Secrets

Image pull secrets are essentially a combination of registry, username, and password. You may need them in an application you are deploying, but to create them requires running base64 a couple of times. We can write a helper template to compose the Docker configuration file for use as the Secret's payload. Here is an example:

First, assume that the credentials are defined in the values.yaml file like so:

  1. imageCredentials:
  2. registry: quay.io
  3. username: someone
  4. password: sillyness

We then define our helper template as follows:

  1. {{- define "imagePullSecret" }}
  2. {{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.imageCredentials.registry (printf "%s:%s" .Values.imageCredentials.username .Values.imageCredentials.password | b64enc) | b64enc }}
  3. {{- end }}

Finally, we use the helper template in a larger template to create the Secret manifest:

  1. apiVersion: v1
  2. kind: Secret
  3. metadata:
  4. name: myregistrykey
  5. type: kubernetes.io/dockerconfigjson
  6. data:
  7. .dockerconfigjson: {{ template "imagePullSecret" . }}

Automatically Roll Deployments When ConfigMaps or Secrets change

Often times configmaps or secrets are injected as configurationfiles in containers.Depending on the application a restart may be required should thosebe updated with a subsequent helm upgrade, but if thedeployment spec itself didn't change the application keeps runningwith the old configuration resulting in an inconsistent deployment.

The sha256sum function can be used to ensure a deployment'sannotation section is updated if another file changes:

  1. kind: Deployment
  2. spec:
  3. template:
  4. metadata:
  5. annotations:
  6. checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
  7. [...]

See also the helm upgrade —recreate-pods flag for a slightlydifferent way of addressing this issue.

Tell Tiller Not To Delete a Resource

Sometimes there are resources that should not be deleted when Helm runs ahelm delete. Chart developers can add an annotation to a resource to preventit from being deleted.

  1. kind: Secret
  2. metadata:
  3. annotations:
  4. "helm.sh/resource-policy": keep
  5. [...]

(Quotation marks are required)

The annotation "helm.sh/resource-policy": keep instructs Tiller to skip thisresource during a helm delete operation. However, this resource becomesorphaned. Helm will no longer manage it in any way. This can lead to problemsif using helm install —replace on a release that has already been deleted, buthas kept resources.

To explicitly opt in to resource deletion, for example when overriding a chart'sdefault annotations, set the resource policy annotation value to delete.

Using "Partials" and Template Includes

Sometimes you want to create some reusable parts in your chart, whetherthey're blocks or template partials. And often, it's cleaner to keepthese in their own files.

In the templates/ directory, any file that begins with anunderscore(_) is not expected to output a Kubernetes manifest file. Soby convention, helper templates and partials are placed in a_helpers.tpl file.

Complex Charts with Many Dependencies

Many of the charts in the official charts repositoryare "building blocks" for creating more advanced applications. But charts may beused to create instances of large-scale applications. In such cases, a singleumbrella chart may have multiple subcharts, each of which functions as a pieceof the whole.

The current best practice for composing a complex application from discrete partsis to create a top-level umbrella chart thatexposes the global configurations, and then use the charts/ subdirectory toembed each of the components.

Two strong design patterns are illustrated by these projects:

SAP's Converged charts: These chartsinstall SAP Converged Cloud a full OpenStack IaaS on Kubernetes. All of the charts are collectedtogether in one GitHub repository, except for a few submodules.

Deis's Workflow:This chart exposes the entire Deis PaaS system with one chart. But it's differentfrom the SAP chart in that this umbrella chart is built from each component, andeach component is tracked in a different Git repository. Check out therequirements.yaml file to see how this chart is composed by their CI/CDpipeline.

Both of these charts illustrate proven techniques for standing up complex environmentsusing Helm.

YAML is a Superset of JSON

According to the YAML specification, YAML is a superset of JSON. Thatmeans that any valid JSON structure ought to be valid in YAML.

This has an advantage: Sometimes template developers may find it easierto express a data structure with a JSON-like syntax rather than deal withYAML's whitespace sensitivity.

As a best practice, templates should follow a YAML-like syntax _unless_the JSON syntax substantially reduces the risk of a formatting issue.

Be Careful with Generating Random Values

There are functions in Helm that allow you to generate random data,cryptographic keys, and so on. These are fine to use. But be aware thatduring upgrades, templates are re-executed. When a template rungenerates data that differs from the last run, that will trigger anupdate of that resource.

Upgrade a release idempotently

In order to use the same command when installing and upgrading a release, use the following command:

  1. helm upgrade --install <release name> --values <values file> <chart directory>