[Kubernetes] Using Helm to manage your Kubernetes application

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 and installed.

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

helm create nginx-test

This will create a folder named 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 folder you will see files such as , , … etc.

Do the following,

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

We have removed all of the files in the folder.

Test App

For this tutorial, We will be working with an Nginx application. Add the following 3 files to the 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 as above, this is a pretty standard deployment 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 folder run the following command.

helm install nginx-app ../nginx-test/ --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 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
Image for post
Image for post
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 also, we can achieve this. It’s time to see 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 . 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 file in the 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 it will look in the file for a which is in our example. means find for 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 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: . What we are telling here is, take the image.port value in the values.yaml file and apply 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 error.
Now let’s assume this was a mistake and we want to revert back to the previous version. This is easy with

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

If you check for the history of our app.

Image for post
Image for post
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 fied in and create two other files and 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 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 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/

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

as we discussed contains values for the template directives.

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

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!

Software Engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store