This will show you how to create your own Helm Chart for your Node.js Application and how to tweak it to best fit your applications needs.
In this self-paced tutorial you will:
- Create a Helm Chart
- Add a dependency chart
- Create liveness probes
The application you will use is the one created from - https://github.com/nodeshift/mern-workshop
Before getting started, make sure you have the following prerequisites installed on your system.
- Install Node.js 16 (or use nvm for linux, mac or nvm-windows for windows)
- Podman v4 (and above)
- Podman Desktop
- On Mac: Podman Desktop
- On Windows: Podman Desktop
- On Linux: Podman Desktop
- Kubernetes
- Helm v3 - Installation
- Note: This workshop tested with Helm v3.7
Nothing to do, no Podman machine is required on Linux
After installing podman, open a terminal and run the below commands to initialize and run the podman machine:
NOTE: *On Apple M1 Pro chip, the system version has to be 12.4 and above.
podman machine init --cpus 2 --memory 8096 --disk-size 20
podman machine start
podman system connection default podman-machine-default-root
-
Launch Podman Desktop and on the home tab click on install podman. In case of any missing parts for podman installation (e.x. wsl, hyper-v, etc.) follow the instructions indicated by Podman Desktop on the home page. In that case you might need to reboot your system several times.
-
After installing podman, set WSL2 as your default WSL by entering below command in PowerShell (with administration priviledges).
wsl --set-default-version 2 -
On Podman Desktop Home tab -> click on initialize Podman -> wait till the initialization is finished
-
On Podman Desktop Home tab -> click on Run Podman to run podman.
- Downlodad podman from https://github.com/containers/podman/releases the Windows installer file is named podman-v.#.#.#.msi
- Run the MSI file
- Launch as Administrator a new Command Prompt
- On the Command Prompt run:
podman machine init podman machine set --rootful podman machine start - Launch Podman Desktop to see and manage your containers, images, etc.
-
Install Minikube
Download binary file (click to expand)
x86-64
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64ARM64
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64Add minikube binary file to your
PATH system variablechmod +x minikube-darwin-* mv minkube-linux-* /usr/local/bin/minikube -
start minikube
minikube start --driver=podman --container-runtime=cri-o
-
Download minikube
https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe -
Rename
minikube-windows-amd64.exetominikube.exe -
Move minikube under
C:\Program Files\minikubedirectory -
Add
minikube.exebinary to yourPATH system variable- Right-click on the Start Button -> Select System from the context menu -> click on Advanced system settings
- Go to the Advanced tab -> click on Environment Variables -> click the variable called Path -> Edit
- Click New -> Enter the path to the folder containing the binary e.x.
C:\Program Files\minikube-> click OK to save the changes to your variables - Start Podman Desktop and click on run podman
-
Start minikube:
- For windows Start minikube by opening Powershell or Command Prompt as administrator and enter below command.
minikube start- For windows Home Start minikube by opening Powershell or Command Prompt as administrator and enter below command.
minikube start --driver=podman --container-runtime=containerd
-
Install Minikube
Download binary file (click to expand)
x86-64
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64ARM64
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64ARMv7
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-armppc64
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-ppc64leS390x
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-s390xAdd minikube binary file to your
PATH system variablechmod +x minikube-linux-* mv minkube-linux-* /usr/local/bin/minikube -
Change minikube for starting podman rootless https://minikube.sigs.k8s.io/docs/drivers/podman/#rootless-podman
minikube config set rootless true -
start minikube
minikube start --driver=podman --container-runtime=containerd -
Possible additional steps needed
- delegation also needed on Unbuntu 2022 - https://rootlesscontaine.rs/getting-started/common/cgroup2/
Helm is a package manager for Kubernetes. By installing a Helm "chart" into your Kubernetes cluster you can quickly run all kinds of different applications. You can install Helm by downloading the binary file and adding it to your PATH:
-
Download the binary file from the section Installation and Upgrading for Operating system accordingly.
-
Extract it:
- On Linux:
tar -zxvf helm-v3.7.2-* - On Windows: Right Click on
helm-v3.7.2-windows-amd64zipped file -> Extract All -> Extract - On Mac:
tar -zxvf helm-v3.7.2-*
- On Linux:
-
Add helm binary file to your
PATH system variableOn Linux and Mac (sudo required for cp step on linux):
cp `./<your-linux-distro>/helm` /usr/local/bin/helm rm -rf ./<your-linux-distro>If running on Mac results in a pop up indicating that the app could not be verified, you will need to go to Apple menu > System Preferences, click Security & Privacy and allow helm to run.
On Windows:
- Move helm binary file to
C:\Program Files\helm - Right-click on the Start Button -> Select System from the context menu -> click on Advanced system settings
- Go to the Advanced tab -> click on Environment Variables -> click the variable called Path -> Edit
- Click New -> Enter the path to the folder containing the binary e.x.
C:\Program Files\helm-> click OK to save the changes to your variables
- Move helm binary file to
Make sure you clone down the application repo and you are in the correct directory.
$ git clone https://github.com/nodeshift/mern-workshop.git
$ cd mern-workshopIn your terminal inside the folder holding your application run:
$ mkdir chart
$ cd chart
$ helm create myappThis will create the following file structure:
myapp/
├── .helmignore # Contains patterns of which files to ignore when packaging your Helm Chart.
├── Chart.yaml # Information about your chart
├── values.yaml # The default values for your templates
├── charts/ # Charts that this chart depends on
└── templates/ # The template files
└── tests/ # The test files
These files will form the basis of your Helm Chart, lets explore the pre-created files and make some necessary changes.
This file works the same as any .ignore file, you just fill in the patterns you don't want to be packaged up into the helm chart. For example, if you have some secrets saved as a JSON file that you do not want to be inside the helm chart. For our application we do not have any files we need to protect so let's move on to the next step.
This file contains the base information about your Helm Chart, your premade one should look similar to this (with extra comments):
apiVersion: v2
name: myapp
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: 1.0.0apiVersion: v2 signals that this chart is designed for Helm3 support only.
version is the charts version, increment this under semver every time you update the chart
appVersion is the version of the app you are deploying, this is to be increased every time you increase the version of your app but does not impact the charts version
The rest of the fields are self explanatory but lets add some more information to describe our chart. We are going to set a Kubernetes minimum version, add some descriptive keywords and add our name as Maintainers. So go ahead and add the following to your Chart.yaml whilst subsituting your name and email in:
kubeVersion: ">= 1.21.0-0"
keywords:
- nodejs
- express
- mern
maintainers:
- name: Firstname Lastname
email: FirstnameLastname@company.comThe final key thing we are going to add is a dependency, our application needs mongoDB to run so we are going to call an existing mongo chart to install mongo as we install our chart. Firstly we need to add to our Chart.yaml:
dependencies:
- name: mongodb
version: ">= 10.26.3"
repository: https://charts.bitnami.com/bitnamiThen run the following command in the terminal to download the chart:
cd myapp
helm dependency update
cd ..Inside the templates folder you will find that Helm has created some files for us already:
$ ls myapp/templates
NOTES.txt
_helpers.tpl
deployment.yaml
hpa.yaml
ingress.yaml
service.yaml
serviceaccount.yaml
testsThese files represent the kubernetes objects that our Helm Chart will deploy - you can set up the deployment and service, and you can also create a serviceaccount for your app to use. The NOTES.txt file is just the notes that will be displayed to the user when they deploy your Helm Chart, you can use this to provide further instruction to them to get your application working. _helpers.tpl is where template helpers that you can re-use throughout the chart go. However for this tutorial we are going to use our own files so you can go ahead and remove the generated files:
$ rm -rf chart/myapp/templates/*Now let's move on to making our own template files!
The first template we will create will deploy the service for our frontend. Let's create a file called chart/myapp/templates/frontend-service.yaml and paste in the following:
apiVersion: v1
kind: Service
metadata:
annotations:
prometheus.io/scrape: 'true'
name: "frontend-service"
spec:
ports:
- name: http
port: {{ .Values.frontend.service.servicePort }}
nodePort: 30444
type: {{ .Values.frontend.service.type }}
selector:
app: "frontend-selector"This will create the service that serves our frontend and allows for it to be connected to the outside world. nodePort is the port we want our service to be accessible through so localhost:30444 will take us to our frontend once deployed
The next template we create will be the deployment for the frontend. Create a file called chart/myapp/templates/frontend-deployment.yaml and paste in the following:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
labels:
chart: frontend
spec:
selector:
matchLabels:
app: "frontend-selector"
template:
metadata:
labels:
app: "frontend-selector"
spec:
containers:
- name: frontend
image: "{{ .Values.frontend.image.repository }}/{{ .Values.frontend.image.tag }}"
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
livenessProbe:
httpGet:
path: /
port: {{ .Values.frontend.service.servicePort }}
initialDelaySeconds: {{ .Values.frontend.livenessProbe.initialDelaySeconds}}
periodSeconds: {{ .Values.frontend.livenessProbe.periodSeconds}}
ports:
- containerPort: {{ .Values.frontend.service.servicePort}}What this creates is our frontend deployment, inside that is the pod that is running our frontend image plus its replicaset.
image is the image we want the container to deploy using variables pulled from our values.yaml file.
We also add the frontend-selector label to our pod which allows for our frontend service to connect with it.
Our third file will spawn the service for the backend part of our application. Create a file called chart/myapp/templates/backend-service.yaml and paste in the following:
apiVersion: v1
kind: Service
metadata:
annotations:
prometheus.io/scrape: 'true'
name: "backend-service"
spec:
ports:
- name: http
port: {{ .Values.backend.service.servicePort }}
nodePort: 30555
type: NodePort
selector:
app: "backend-selector"This file does the same thing as frontend-service.yaml but for the backend and under a different port.
Our final template is the deployment for the backend. Create a file called chart/myapp/templates/backend-deployment.yaml and paste in the following:
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
labels:
chart: backend
spec:
selector:
matchLabels:
app: "backend-selector"
template:
metadata:
labels:
app: "backend-selector"
spec:
containers:
- name: backend
image: "{{ .Values.backend.image.repository }}/{{ .Values.backend.image.tag }}"
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
livenessProbe:
httpGet:
path: /health
port: {{ .Values.backend.service.servicePort }}
initialDelaySeconds: {{ .Values.backend.livenessProbe.initialDelaySeconds}}
periodSeconds: {{ .Values.backend.livenessProbe.periodSeconds}}
ports:
- containerPort: {{ .Values.backend.service.servicePort}}
env:
- name: PORT
value : "{{ .Values.backend.service.servicePort }}"
- name: APPLICATION_NAME
value: "{{ .Release.Name }}"
- name: MONGO_URL
value: {{ .Values.backend.services.mongo.url }}
- name: MONGO_DB_NAME
value: {{ .Values.backend.services.mongo.name }}Similar to our frontend deployment file, this file creates our backend deployment, with a key difference being the mongoDB information that is passed through to allow for communication with the mongo instance.
For the chart/myapp/values.yaml file we are going to split it into 3 sections, have a read of each section and then add them all to your values.yaml file
# Frontend
frontend:
replicaCount: 1
revisionHistoryLimit: 1
image:
repository: frontend
tag: frontend:v1.0.0
pullPolicy: IfNotPresent
resources:
requests:
cpu: 200m
memory: 300Mi
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 10
service:
name: frontend
type: NodePort
servicePort: 80 # the port where nginx serves its trafficThese values are for the frontend section. Here we pass through the image name, tag and the values for the frontend service.
# backend
backend:
replicaCount: 1
revisionHistoryLimit: 1
image:
repository: backend
tag: backend:v1.0.0
pullPolicy: IfNotPresent
resources:
requests:
cpu: 200m
memory: 300Mi
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 10
service:
name: backend
type: NodePort
servicePort: 30555
services:
mongo:
url: myapp-mongodb
name: todos
env: productionThese values are for the backend section. Here we pass through the image, tag, service information, and some mongoDB information to locate the instance.
# mongo
mongodb:
auth:
enabled: false
replicaSet:
enabled: true
replicas:
secondary: 3
service:
type: LoadBalancerFinally these values are passed through to the mongoDB chart we downloaded earlier.
Now that you have built a Helm chart for your application, the process for deploying your application has been greatly simplified.
Deploy your Express.js application into Kubernetes using the following steps:
- Create a local image registry
You will need to push the image into the kubernetes container registry so that minikube can access it.
First we enable the image registry addon for minikube:
minikube addons enable registry
console output:
$ minikube addons enable registry
╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ Registry addon with podman driver uses port 42795 please use that instead of default port 5000 │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
📘 For more information see: https://minikube.sigs.k8s.io/docs/drivers/podman
▪ Using image registry:2.7.1
▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
🔎 Verifying registry addon...
🌟 The 'registry' addon is enabledNote: As the message indicates, be sure you use the correct port instead of 5000. If you don't see the warning then just use 5000 for the port in the instructions below.
On Linux and macOS export a variable with the registry with:
export MINIKUBE_REGISTRY=$(minikube ip):<port>replacing with the port listed when you ran minikube addons enable registry.
On Windows export a variable with the registry with by first running
minikube ip
to get the ip of the registry and then exporting
set MINIKUBE_REGISTRY=<ip from minikube ip command above>:<port>
We can now build the image directly using minikube image build:
On Linux and macOS:
cd backend
minikube image build -t $MINIKUBE_REGISTRY/backend:v1.0.0 --file Dockerfile .
cd ../frontend
minikube image build -t $MINIKUBE_REGISTRY/frontend:v1.0.0 --file Dockerfile .On Windows:
cd ../backend
minikube image build -t %MINIKUBE_REGISTRY%/backend:v1.0.0 --file Dockerfile .
cd ../frontend
minikube image build -t %MINIKUBE_REGISTRY%/frontend:v1.0.0 --file Dockerfile .And we can list the images in minikube:
minikube image lsConsole output
<minikube-ip>:<minikube-registry-port>/frontend:v1.0.0
<minikube-ip>:<minikube-registry-port>/backend:v1.0.0Next, we push the image into the registry using:
On Linux and macOS:
minikube image push $MINIKUBE_REGISTRY/backend
minikube image push $MINIKUBE_REGISTRY/frontendOn Windows:
minikube image push %MINIKUBE_REGISTRY%/backend
minikube image push %MINIKUBE_REGISTRY%/frontendOnce the images are built you can now deploy your helm chart
Validate you are in the mern-workshop directory
On Linux and macOS:
$ helm install myapp --set backend.image.repository=$MINIKUBE_REGISTRY --set frontend.image.repository=$MINIKUBE_REGISTRY chart/myappOn Windows:
$ helm install myapp --set backend.image.repository=%MINIKUBE_REGISTRY% --set frontend.image.repository=%MINIKUBE_REGISTRY% chart/myappCheck your pods are running by running:
$ kubectl get podsYou should get a similar output to:
NAME READY STATUS RESTARTS AGE
backend-deployment-5d6bb8c5f8-xm4bv 1/1 Running 0 67s
frontend-deployment-6c7779ff9d-xjdrv 1/1 Running 0 67s
myapp-mongodb-64df664c5b-st8fb 1/1 Running 0 66sWait for some time until mongo status is running
Also, by viewing the logs of the backend service with below command
kubectl logs backend-deployment-xxxxxxxx-xxxxx -fyou should get below message
Connection is established with mongodb, details: mongodb://myapp-mongodb:27017
You can then run the following two commands in order to forward 30555 port to your backend pod:
On Linux and macOS:
export BACKEND_POD_NAME=$(minikube kubectl -- get pods --namespace default -o jsonpath="{.items[0].metadata.name}")
minikube kubectl -- --namespace default port-forward $BACKEND_POD_NAME 30555:30555On Windows Command Prompt:
for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace default -o jsonpath={.items[0].metadata.name}"') do set BACKEND_POD_NAME=%i
minikube kubectl -- --namespace default port-forward %BACKEND_POD_NAME% 30555:30555
And by running the following two commands you are able to port forward your app on port 30444 on localhost:
On Linux and macOS:
export FRONTEND_POD_NAME=$(minikube kubectl -- get pods --namespace default -o jsonpath="{.items[1].metadata.name}")
minikube kubectl -- --namespace default port-forward $FRONTEND_POD_NAME 30444:80On Windows Command Prompt:
for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace default -o jsonpath={.items[1].metadata.name}"') do set FRONTEND_POD_NAME=%i
minikube kubectl -- --namespace default port-forward %FRONTEND_POD_NAME% 30444:80
Now you can access your application at http://localhost:30444/
You have now created a Helm Chart which deploys a frontend and a backend whilst also calling and installing a dependency chart from the internet.
By visiting http://localhost:30444/ you should be able to see the UI as shown on the image below
Lets add an item by filling the textbox and clicking on the Add new toDo button
By clicking the Add new ToDo button, the item should be added on the list as shown on image below.
You should also be able to see below message on the backend service
Creating Todo for NodeConfEU Clean the bicycle
by viewing the logs with below command
kubectl logs backend-deployment-xxxxxxxx-xxxxx -fClick the x button next to the todo item and it should be removed
Once you are finished you can uninstall the chart by running:
$ helm uninstall myapp

