Table of contents
Introduction
Helm is a package manager for Kubernetes that helps us to deploy custom applications and generate parameterized templates. This is very useful when it comes to deploying microservices on Kubernetes.
Helm was accepted into CNCF on January 18, 2016 and is at the Graduate project maturity level.
The package format used by Helm is called a chart. Helm has a command-line tool that makes it easy to create charts for us to customize and then deploy. Also has a registry where the community and companies share their charts. If you are looking for one, it probably already exists, you can check it out here.
You can review the documentation for more information on charts and best practices in its implementation.
Demo
This demo applies to any programming language and application/microservice you need to deploy on Kubernetes. Remember that it is a template and it is customizable.
Install and run an Ubuntu instance
I will use multipass to generate an ubuntu instance and microk8s to deploy kubernetes. Both are very good tools developed by Canonical.
Install multipass.
For this demo, create a very light ubuntu instance with 2GB of RAM and 5 GB of Disk space:
$ multipass launch --name demo --mem 2G --disk 5G
Install and setup microk8s
After that, install microk8s inside the ubuntu instance:
$ multipass shell demo
$ sudo su
$ snap install microk8s --classic --channel=1.18/stable
$ iptables -P FORWARD ACCEPT
The iptables command is necessary to permit traffic between the VM and host.
Create an alias for kubectl command:
$ alias k="microk8s kubectl"
$ k cluster-info
We already have our lightweight Kubernetes cluster using microk8s.
Install and setup Helm
$ snap install helm --classic
$ helm
$ microk8s config > ~/.kube/config
It is important to copy the microk8s configuration to the
~/.kube/config
directory for Helm to work.
Create a Helm Chart
Then, we can create a Helm chart for our microservice.
$ helm create microservice-template
$ snap install tree
$ tree microservice-template
This is the file structure of a chart. In the following, we will explain the most important ones:
The most important files of a chart
Chart.yaml
is required, and in this gist repository you have all the allowed fields. You can call their variables like this {{ .Chart.my_variable }}
values.yaml
is the file containing all customizable input parameters for your chart. You can call their variables like this {{ .Values.my_variable }}
templates/_helpers.tpl
is the file where you can transform your input values and even define global variables for your chart. These cannot be entered as input parameters.
Then there are the kubernetes manifests *.yaml
which are the ones that will be created in your kubernetes cluster. These receive the input variables and the ones you have defined in Chart.yaml
, values.yaml
and _helpers.tpl
files. Here is an example of what you can call them.
Using conditionals and calling variables
For example in the templates/hpa.yaml
, this is a Horizontal Pods Autoscaler manifest that uses variables from the values.yaml file
, _helpers.tpl
and even uses if
conditional to create certain properties:
The conditional says that if the value of the .Values.autoscaling.enabled
variable is true
the whole manifest will be created, but if it is false
, it will not create it.
In this case, in values.yaml
, the .Values.autoscaling.enabled
parameter is false
, then it will not create it.
Also, in the templates/hpa.yaml
file uses microservice-template.fullname
and microservice-template.labels
, these refers to the variable that was defined in the _helpers.tpl
file.
Also uses .Chart.name
, .Chart.AppVersion
that are found within Chart.yaml
file.
.Release.Name
refers to the name you assign when you install the chart, for example:helm install demo .
-> .demo
is the.Release.Name
variable and.Release.Service
isHelm
.
Validate a Helm chart
helm lint
Create Kubernetes manifests using Helm
helm template .
Output:
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /root/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /root/.kube/config
---
# Source: microservice-template/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: RELEASE-NAME-microservice-template
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
---
# Source: microservice-template/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: RELEASE-NAME-microservice-template
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
---
# Source: microservice-template/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: RELEASE-NAME-microservice-template
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
template:
metadata:
labels:
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
spec:
serviceAccountName: RELEASE-NAME-microservice-template
securityContext:
{}
containers:
- name: microservice-template
securityContext:
{}
image: "nginx:1.16.0"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{}
---
# Source: microservice-template/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-microservice-template-test-connection"
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['RELEASE-NAME-microservice-template:80']
restartPolicy: Never
Debug and dry-run a Helm Chart
This command simulates a Helm Chart install and generates k8s manifests:
helm upgrade --install demo . --debug --dry-run
Output:
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /root/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /root/.kube/config
history.go:56: [debug] getting history for release demo
upgrade.go:139: [debug] preparing upgrade for demo
upgrade.go:147: [debug] performing update for demo
upgrade.go:310: [debug] dry run for demo
Release "demo" has been upgraded. Happy Helming!
NAME: demo
LAST DEPLOYED: Sun Jun 26 02:15:07 2022
NAMESPACE: default
STATUS: pending-upgrade
REVISION: 2
USER-SUPPLIED VALUES:
{}
COMPUTED VALUES:
affinity: {}
autoscaling:
enabled: false
maxReplicas: 100
minReplicas: 1
targetCPUUtilizationPercentage: 80
fullnameOverride: ""
image:
pullPolicy: IfNotPresent
repository: nginx
tag: ""
imagePullSecrets: []
ingress:
annotations: {}
className: ""
enabled: false
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
nameOverride: ""
nodeSelector: {}
podAnnotations: {}
podSecurityContext: {}
replicaCount: 1
resources: {}
securityContext: {}
service:
port: 80
type: ClusterIP
serviceAccount:
annotations: {}
create: true
name: ""
tolerations: []
HOOKS:
---
# Source: microservice-template/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "demo-microservice-template-test-connection"
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['demo-microservice-template:80']
restartPolicy: Never
MANIFEST:
---
# Source: microservice-template/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: demo-microservice-template
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
---
# Source: microservice-template/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-microservice-template
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
---
# Source: microservice-template/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-microservice-template
labels:
helm.sh/chart: microservice-template-0.1.0
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
template:
metadata:
labels:
app.kubernetes.io/name: microservice-template
app.kubernetes.io/instance: demo
spec:
serviceAccountName: demo-microservice-template
securityContext:
{}
containers:
- name: microservice-template
securityContext:
{}
image: "nginx:1.16.0"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{}
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=microservice-template,app.kubernetes.io/instance=demo" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
Install a Helm chart
$ helm install demo .
$ helm ls
$ k get all
I recommend using
helm upgrade --install
, because if the chart already exists, it will only apply the changes. If you usehelm install
and the chart already exists, returns an error.
Uninstall a Helm chart
$ helm del demo
Github example
Here, a simple example of a springboot chart:
Conclusion
You can use Helm if you have a lot of microservices and you want a template to easily deploy them in a parameterized way. Helm is not only for microservices, actually you can use it for everything.
I hope you find it useful and can apply it.
Later on we will see how powerful your deployments can be using GitOps, Kustomize and Helm.