Skip to main content

Kubernetes Helm Charts: Create a Custom Helm Chart

1447 words·
Kubernetes Kubectl Helm
Kubernetes-Components - This article is part of a series.
Part 20: This Article

Overview
#

  • A Helm chart is a combination of Kubernetes YAML manifest templates and helm-specific files

  • A values file is used to modify the deployment parameters

  • Helm takes care of applying the values to the templates

Helm Chart Overview:

├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml
  • Chart.yaml Contains information about the Helm chart, like version, name & description.

  • values.yaml Defines the values for the YAML templates, like image name, replica count, etc.

  • charts (directory) Used to add another charts structure inside this directory if the main charts have some dependency on others. By default this directory is empty.

  • templates This directory contains all the Kubernetes manifests used for the application.

  • templates/NOTES.txt Text file that gets printed out after the chart is successfully deployed.

  • templates/tests/ Used for tests for the chart, that can be defined to validate that the chart works as expected when it is installed.


Create a Helm Chart
#

Create an example Helm chart for an Nginx deployment.


Create Helm Chart Blueprint
#

Create a new Helm chart with the default files and folders:

# Create a Helm chart blueprint with the name "nginx-helm-chart"
helm create nginx-helm-chart

# Shell output:
Creating nginx-helm-chart

# CD into the chart directory
cd nginx-helm-chart

The file and folder structure looks like this:

├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

Adopt Helm Chart
#

Chart.yaml
#

Replace the default contents of Chart.yaml:

# Open Chart.yaml
vi Chart.yaml
apiVersion: v2
name: nginx-helm-chart
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"
maintainers:
- email: juergen@jklug.work
  name: juergen
#icon: https://example.com/path/to/icon.png
  • type: application The chart type can be either application or library. Application charts are deployed on Kubernetes. Library charts are re-usable charts that can be used with other charts.

  • version: Defines the chart version.

  • appVersion: Defines the version number of the application, in this example Nginx.

  • maintainers: Information about the chart owner.


templates
#

# Remove all default files from the template directory
rm -rf templates/*

deployment.yaml
#

# Create "deployment.yaml" manifest
vi templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-nginx
  labels:
    app: nginx
    app.kubernetes.io/instance: {{ .Release.Name }}-nginx
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: nginx
      app.kubernetes.io/instance: {{ .Release.Name }}-nginx
  template:
    metadata:
      labels:
        app: nginx
        app.kubernetes.io/instance: {{ .Release.Name }}-nginx
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          volumeMounts:
            - name: nginx-index-file
              mountPath: /usr/share/nginx/html/
      volumes:
        - name: nginx-index-file
          configMap:
            name: {{ .Release.Name }}-index-html-configmap

service.yaml
#

# Create "service.yaml" manifest
vi templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-service
spec:
  selector:
    app.kubernetes.io/instance: {{ .Release.Name }}-nginx
  type: {{ .Values.service.type }}
  ports:
    - protocol: {{ .Values.service.protocol | default "TCP" }}
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}

configmap.yaml
#

# Create "configmap.yaml" manifest
vi templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-index-html-configmap
data:
  index.html: |
    <html>
    <h1>Helm Chart Example</h1>
    </br>
    <h1>Deployed in {{ .Values.env.name }} Environment</h1>
    </html    

values.yaml
#

# Create "values.yaml" manifest
vi values.yaml
replicaCount: 2

image:
  repository: nginx
  tag: "1.26.0"
  pullPolicy: IfNotPresent

service:
  name: nginx-service
  type: ClusterIP
  port: 8080
  targetPort: 80

env:
  name: dev

Files And Folder Structure
#

The file and folder structure should now look like this:

├── charts
├── Chart.yaml
├── templates
│   ├── configmap.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

Validate the Helm Chart
#

Validate the Helm chart syntax and the YML indentations are correct:

# Verify the chart is valid
helm lint .

# Shell output:
==> Linting .
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed

Validate the template values are getting substituted correctly via the values fined in the values.yaml manifest:

# Render the templated YML manifests
helm template .

Shell output:

---
# Source: nginx-helm-chart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: release-name-index-html-configmap
data:
  index.html: |
    <html>
    <h1>Helm Chart Example</h1>
    </br>
    <h1>Deployed in dev Environment</h1>
    </html
---
# Source: nginx-helm-chart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: release-name-service
spec:
  selector:
    app.kubernetes.io/instance: release-name-nginx
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
---
# Source: nginx-helm-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-name-nginx
  labels:
    app: nginx
    app.kubernetes.io/instance: release-name-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
      app.kubernetes.io/instance: release-name-nginx
  template:
    metadata:
      labels:
        app: nginx
        app.kubernetes.io/instance: release-name-nginx
    spec:
      containers:
        - name: nginx-helm-chart
          image: "nginx:1.26.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          volumeMounts:
            - name: nginx-index-file
              mountPath: /usr/share/nginx/html/
      volumes:
        - name: nginx-index-file
          configMap:
            name: release-name-index-html-configmap

Install the Helm Chart
#

Dry Run
#

# CD out of the Helm chart directory
cd ..

# Start a dry run for the Helm installation of the nginx 
helm install --dry-run development nginx-helm-chart

Shell output:

NAME: development
LAST DEPLOYED: Wed Aug 28 12:44:42 2024
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: nginx-helm-chart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: development-index-html-configmap
data:
  index.html: |
    <html>
    <h1>Helm Chart Example</h1>
    </br>
    <h1>Deployed in dev Environment</h1>
    </html
---
# Source: nginx-helm-chart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: development-service
spec:
  selector:
    app.kubernetes.io/instance: development-nginx
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
---
# Source: nginx-helm-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: development-nginx
  labels:
    app: nginx
    app.kubernetes.io/instance: development-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
      app.kubernetes.io/instance: development-nginx
  template:
    metadata:
      labels:
        app: nginx
        app.kubernetes.io/instance: development-nginx
    spec:
      containers:
        - name: nginx-helm-chart
          image: "nginx:1.26.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          volumeMounts:
            - name: nginx-index-file
              mountPath: /usr/share/nginx/html/
      volumes:
        - name: nginx-index-file
          configMap:
            name: development-index-html-configmap

Install Helm Chart
#

# Install the Helm chart
helm install development nginx-helm-chart \
  --namespace development \
  --create-namespace

# Shell output:
NAME: development
LAST DEPLOYED: Wed Aug 28 12:45:08 2024
NAMESPACE: development
STATUS: deployed
REVISION: 1
TEST SUITE: None

Verify Installation
#

# List resources in "development" namespace
kubectl get all -n development

# Shell output:
NAME                                     READY   STATUS    RESTARTS   AGE
pod/development-nginx-75c84bb89d-75h99   1/1     Running   0          60s
pod/development-nginx-75c84bb89d-c4kbf   1/1     Running   0          60s

NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/development-service   ClusterIP   10.110.120.89   <none>        8080/TCP   60s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/development-nginx   2/2     2            2           60s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/development-nginx-75c84bb89d   2         2         2       60s
# List ConfigMap
kubectl get configmap -n development

# Shell output:
NAME                               DATA   AGE
development-index-html-configmap   1      65s

Create a portforwarding for the Nginx service and curl the Nginx container:

# Create portforwarding
kubectl port-forward service/development-service 8080:8080 --namespace development


# Curl for the Nginx container (use a new shell)
curl http://localhost:8080

# Shell output:
<html>
<h1>Helm Chart Example</h1>
</br>
<h1>Deployed in dev Environment</h1>
</html

Upgrade Helm Installation
#

Create a custom values:

# Create a custom values manifest
vi custom-values.yaml
replicaCount: 3 # Adopt the pod count

image:
  repository: nginx
  tag: "1.26.0"
  pullPolicy: IfNotPresent

service:
  name: nginx-service
  type: ClusterIP
  port: 8080
  targetPort: 80

env:
  name: dev

Upgrade the Helm installation:

# Upgrade the Helm installation
helm upgrade  development nginx-helm-chart \
  --values custom-values.yaml \
  --namespace development

# Shell output:
Release "development" has been upgraded. Happy Helming!
NAME: development
LAST DEPLOYED: Wed Aug 28 12:56:16 2024
NAMESPACE: development
STATUS: deployed
REVISION: 2
TEST SUITE: None

Note that the REVISION number changes to 2.


Verify the Upgraded Installation
#

Verify the upgraded pod number in the “development” namespace:

# List pods in "development" namespace
kubectl get pods -n development

# Shell output:
NAME                                 READY   STATUS    RESTARTS   AGE
development-nginx-75c84bb89d-52gt4   1/1     Running   0          105s
development-nginx-75c84bb89d-75h99   1/1     Running   0          12m
development-nginx-75c84bb89d-c4kbf   1/1     Running   0          12m

Rollback the Helm Installation
#

# Rollback the Helm chart installation: Define revision number
helm rollback development -n development 1

# Shell output:
Rollback was a success! Happy Helming!

Verify the upgraded pod number in the “development” namespace:

# List pods in "development" namespace
kubectl get pods -n development

# Shell output:
NAME                                 READY   STATUS    RESTARTS   AGE
development-nginx-75c84bb89d-75h99   1/1     Running   0          17m
development-nginx-75c84bb89d-c4kbf   1/1     Running   0          17m

Verify the Rollback
#

# List Helm releases / installations
helm list -n development

# Shell output
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
development     development     3               2024-08-28 12:59:43.671536115 +0000 UTC deployed        nginx-helm-chart-0.1.0  1.16.0

Uninstall Helm Chart
#

# Uninstall the Helm chart
helm uninstall development -n development

Package Helm Chart
#

# Package Helm chart: Define path the Helm chart
helm package nginx-helm-chart/

# Shell output:
Successfully packaged chart and saved it to: /home/ubuntu/Helm/nginx-helm-chart-0.1.0.tgz
# Verify packaged chart
ls -la

# Shell output:
-rw-rw-r-- 1 ubuntu ubuntu  207 Aug 28 12:54 custom-values.yaml
drwxr-xr-x 4 ubuntu ubuntu 4096 Aug 28 12:44 nginx-helm-chart
-rw-rw-r-- 1 ubuntu ubuntu 1210 Aug 28 13:05 nginx-helm-chart-0.1.0.tgz



Links #

# Helm Documentation
https://helm.sh/docs/chart_template_guide/builtin_objects/
Kubernetes-Components - This article is part of a series.
Part 20: This Article