Building a Helm Chart for Microservices

·

7 min read

Building a Helm Chart for Microservices

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

image.png

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

image.png

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:

image.png

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.

image.png

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.

image.png

image.png

image.png

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 is Helm.

Validate a Helm chart

helm lint

image.png

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 .

image.png

$ helm ls

image.png

$ k get all

image.png

I recommend using helm upgrade --install, because if the chart already exists, it will only apply the changes. If you use helm install and the chart already exists, returns an error.

Uninstall a Helm chart

$ helm del demo

image.png

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.

Did you find this article valuable?

Support Staz by becoming a sponsor. Any amount is appreciated!