Simplify Kubernetes deployments with Helm (Part 2)

Aug 17, 2017 | Announcements, Migration, MSP

In our first installment of this blog series we introduced you to Helm and gave you an overview. Need a quick refresh?

Read “Simplify Kubernetes deployments with Helm (Part 1)”

Now let’s dig a little deeper. Let’s investigate the Chart we have been using for our example. Instead of installing the Chart like we did in the first part, now we just want to download it so we use the fetch option of the Helm command:

helm fetch stable/mysql

This will download a compressed tarball of the mysql chart into the current directory. Extract and untar it and you will see the following structure:

├── Chart.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── pvc.yaml
│   ├── secrets.yaml
│   └── svc.yaml
└── values.yaml

As we wrote in the first blog in this series, a Helm Chart packages everything to create a deployment, or as Helm calls it, a release of our application. Let us now focus on service and the Kubernetes deployment creation — two of the most important building blocks of Kubernetes.

So let’s have a look at the templates/svc.yaml

apiVersion: v1
kind: Service
  name: {{ template "fullname" . }}
    app: {{ template "fullname" . }}
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    release: "{{ .Release.Name }}"
    heritage: "{{ .Release.Service }}"
  - name: mysql
    port: 3306
    targetPort: mysql
    app: {{ template "fullname" . }}

This represents, of course, a fairly standard k8s service description, with the exception that most of the parameters still need to be rendered. So let’s look at the metadata part of this template. Most of these derive from what Helm calls predefined values. First, we see that the values necessary to generate the label named “chart” are stored in the Chart.yaml file .Chart.Version: 0.2.6 and .Chart.Name: mysql.
The values for the release and heritage labels are set when the Helm install/upgrade command is being executed. Here is an example print out for the .Release map, when we ran the helm init command:


The service name parameter uses the variable “fullname” as it is defined in the _helpers.tpl templates, a file you can find in almost all charts. For more information on the file naming conventions see the best practices guide


We can see the result of the rendered service template, when we run the command (Note: don’t go into the details of Kubernetes services, we are only interested in how templates are rendered):

kubectl get services joyous-pike-mysql -o yaml
apiVersion: v1
kind: Service
  creationTimestamp: 2017-07-09T06:09:43Z
    app: joyous-pike-mysql                   <- fullname
    chart: mysql-0.2.6                       <- .Chart.Name-.Chart.Version
    heritage: Tiller                         <- .Release.Service
    release: joyous-pike                     <- .Release.Name
  name: joyous-pike-mysql                    <- fullname
  namespace: default
  resourceVersion: "203260"
  selfLink: /api/v1/namespaces/default/services/joyous-pike-mysql
  uid: 33549c4a-646d-11e7-821b-08002744cb40
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: mysql
    app: joyous-pike-mysql                   <- fullname
  sessionAffinity: None
  type: ClusterIP
  loadBalancer: {}

One option we haven’t used is setting parameters via the values.yml file, which we can do now as we check how the k8s deployment is being created. So let’s look at the deployment.yml file. The metadata part is pretty much the same as it is with services, so we skip that and instead go to the very end of the file first. Remember how we created a mysql deployment in the Helm example in our first part? Well, here is the code that allowed us to deploy mysql with or without a persistent volume:

      - name: data
      {{- if .Values.persistence.enabled }} <-  is set in the values.yaml file (default) 
          claimName: {{ template "fullname" . }}
      {{- else }}
        emptyDir: {}
      {{- end -}}

This is a simple example for a conditional statement, but this allows us to deploy our application in various environments, and even use it locally when we don’t have or need access to a persistent volume.

Let’s go back up a bit to where the containers for the pod are defined:

      - name: {{ template "fullname" . }}
        image: "{{ .Values.image }}:{{ .Values.imageTag }}"
        imagePullPolicy: {{ .Values.imagePullPolicy | quote }}
{{ toYaml .Values.resources | indent 10 }}

As in the volume example parameter, values are set in the values.yaml, but we see a few more constructs. The first one {{ .Values.imagePullPolicy | quote }} puts quotes around the parameter, so the rendered yaml is compliant. The same is true for the second, more complex example {{ toYaml .Values.resources | indent 10 }}. Whereas toYaml makes sure the value in .Values.resources is read as valid yaml, indent 10 helps to ensure the rendered yaml file is formatted correctly.

As the last example, we want to glance over the use of the default function:

 - name: MYSQL_USER
          value: {{ default "" .Values.mysqlUser | quote }}

Here the default is set to an empty string unless .Values.mysqlUser is set either in the values.yaml file or via --set in the command line. The result of this is then put in quotes.

Here is a plugin that will make developing Helm Charts a little easier while working on a customer project that will use Helm extensively I found a great plugin that can help you with developing Helm configuration files. Perhaps a bit confusing, it’s called template. The installation is very simple:

helm plugin install

It renders chart templates locally and displays the output while not requiring Tiller or any access to a Kubernetes cluster. The simplest way to run it is to execute the following command in your Charts directory:

helm template .

So now we have seen how to specify parameters for a particular Helm release. We showed some simple examples that can, of course, be applied to other Kubernetes components such as stateful and daemon sets. In the next installment in this series, we will work with secrets and configmaps, and show more complex use cases of the Helm building blocks.