Golden Gate Bridge

Using Helm to Generate k8s Manifests

Helm is a package manager for kubernetes. It allows users to define and share applications in an reliable and repeatable fashion. Helm uses the go templating engine to help users customize charts to meet the needs of each individual deployment. For up-to-date installation instructions, please visit the Helm installation guide.

Before we dive into the details of Helm, lets take a second to understand the idea behind helm. It is useful to think of Helm as a templating engine. While you can use it to install applications into your k8s cluster, it's main use, for me at least, is to generate k8s manifests. I've always thought of Helm in terms of programming. The Helm chart has a lot of similarities to a class object in programing. On it's own, it may set some sane defaults or even have business logic defined to keep applicaions secure while adhearing to best practices. It also allows for engineers who are more familiar with kubernetes to define how applications should be deployed. This abstraction allows deployments to be used by engineers who may not be as familiar with kubernetes to build and deploy applications with ease. All the end users would need to supply are the values needed to tailor the chart to run their specific application. Usually these override files include things like where to find the container image, what version or tag to use, and what commands are needed to run the application.

With that said, lets take a look at the structure of a Helm chart. Helm has a few key components that every chart needs to have in place. The general file structur of a chart should look something like this: Keep in mind that this is a highly simplified version of a helm chart and this walk through will not cover all of the possible files that can be included in a chart. If you run helm create NAME [flags] to create a new chart, you will see a lot more files than what is listed here.

  

$ tree helm-chart
helm-chart
├── Chart.yaml
├── templates
│   ├── pod.yaml
└── values.yaml

  

Lets dive in to each of these files and get a better look at what they do. The Chart.yaml file is where we define the metadata associated with the chart. In this walkthrough, we'll only focus on the required fields and what they are for. The apiVersion: v2 field defines what version on the Helm chart api we are using. Confusingly enough, v2 means you will be using the Helm 3 api. Then we have the name: helm-chart field to define the name of the chart. The description: A Helm chart for Kubernetes field is used to describe what the chart does and what it is used for. Finally, the version: 0.1.0 field is used to define the version of the chart. This is useful for tracking changes to the chart and for users to know what version of the chart they are. Interestingly enough, the version of the chart doesn't matter all that much unless you plan on publishing your chart to a registry or sharing it with others. When you team Heml up with things like ArgoCD, you can point ArgoCD directly to your Helm chart and you can use other things to track the version of your chart. That being said, it's a business decision to make on how you want to manage and track version of your chart. It's just good to know that you have other options.

  

---
apiVersion: v2
name: helm-chart
description: A Helm chart for Kubernetes
version: 0.1.0

  

The values.yaml file is where we define the default values for the chart. This file is a great place to look to find out what values can be overridden when you are deploying the chart. Here you can also find information of the expected structure of the values that are expected to be passed in. We are going to build a small chart that will help us deploy a single pod that is customizable for different images and commands to run. Our values.yaml file will only containe a few fields to keep things simple. The image field will define our container image and the command field will define the command that will be run when the container starts.

  

image: "nginx:latest"
command: '["nginx", "-g", "daemon off;"]'

  

Now for the templates directory. This is where all the magic happens. This directory is where we can all of our template files and logic that we will need to generate our k8s manifests. I'm a big fan of naming the files after the things they are going to build. In this case, we are going to build a pod, so we will name our file pod.yaml. This directory can contain things like tmpl files too. Those are great for when you have things that will be used in multiple files and you don't want to repeat yourself. A good example of things to add to a template are things like labels and annotations.

  

---
apiVersion: v1
kind: Pod
metadata:
  name: {{ .Release.Name }}-pod
  labels:
    app: {{ .Release.Name }}
spec:
  containers:
    - name: {{ .Release.Name }}-container
      image: {{ .Values.image }}
      command: {{ .Values.command }}

  

Awesome, now with all that in place we can start to see how Helm can be used to generate k8s manifests. The helm template command is a great way to visualize what Helm will build from our chart. Let's run it against our basic chart to see what will be generated. I'll be inside the heml-chart directory when I run this command, but you can reference it from anywhere on your system. For this run we'll call our release "demo".

  

$ helm template demo .
---
# Source: helm-chart/templates/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo
spec:
  containers:
    - name: demo-container
      image: nginx:latest
      command: ["nginx", "-g", "daemon off;"]

  

The chart renders out as expected. Now we can see where Helm starts to shine. Using the same chart let's build a few diferent pods and see how we can layer or override files to create a few varriations of the same chart. We'll start by creating a new file called hello-world.yaml. This file will only contain the values we need to run a simple hello world container. The override files don't have to be in the same directory as the chart, but they will need to be accissible to the helm command.

  

image: "hello-world:latest"
command: '["echo", "hello world"]'

  
  

helm template demo . -f ../hello-world.yaml
---
# Source: helm-chart/templates/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo
spec:
  containers:
    - name: demo-container
      image: hello-world:latest
      command: ["echo", "hello world"]

  

Using the same command and adding -f with the path to the override file, we are able to see the new values rendered in our output. The image and command fields have been updated to reflect the values in the hello-world.yaml file. Let's take this one step further and see how we can use override files to layer on top of each other. We'll create a new override file that will only change the image field. First, we'll run the single override file to see how the default values will work. Then, we'll run the two override files together to see how they layer on top of each other.

  

image: "alpine:latest"

  
  

$ helm template demo . -f ../alpine.yaml
---
# Source: helm-chart/templates/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo
spec:
  containers:
    - name: demo-container
      image: apline:latest
      command: ["nginx", "-g", "daemon off;"]

  

Since we only set the image field in the alpine.yaml file, the command field defaulted to the value in our charts values.yaml file. Now let's see how the two override files layer on top of each other. We'll run the same command

  

$ helm template demo . -f ../hello-world.yaml -f ../alpine.yaml
---
# Source: helm-chart/templates/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo
spec:
  containers:
    - name: demo-container
      image: apline:latest
      command: ["echo", "hello world"]

  

The two override files layered on top of each other as expected. The image field was updated to the value in the alpine.yaml file and the command field was updated to the value in the hello-world.yaml file. It's important to know that odering of the override files matters. Each file has the ability to override the file defined before it, so keep this in mind when ordering you override files. This is just a small example of what Helm can do. There are a lot of other features that Helm has to offer. I hope this walkthrough has given you a good idea of what Helm can do and how it can be used to generate k8s manifests.

Posts + Projects