[Kubernetes] Using Helm to manage your Kubernetes application

Theekshana Wijesinghe
7 min readDec 19, 2020

Previously I wrote an introductory article on how to deploy a NodeJS application in a Kubernetes cluster. It was a step by step guide on carrying out the said task in a local setup. It introduced concepts like Deployments, Services, ConfigMaps which are fundamentals of Kubernetes.

In Kubernetes even with a small application, there are quite a number of files and commands we need to use in order to get a deployment done. And when the application size and complexity increase as you can imagine, it will be a tedious task to update and manage various applications running in the cluster.

Helm to the rescue.

Helm

Helm is a package manager for Kubernetes. With the help of Helm, we can easily deploy, update, rollback applications running on a Kubernetes cluster.

Helm helps you manage Kubernetes applications — Helm Charts help you define, install, and upgrade even the most complex Kubernetes application.

When using Helm, one will work with what is known as Helm chart (in the latter parts of this tutorial chart implies Helm chart). A Helm chart in simple terms will contain your application scripts such as Deployment, Service, ConfigMap (Fundamentals consistent of a Kubernetes application) files, and configuration file(s) that customize each deployment. Not clear enough? Let’s dive into an example and see how this works.

First, you need to install Helm, here you can get it for your machine.

Note: At this stage, you need to have minikube and kubectl installed.

Once you have installed Helm , run the below command in your terminal

helm create nginx-test

This will create a folder named nginx-test with the following content

nginx-test/
├── .helmignore
├── Chart.yaml
├── values.yaml
├── charts/
└── templates/
└── tests/

We will discuss the meaning of each file or folder as we go along.
If you navigate to templates folder you will see files such as deployment.yaml , service.yaml , … etc.

Do the following,

cd cd nginx-test/
rm -rf templates/*

We have removed all of the files in the templates folder.

Test App

For this tutorial, We will be working with an Nginx application. Add the following 3 files to the templates folder.

# templates/deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
env:
- name: NGINX_PORT
value: "80"
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: external-config
key: env

Add the deployment.yaml as above, this is a pretty standard deployment .yaml file. Note that there are two environment variables, one defines the port Nginx app opens and the other one reads from a ConfigMap that defines the APP_ENV (ex: development, production, … etc).

# templates/service.yamlapiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000

We have defined an external service so that we can talk to our Nginx app from port 30000 from our localhost.

# templates/configmap.yamlapiVersion: v1
kind: ConfigMap
metadata:
name: external-config
data:
env: "development"

Now if you are in the root of the nginx-test folder run the following command.

helm install nginx-app ../nginx-test/ --dry-run

--dry-run flag will simulate the install and output the YAML documents if all are good else notify you of errors.
You should see 3 documents and this means we can install the app in the cluster without any issues. Run the above command without the --dry-run flag. You should get an output similar to the below.

NAME: nginx-app
LAST DEPLOYED: Fri Dec 18 23:28:00 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

By running the below command you can get a URL that can be accessed via the browser.

minikube service nginx-service --url
ngnix default page

You should see the above default page when you access the URL. Up to this point, we have not done anything fancy, and just by using kubectl also, we can achieve this. It’s time to see Helm in action.

Let’s change the 3 files we added as follows.

# templates/deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.appName }}-deployment
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Values.appName }}
template:
metadata:
labels:
app: {{ .Values.appName }}
spec:
containers:
- name: {{ .Values.image.name }}
image: {{ .Values.image.name }}:{{ .Values.image.version }}
ports:
- containerPort: {{ .Values.image.port }}
env:
- name: NGINX_PORT
value: {{ .Values.image.port | quote }}
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: external-config
key: appEnv
# templates/service.yamlapiVersion: v1
kind: Service
metadata:
name: {{ .Values.appName }}-service
spec:
selector:
app: {{ .Values.appName }}
type: LoadBalancer
ports:
- protocol: TCP
port: {{ .Values.image.port }}
targetPort: {{ .Values.image.port }}
nodePort: {{ .Values.image.exposedPort }}
# templates/configmap.yamlapiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.appName }}-external-config
data:
appEnv: {{ .Values.data.appEnv | quote }}

If you look carefully, you can see that all the configs such as image, ports, replicas have been replaced with something like {{ .Values.image }} . In simple terms, we have parameterized template files.

{{ … }} is know as a template directive

A template directive is a way to inject values into a template (any file inside the template folder).

This is a blueprint for any Nginx app we want to deploy in a Kubernetes cluster (Of course our app does not do anything fancy, but you get the idea).

Now we have a question, how Helm template engine knows which value to inject to the appropriate place? The answer to that is the values.yaml file in the nginx-test folder. Let’s look at it now.

# values.yaml appName: nginx
replicas: 2
image:
name: nginx
version: 1.19
port: 80
exposedPort: 31000
data:
appEnv: "development"

When the Helm template engine sees something like {{ .Values.image.name }} it will look in the values.yaml file for a image.name which is nginx in our example. .Values means find for Values object in the root/global scope (.).

In Helm, there are a few built-in objects which you can read here.

Note here instead of Nginx latest we are specifying a tag for Docker image (nginx:1.19). Now to apply our latest changes, let’s upgrade the cluster.

helm upgrade nginx-app ../nginx-test/####
Release "nginx-app" has been upgraded. Happy Helming!
NAME: nginx-app
LAST DEPLOYED: Sat Dec 19 09:02:36 2020
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

It is always a good idea to first try to upgrade with the flag --dry-run to check if all the specified changes have been applied.

Let’s note down a few useful commands in Helm.

helm ls
# list down release (you can specify the namespace if any)
helm history nginx-app
# list the revisions for a release. (We should have 2)

In the template files you can see some template directives are a bit different ex:{{ .Values.image.port | quote }} . What we are telling here is, take the image.port value in the values.yaml file and apply quote function. Here the pipe | operator has been used. Find more on functions and pipelines here.

Note: There is a big list of template functions at your disposal for various requirement: https://helm.sh/docs/chart_template_guide/function_list/

To make things interesting let’s change the image.exposedPort in the values.yaml to 31000 and upgrade the application. Obviously, when you try to access the application via port 30000 it will give This site can’t be reached error.
Now let’s assume this was a mistake and we want to revert back to the previous version. This is easy with Helm

helm rollback nginx-app 2
# We rollback to revision 2 of our application

If you check for the history of our app.

helm history nginx-app

In the 4th revision, we see that it has been rollback to revision 2. Cool isn’t it?

Now we will move a step forward. Go ahead and remove the data fied in values.yaml and create two other files dev.yaml and prod.yaml with the following content.

# values.yamlappName: nginx
replicas: 2
image:
name: nginx
version: 1.19
port: 80
exposedPort: 30000
# dev.yamldata:
appEnv: "development"
# prod.yamldata:
appEnv: "production"

You guessed it right, by using helm we can separate environment-specific configuration and common configuration.

Now we can deploy our application with development configuration as follows.

helm install nginx-app -f dev.yaml ../nginx-test/

by specifying the -f flag we can specify YAML file(s) with values. Helm will automatically merge the specified file(s) with values.yaml file and feed into the template files. Pretty powerful right!

Note: If you try to run the above command as it, it will fail because we already have nginx-app defined.

Please note that this tutorial was carried out in the default namespace of Kubernetes, but it is always best to specify a namespace for your application to avoid conflicts.

Now with the basic introduction of how to use Helm, we are now ready to define the folder structure of the Helm chart.

nginx-test/
├── .helmignore
├── Chart.yaml
├── values.yaml
├── charts/
└── templates/

Chart.yaml contains the metadata for the deployed application like name, version, … etc. These values can be accessed through the Object .Chart .

values.yaml as we discussed contains values for the template directives.

templates contain the Kubernetes template files like Service, ConfigMap, and so on.

charts this folder contains charts on which our main charts dependents. (We will not be looking into this in this tutorial)

As we saw in this example, our chart acted as a blueprint for an Nginx app even though it did nothing very much useful. Since this templating is a powerful construct, people have defined charts for various applications and can be found via https://artifacthub.io/. Even though these are sophisticated applications, the idea behind them remains the same.

Happy Helming!

--

--