Kubernetes is an excellent tool for container orchestration, but managing configurations and deployments manually can become cumbersome as your environment grows. Helm simplifies this process by introducing the concept of “charts.” These charts encapsulate Kubernetes manifests, offering a reusable and templated solution for orchestrating deployments, services, config maps, and other Kubernetes resources. Overall, Helm makes deployments configurable, simpler, repeatable, and manageable.
Helm helps you to deploy your own code, but also to manage and deploy necessary infrastructure components - such as Redis, Prometheus, cert-manager, MinIO, Nginx, or PostgreSQL - with ease.
This blog post provides a beginner-friendly introduction to Helm 3, highlighting its key features and benefits. We’ll explore the distinctions from Helm 2, its potential benefits, and steps to begin using it. Towards the conclusion, we’ll delve into recommended practices for seamlessly integrating it into your current DevOps framework.
No Helm Without Kubernetes
To commence our explanation of Helm, it’s essential to briefly examine Kubernetes, often abbreviated as K8s, and grasp some fundamental principles of its operation.
Kubernetes is an open-source container orchestration system for automated software deployment. The project is Cloud Native Computing Foundation (CNCF) project, like Helm itself.
The Concept of “Desired State”
Kubernetes employs the foundational concept of a “desired state,” where in resources are specified, typically in YAML format, to reflect their intended configuration. Kubernetes then endeavors to reconcile the current state with the desired state outlined in the manifests.
For example, you normally deploy a simple microservice with a Deployment. In a Deployment, you set the amount of desired replicas. When you apply this Deployment, the app will first have no replicas at all, then Kubernetes will try to go to the desired state and deploy the needed Pods for the deployment.
By manually deleting a Pod, Kubernetes will recreate a Pod to get to the desired number of replicas.
The Challenge With Kubernetes Manifests
While YAML files provide a powerful way to configure Kubernetes resources, managing them can become complex and error-prone as the number of files and configurations increases.
What is a Kubernetes manifest?
A Kubernetes manifest is a YAML (sometimes also represented as a JSON file) that defines the desired state of Kubernetes resources such as a Pod, Deployment, or Service.
File Size
The files get huge, and it’s hard to keep track of what is important. The issue lies in the API’s low-level nature, lacking built-in abstraction.
Copy-Paste Problem
I’ve come across many errors and peculiar setups due to the direct copying of manifest files. For instance, engineers would duplicate an existing deployment to create a new one and only update certain values, leaving others untouched. Consequently, unintentionally, the CPU and RAM limits from the previous Deployment were reused. As a result of this copy-paste approach, it became unclear which values were intentionally set and which ones were merely copied over.
Zombies
Another big problem are “zombie” resources: When you use YAML files to deploy multiple resources as Kubernetes manifests, you normally just do a kubectl apply
. This will create and update resources. However, if you rename or delete resources, the old ones won’t be deleted. To accomplish this, you’ll require a kubectl delete
command, which must be executed explicitly because Kubernetes lacks awareness of the resources contained in the file beforehand.
In fact, not removing unused resources on Kubernetes is one of the biggest factors for overspending on Kubernetes, according to the latest FinOps + Cloud Financial Management Microsurvey of the CNCF.
The Solution
Helm solves these problems by introducing the concept of charts.
What Is Helm?
Helm is a graduate project of the CNCF that simplifies Kubernetes deployments using charts. A chart is a bundle of Kubernetes configurations, similar to an apt-get package on Ubuntu. Helm uses these packages called charts to deploy resources onto a Kubernetes cluster.
In essence, it handles three main tasks: creating, updating, and deleting Kubernetes resources. So, it basically does kubectl apply
, kubectl delete
(to simplify).
Did you know what the original meaning of “Helm” is?
“A helmsman” or “helm” is a person who steers a ship, sailboat, submarine, other types of maritime vessel, or spacecraft."1
Helm Charts
Helm charts are the packages of Helm. A simple package has the following structure:2
mychart/
Chart.yaml # A YAML file containing information about the chart
values.yaml # The default configuration values for this chart
templates/ # A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files.
The format is more powerful and can contain more, as you can see below.2 However, our focus will be on the most crucial files at the top.
mychart/
Chart.yaml # A YAML file containing information about the chart
LICENSE # OPTIONAL: A plain text file containing the license for the chart
README.md # OPTIONAL: A human-readable README file
values.yaml # The default configuration values for this chart
values.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml file
charts/ # A directory containing any charts upon which this chart depends.
crds/ # Custom Resource Definitions
templates/ # A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files.
templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes
When distributing the charts, you can opt for either an artifact or an Open Container Initiative (OCI) image.
Chart.yaml File
The Chart.yaml
file contains metadata of the chart.3
Below is a quite minimal version of the Chart.yaml
file to show the intent of the chart:
apiVersion: v2
name: mychart
type: application
version: 0.1.0
values.yaml File
The values.yaml
file contains the values of the chart, which are then utilized within the templates to configure the manifest files.
Templates
The template files in the templates/
folder are rendered with the go template syntax. However, helm extended the Go template language with some extra functions and wrappers.4
They look as follows:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}-config-map
labels:
{{- include "mychart.labels" . | nindent 4 }}
data:
config: {{ .Values.config }}
The Go templating engine is quite powerful. You have functions, pipes (|
), nesting, logical operations and loops.
Helm Version 3: A Major Improvement With a New Architecture
Before we dive into Helm 3, it’s essential to note that Helm underwent significant changes in version 3. In Helm 2, there was a Tiller component running in the Kubernetes cluster, but Helm 3 removed this, making Helm a client-only application. This change simplified Helm usage, removed security concerns5, and allowed users to start using Helm without deploying additional components.
Security
Helm 2 used a single Tiller installation for all deployments on the cluster. This meant that all role-based access controls (RBACs), which were required for any deployment in Helm, needed to be given to Tiller. With Helm 3, you can give each developer (or continuous deployment (CD) pipeline user) only the access she/he/it needs for the deployment.
Practical Usage: Deploying an Nginx Server
Let’s walk through a simple example of deploying an Nginx server using Helm. Nginx is a simple HTTP server that is commonly used for web applications on Kubernetes.
Here, we’ll briefly cover how we can accomplish this. In the next section we will further dive into the details of what happened.
Find the Chart: Helm applications are installed with charts. You can find charts really easily on the Artifact Hub. The Nginx chart can be found here: https://artifacthub.io/packages/helm/bitnami/nginx
Install the Chart:
helm install hello-world oci://registry-1.docker.io/bitnamicharts/nginx
Here, we install the chart from an OCI source.
hello-world
is the name of the release that we have chosen for this example.
And that’s it!
After installation, we can use our web server.
To test it briefly, we can initiate a port forwarding: kubectl port-forward svc/hello-world-nginx 8000:80
.
Demo of how to deploy a chart (here, an Nginx server).
You can try that yourself with the GitHub sample
demo-1.sh
.
Deploy a Chart: What Happens Exactly?
Now let’s go into the details on what happened exactly when we deployed the chart:
1. Pull
The first thing the helm install
command does, is pulling the chart (if the chart is stored remotely).
In this case, we use an OCI registry. Traditionally, Helm charts were stored as artifacts, but there’s a growing trend toward utilizing OCI registries. This streamlines hosting, as you can utilize your Docker registry as both a Docker and a Helm registry simultaneously.6
You can try that yourself with the GitHub sample
demo-2.sh
.
2. Template
After the image is pulled, it gets templated. Templating is the process of rendering the Go template-based charts to YAML-based Kubernetes manifest files, which can then be sent to the Kubernetes API.
Unrendered Chart (head):
{{- /*
Copyright VMware, Inc.
SPDX-License-Identifier: APACHE-2.0
*/}}
apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
kind: Deployment
metadata:
name: {{ include "common.names.fullname" . }}
namespace: {{ include "common.names.namespace" . | quote }}
labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
{{- if .Values.commonAnnotations }}
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
{{- end }}
Rendered chart (head):
---
# Source: nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-nginx
namespace: "knative-debug"
labels:
app.kubernetes.io/instance: hello-world
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nginx
app.kubernetes.io/version: 1.25.4
helm.sh/chart: nginx-15.14.0
You can try that yourself with the GitHub sample
demo-3.sh
.
3. Install
Now, the rendered manifest files can be deployed on Kubernetes, which is simplified the same as a kubectl apply
.
4. The Deployment
When the chart is deployed on Kubernetes, the reconciliation loop will try to achieve the desired state. In the case of the deployment, Kubernetes will try to start the Pods.
You can try that yourself with the GitHub sample
demo-4.sh
.
Make Changes to the Chart
In the previous example, the deployment of a chart was illustrated. We initially installed the chart exactly as provided (only with default values). However, the true strength of Helm lies in customizing these charts to suit specific needs.
If we look again at the Nginx chart, we can find all the parameters that can be configured on the Artifact Hub: https://artifacthub.io/packages/helm/bitnami/nginx#parameters
Now, let’s change the configuration replicaCount
. This will change the number of replicas that will be deployed.
We have two methods to alter the configuration: either by passing a parameter or by applying a file containing the corresponding values.
Passing a value by parameter
A parameter can be passed to the chart by adding
--set
with the appropriated parameter:helm upgrade hello-world oci://registry-1.docker.io/bitnamicharts/nginx \ --set replicaCount=2
Passing a value by file
You need to create a YAML file containing the value. For our example, we call it
myvalues.yaml
, containing:replicaCount: 2
Afterward, we can upgrade the chart by passing a reference to our file:
helm upgrade hello-world oci://registry-1.docker.io/bitnamicharts/nginx \ -f myvalues.yaml
Once the change is deployed, you’ll observe with kubectl get po
that we now have two Nginx Pods.
Revisions
These upgrades (e.g., a value change from just before) to a release will always create a new revision. We can see that by listing the releases:
$ helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
hello-world default 2 2024-04-02 22:30:04.156194 +0200 CEST deployed nginx-15.14.2 1.25.4
If a change was a mistake or something is not working anymore, we can simply roll back the change to the previous revision:
$ helm rollback hello-world
Rollback was a success! Happy Helming!
You can try that yourself with the GitHub sample
demo-10.sh
.
Popular Helm Charts
There are numerous charts available for Helm beyond just Nginx; you can find some popular by viewing the list on Artifact Hub Stats. Here are some noteworthy highlights that you may find useful:
Create an Own Chart
In many scenarios, you’ll likely need to deploy your own code rather than solely relying on charts from others. Let’s explore how we can create our own chart for that purpose.
Create a Chart From a Template
To start with the creation of your own chart, it’s really useful to generate it from the template.
To do so, just execute helm create mychart
. This will create a folder with the needed structure for a new chart. It also creates a deployment, an ingress, a service, etc. That’s an excellent way to begin. The fully generated structure looks as follows:
mychart
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
After the chart is configured as needed, it can be installed as before with helm install myapp ./mychart
.
Publish a Chart
Publishing a chart is really simple using the OCI format. You can just use any OCI/Docker registry (e.g., Docker Hub or Amazon ECR)7 for it:
Pack the chart
helm package ./mychart
Publish the chart
helm push mychart-0.1.0.tgz oci://my.registry/project/mychart
Publishing a chart to a Helm chart repository as an artifact is also an option, though it’s a more complex process and beyond the scope of this post. For more details, see the official docs.
Best Practices
Now that we understand the basics, let’s look into some best practices when working with Helm.
Complexity With Many Charts
When you realize how Helm can streamline your processes and start applying it to all your resources, managing multiple Helm releases can become challenging. How should we handle this complexity?
Chart Dependencies
Helm charts can depend on other Helm charts.
In a Chart.yaml
file, you can declare these dependencies:
# umbrella-chart/Chart.yaml
dependencies:
- name: svc1
repository: "https://charts.ex.com/repo"
version: 1.2.3
- name: svc2
repository: "https://charts.x.com/repo"
version: 2.3.4
Umbrella Charts
If you aim to deploy all your charts at once, enabling deployment with a single helm install
, you can achieve this by creating an umbrella chart.
An umbrella chart is a chart that contains all other charts as a depencencies.
@startuml
top to bottom direction
(<<Chart>>\nUmbrella Chart) as (U)
(<<Chart>>\nMicroService 1) as (MS1)
(U) --> (MS1)
(U) --> (<<Chart>>\nMicroService 2)
(MS1) --> (<<Chart>>\nDB)
@enduml
The Problem With Dependencies
Here are some problems with “the big umbrella chart” approach:
- No separately deployable units While having dependencies between Helm charts can be beneficial, deploying numerous Helm charts with dependencies as one atomic unit can introduce instability. The deployment will be slow, and the rollback is bigger, as we always need to roll back all charts together.
- Installation of subcharts Subcharts still need to be installed first before you can use
helm upgrade
. This makes a CI deployment more difficult. - Release name isolation There is no name isolation on subcharts. Thus, when MicroService 2 of the previous example will have a dependency called
DB
for example, there will be a naming collision with the MicroService 1 dependencyDB
. - Kubernetes namespaces You cannot set a Kubernetes namespace for each chart; instead, you need to select a single namespace for the umbrella chart.
There is a solution to all these problems: helmfile. This is an additional tool that encapsulates Helm’s functionality.
CI/CD & GitOps
It’s good practice to have a continuous integration (CI) and continuous deployment (CD) pipeline in order to have tested increments that can be deployed reproducable and fast. This approach fosters a culture of small, frequent deployments.
A simple approach to achieve this with Helm is to create a GitOps flow like the one in the image below:
- You start with the current deployed version (here v1).
- A change is created in a separate branch (e.g., a feature or a fix).
- A
helm diff
can be executed to see the changes that will be deployed when the branch is merged. (Helm Diff is a plugin of Helm, it will shortly be introduced in Helm Diff) helm lint
can analyze static config problems.- Once the change looks fine, it can be approved and merged.
- This will result in a new version of the deployment.
- Finally, this version can be released with a
helm upgrade
in the CD pipeline.
Terraform
If you are using Terraform for your deployments, you can also integrate Helm into your existing workflow. Terraform has a Helm provider that lets you deploy Helm charts with your Terraform workflow.
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
}
resource "helm_release" "hello-world" {
name = "hello-world"
chart = "oci://registry-1.docker.io/bitnamicharts/nginx"
version = "15.4.2"
set {
name = "replicaCount"
value = "2"
}
}
Sample of a Helm deployment in Terraform of the already known hello-world
sample.
You can try that yourself with the GitHub sample and the command
terraform init && terraform apply
.
The Pain Points of Helm and Some Solutions
Even though Helm is a great tool and makes many things simpler, it also adds some new problems:
Go templating render problems Debugging can be challenging when developing new, complex Go templating rendering logic for a chart.
helm template --debug <chart name>
can provide assistance, the process may still not be straightforward at times.Big upgrades of third party charts The challenge of upgrading resources isn’t necessarily reduced with Helm. Even with Helm, ensuring a smooth transition and safeguarding against data loss (especially if the chart contains data within the cluster, e.g., a database) can be a complex process.
Not fully declarative Integrating Helm into the CI pipeline introduces an additional step, as you must first execute a
helm install
followed by ahelm upgrade
for new charts.Dependency problems Check out the previous section, where we delved deeper into this matter.
Useful Extensions
You can add some extensions and other tools to overcome some of the pain points mentioned before.
Helm Diff
The plugin Helm Diff can help to visualize the changes that are made between releases. This can be really helpful to view differences between parameter changes or upgrades.
For instance, this can be used before we upgrade the replicaCount
, as shown in Make Changes to the Chart.
Instead of instantly doing an upgrade with:
helm upgrade hello-world oci://registry-1.docker.io/bitnamicharts/nginx \
--set replicaCount=2
We can first do a diff
:
helm diff upgrade hello-world oci://registry-1.docker.io/bitnamicharts/nginx \
--set replicaCount=2
This will provide us with a diff of the resources that will be changed (excerpted output):
default, hello-world-nginx, Deployment (apps) has changed:
# Source: nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-nginx
namespace: "default"
labels:
app.kubernetes.io/instance: hello-world
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nginx
app.kubernetes.io/version: 1.25.4
helm.sh/chart: nginx-16.0.1
spec:
- replicas: 1
+ replicas: 2
revisionHistoryLimit: 10
Helmfile
Helmfile can help to simplify multiple Helm deployments at once and make it more declarative.
However, delving into the specifics is beyond the scope of this article. You can watch the talk An introduction to Helm where I explain Helmfile.
Conclusion
Helm empowers developers to manage Kubernetes resources efficiently. By leveraging charts and the Helm CLI, you can streamline deployments, ensure consistency, and simplify your Kubernetes workflow.
Give it a try yourself!
I created a small Helm demo on GitHub. You can run the demo on your machine or in GitHub Codespaces. With GitHub Codespaces, no installation is needed, and you can try Helm and Kubernetes within your browser.
Additional Resources
- Official docs of Helm: https://helm.sh/docs/
- The Artifact Hub (a package browser for Helm) https://artifacthub.io/
- The demo repo: https://github.com/Lazzaretti/helm-demo
- A talk I held about Helm and also dived into helmfile and its benefits: An introduction to Helm
Special thanks to the reviewers, Dr. Annegret Junker and Hannah Li Hägi, for dedicating their time and effort to providing constructive and valuable feedback for this post.