Tips for Getting Started with Helm

Written by ryandawsonuk | Published 2018/10/15
Tech Story Tags: devops | docker | helm | software-development | kubernetes

TLDRvia the TL;DR App

You’ve got an application that you want to deploy with Helm. Probably you’ve seen the official docs and some examples. But you’re not sure how to apply them to your app. Here are some tips.

1 Know Your Docker Image

Have a docker image/s for your app first and be sure you can work with it. Deploy first with plain Kubernetes descriptors or start with docker-compose.

When you have everything working with plain k8s descriptors then you’ll want to think about how to make deployment more flexible. You may want to pass in deployment switches for different configurations or for different environments. That’s a great time to introduce helm.

2 Understand the Role of the values File

To make sure this is clear, let’s remind ourselves of the structure of a helm chart:

├── Chart.yaml
├── README.md
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── secrets.yaml
│   └── ...more yaml...
└── values.yaml

When the user runs helm install then the entries from the values.yaml in the chart and the helm release information (such as unique release name) get injected into the templated yaml resource descriptors as the template is evaluated and rendered into pure Kubernetes deployment descriptors. When the user runs helm install with parameters or a values file, then the parameters or values file will be overlaid on the one in the chart. Effectively the chart’s values file sets defaults that can be overridden.

If this isn’t clear, try it out. Create a file called mysql-values.yaml:

imageTag: “5.7.10”

Then run helm install stable/mysql --values=mysqlvalues.yaml. Helm generates a unique release name for us (`ignorant-camel`) and mysql is deployed in the cluster. The output of kubectl describe pod ignorant-camel-mysql-5dc6b947b-lf6p8 tells us that our chosen imageTag has been applied.

3 Try Something Out

When you’re done with intalling and overriding values, try creating a basic chart. You can use helm create to get a starter chart that you can modify — there’s an example on how to do this in the official docs. Try it.

Or find a guide online that deploys an app built in the same language you’re using. If you can find an example of a chart for an app that is similar enough to yours then you can copy-paste and use that as a starting point. There are even some tools out there that can do give you starter examples — Jenkins-X for examples has these, which it calls draft packs.

4 Start Simple

Most charts are designed for a single docker image. If you’ve got a microservices app with several interacting parts, you can take a small part of your app that you think you can deploy on its own and try writing a helm chart for that.

5 Start with Environment Variables and `if` conditions

Most charts involve injecting environment variables into docker containers in a Pod. So you’ll see something like this in a deployment.yaml:

- name: ENV_VAR1
  value: {{ .Values.var1 }}
- name: ENV_VAR2
  value: {{ .Values.var2 }

With this, users of the chart can override these values in their values.yaml or with —set var1=foo .

Often there are also options that control whether to enable a configuration. So you might find something like:

- name: ENV_VAR1
  value: {{ .Values.var1 }}
{{- if .Values.var2 }}
- name: ENV_VAR2
  value: {{ .Values.var2 }
{{- end }}

Then ENV_VAR2 will only be set if a value is set for var2. Or you could do

- name: ENV_VAR1
  value: {{ .Values.var1 }}
{{- if .Values.var2enabled }}
- name: ENV_VAR2
  value: {{ .Values.var2 }
{{- end }}

Then ENV_VAR2 will only be set if var2enabled is true.

The if directive is an easy Helm control structure to understand and allows you to do quite a lot.

6 You Can Use Different values Files

We’ve already seen that we can create a custom mysqlvalues.yaml file and pass it into the official mysql chart. We can do that with our own charts too. This can be very handy if we’ve got different configurations we want to apply for different environments e.g. staging and prod.

7 Expect to Debug

Helm install with the --dry-run --debug option is immensely useful. It doesn’t deploy your chart for real but it does run the checks involved in a deploy so you’ll know if you’ve done something fundamentally wrong. It also shows the Kubernetes descriptors that are generated. This let’s you check that your chart is doing what you want it to do.

8 Don’t Worry About Releasing Yet

Eventually you will probably want to package your charts and release them in a chart repository. But that’s only needed when you want others to be able to consume your charts and build on top of them. To begin with you can work from just source code as it makes things easier. If they’re in source control then others can check them out of source control in the same way you do until you get a charts repository set up.

9 Understand Release Names

We saw before that when we installed the mysql chart Helm generated the release name ‘ignorant-camel’ for the installation we created. This release name is injected by helm into the kubernetes deployment descriptors that helm generates from our chart. This ensures that every resource that is deployed has a unique name (and kubernetes needs it to), even if we install the chart multiple times. This happens because the templates contain content like:

apiVersion: v1
kind: Service
metadata:
  name:  {{ .Release.Name }}-myservice

So {{ .Release.Name }} is a placeholder into which Helm will inject the release name. For many charts you’ll more likely see this as:

apiVersion: v1
kind: Service
metadata:
  name: {{ template "fullname" . }}

This amounts to the same thing as the template fullname function is defined in _helpers.tpl in the chart and uses release name and chart name.

You need to be aware of this when you start deploying multiple resource in your chart which need to reference one another. If the resources are defined inside your chart then you need to be careful to use the same names that are used in the definitions by using the same functions. So if you wanted to inject an environment variable into a Pod containing the name of the above Service then you could do:

- name: SERVICE_NAME
  value: {{ template "fullname" . }}

10 Interaction is Tricky

Let’s say you have a bunch of microservices that work together as an app. This might be one big chart if the services are only ever used together and don’t need to be individually deployable. But most likely you do need to be able to separate. So the tricky bit is how to build individual charts for each part and also get them to talk to each other.

So you’re going to want to use subcharts. This lets us include other charts within our chart to create a composite/umbrella chart. We do this by referencing the chart we want to include in the requirements.yaml and including it in the charts directory of our chart. If we’re including a chart we’ve defined ourselves (and haven’t published yet) called mysubchart, we can put this in the requirements.yaml:

- name: mysubchart
  version: 0.1.0

And put the whole source code of mysubchart (its whole directory) in the charts directory of our umbrella chart. If we were including an official/published chart then we’d include its published location in the requirements.yaml:

- name: postgresql                         
  repository: https://kubernetes-charts.storage.googleapis.com
  version: 0.11.0

Then we would do a helm dep build before deploying the chart.

When charts have resources that need to references in other subcharts then things get a bit trickier. You need to know what the names are of the resources you’re looking for. The names are typically generated dynamically to include a release name.

Hopefully the chart will make this clear in its documentation but you might have to go to its source code. As an example, the official postgresql chart can create a secret containing the password we set for the postgresql instance. If we’re using postgresql in our app (adding it as a subchart) then we’ll want to reference this and inject it into an environment variable.

We can look at how that secret is defined or consult examples of how it is used to work out we need to set our environment variable using:

— name: POSTGRESS_PASSWORD
  valueFrom:
    secretKeyRef:
      name: {{ .Release.Name }}-postgresql
      key: postgres-password

This works since the postgres chart is included in ours. Its resources are part of the same release so the same release name is used.

The complexity of these references can grow depending on how the charts need to interact and how they will be used. For example, what if you need to support either creating a postgres along with your chart or pointing at an existing one? Or provide the option to switch to mysql? This sort of thing can get complex. The official charts show patterns for handling these kinds of cases but you’re best to start by limiting options so that initially you just have to handle the simpler case/s.

11 Duplication Across Charts Can Be OK

Each chart is for deploying a specific docker image or images. (Different tags or versions of the same image are normally handled by the same chart.) But what if you’ve got several images that are quite similar? Can we re-use sections across different charts?

The offical helm documentation gives an example of how you can ‘inherit’ features from a parent chart. It defines a chart called mychart that includes a subchart called mysubchart in its charts directory. The values.yaml for mysubchart contains:

dessert: cake

This value goes into a ConfigMap resource created by mysubchart. The values.yaml of mychart can then override this with:

mysubchart:
  dessert: ice cream

So mychart can define the same ConfigMap defined by mysubchart because it includes mysubchart but it can give it different content by overriding the values.

You might therefore use a base chart to define common resources and have other charts include that chart and override them in the way that they need to. For example, you might imagine having a base chart for java applications. However, you don’t currently see this pattern used very much in that way. Actually, many of the official helm charts include sections that are pretty much the same for many charts. Lots handle ingress in the same way. Rather than try to re-use a common parent, they duplicate. (Actually they try to broadly follow a common guideline.) Why?

One reason is that helm has no notion of an abstract parent. Each chart, including the parent, is meant to be individually deployable. If it isn’t then it gets a bit tricky to test it.

Subcharts are more like overrideable members of a chart than parents in OO languages. Each has to be addressed with a prefix (in our example mysubchart). These names would grow quite awkward if you had multiple layers. You can’t modify resources in a subchart unless they are exposed through values. This means having to go and change a subchart in order to change the chart consuming it, which gets more awkward when charts are released as then each change is a version change and requires a rebuild and release.

Further, if there were a common parent for defining Ingress, it would need to refer to the Service being exposed. That means having to inject the service name into the Ingress resource. Since the parent would really be an included 3rd-party chart, that would bring in all the complexity of interactions we saw in the last section.

This is not to say that we shouldn’t try to avoid duplication. If we’ve got several charts that are deployed together and all need the same set of environment variables then using globals or the option to point at a common ConfigMap could make a lot of sense. It’s also possible to reduce duplication in the content of templates with named templates but that’s a bit more complex (although example here).

The point is that you can save yourself some headaches initially by duplicating content and looking to reduce duplication later if you find the duplication to be a problem. (Hopefully some of this will get smoother in Helm3 as the area is under discussion.)

12 You Don’t Have to Automate Your Helm Testing Right Away

Probably you’re testing your app after deploying it anyway. If so, don’t feel you have to go full TDD your chart deployment process. Helm does have options for that and it’ll be great to use them but it’s more to learn.

13 It’s OK to Treat Helm Like a Box of Tricks

Many programmers are used to there being established design patterns to follow. From this perspective it’s surprising how quickly you end up looking at the helm ‘tips and tricks’ pages or documentation of template functions. Embrace this as OK. There’s lots of variation in the official Kubernetes charts. It’s ok to do things in the way that works for you.

14 Help Yourself Out with the Official Charts

Official charts deal with things you won’t have thought of and they are maintained. So if you need a mysql database or anything else you can find in the official charts, look to use it instead of creating your own chart for that.

Note that the official charts are quite complex because they have to be usable in so many ways. Don’t feel you need to delve into their code and follow their example right away, although you might later benefit from that. Initially you can just use them as it suits you. Each should have an explanation of how to use it.

15 Use the Helm Community

If you join the official kubernetes slack, there are channels there for helm and for helm charts. You can get great advice there. Also don’t be afraid to ask on stackoverflow. You are not alone.

Takeaways

There’s a lot to learn with Helm and it’s quite different from the approaches that many programmers are used to. Break it down and take it one step at a time. You’ll be making waves faster than you think.


Written by ryandawsonuk | Principal Data Consultant at ThoughtWorks. Hackernoon Contributor of the Year - Engineering.
Published by HackerNoon on 2018/10/15