# Building a Helm Chart for Microservices

### Introduction

[Helm](https://helm.sh/) 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](https://artifacthub.io/).

You can review the [documentation](https://helm.sh/docs/topics/charts/) 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](https://multipass.run/) to generate an ubuntu instance and [microk8s](https://microk8s.io/) to deploy kubernetes. Both are very good tools developed by [Canonical](https://canonical.com/).

Install [multipass](https://multipass.run/install).

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:

```plaintext
$ 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:

```plaintext
$ alias k="microk8s kubectl"
$ k cluster-info
```

We already have our lightweight Kubernetes cluster using microk8s.

#### **Install and setup Helm**

```plaintext
$ 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.

```plaintext
$ helm create microservice-template
$ snap install tree
$ tree microservice-template
```

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1656222437471/PkQS2ZBEM.png align="left")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1656222900127/nQavj-KeG.png align="left")

`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 }}`

%[https://gist.github.com/stazdx/1cc56addc9eadccc72cbf5127f758a0c] 

`values.yaml` is the file containing all customizable input parameters for your chart. You can call their variables like this `{{ .Values.my_variable }}`

%[https://gist.github.com/stazdx/778d1f3b3479420279426fff8bd5b31a] 

`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.

%[https://gist.github.com/stazdx/171882dc0b14e26aa04abfc08d13e4c2] 

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1656224548066/EnRvqxUfV.png align="left")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1656224837371/a79VDK0Ic.png align="left")

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](https://cdn.hashnode.com/res/hashnode/image/upload/v1656224788566/j1KqE5Izf.png align="left")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1656225516547/PZi1OyggT.png align="left")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1656225567173/y1hz6l4XG.png align="left")

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 .` -&gt; . `demo` is the `.Release.Name` variable and `.Release.Service` is `Helm`.

#### \*\* Validate a Helm chart\*\*

`helm lint`

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1656227516699/nNipu7Kdf.png align="left")

#### **Create Kubernetes manifests using Helm**

`helm template .`

Output:

```plaintext
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:

```plaintext
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](https://cdn.hashnode.com/res/hashnode/image/upload/v1656228418550/9TNz6vXwt.png align="left")

`$ helm ls`

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1656229104030/gb6HWC5Nj.png align="left")

`$ k get all`

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1656229141893/h5cjE5d29.png align="left")

> 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1656229486220/38FAE8nat.png align="left")

#### **Github example**

Here, a simple example of a springboot chart:

%[https://github.com/stazdx/springboot-helm-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.
