Mastering Kubernetes Package Management with Helm: A Comprehensive Exploration

Jack Zhai
6 min readMar 7, 2024

--

Photo by Manja Vitolic on Unsplash

The Nature of Kubernetes’ Package Manager

“Helm is the package manager for Kubernetes,” says the official Helm website.

What is a “package manager for Kubernetes”?

Let’s assume you need to deploy resources without a package manager. You would need to manually execute kubectl apply -f abc.yaml file by file, where abc.yaml is the definition file for the Kubernetes resources.

The contents of the file are as follows:

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: abc
labels:
app.kubernetes.io/name: abc
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: abc

What about when you need to uninstall a resource? Again, you would need to manually execute kubectl delete -f abc.yaml.

So every time you deploy, you have to keep a record of which YAML files to apply and which to delete. After deleting, you have to remember to remove that file from the folder.

If you do this manually every time, it’s a lot of work, not to mention the potential for mistakes. Some people would think of using Shell scripts or Python scripts to solve these problems.

When you can automate the above problems through Shell scripts or Python scripts, you are essentially implementing a Kubernetes package manager.

Once we truly understand the deployment of Kubernetes resources mentioned above, you’ll see that a Kubernetes package manager actually has two core functions:

  1. Automate the execution of Kubernetes resource updates;
  2. Track Kubernetes resource update records (essentially versioning).

When choosing a package manager, we must consider these two perspectives. For example, Grafana’s Tanka didn’t initially implement the “track Kubernetes resource updates” feature, see https://github.com/grafana/tanka/issues/88.

How Helm Implements Package Management

Note: This article is about Helm 3; Helm 2 is quite different from Helm 3.

Helm’s Package: Chart

If there exists a microservice x, and we deploy it to Kubernetes, we need to prepare YAML files for these three resources: Deployment, HPA, and Service. These three files are uniformly placed in a single folder.

Helm itself is a command line tool. With the package subcommand, the entire folder can be packaged into a tgz archive. The package command is helm package x-service --version 1.0. The result is a tgz package, as shown below:

This tgz package is what we call a Chart package. Essentially, it is a collection of resource files for Kubernetes.

We can upload the Chart package to artifact management tools like Nexus for version control. This relates to engineering practices around managing Charts, which is outside the scope of this article.

After we have the Chart package, we can use the command helm install <release> <chart path> to install the service into the specified Kubernetes cluster. For example, the deployment command for x-svc would be: helm install x-svc ./x-svc-1.0.tgz.

release is a Helm concept, referring to the release name. Every time you execute helm install, Helm creates a new release. We typically use the application name as the release name.

This release concept is very important in tracking resource changes. We will repeatedly use this concept later.

Using Templates to Solve Chart Scaling Issues

In real work, we may have y-svc, z-svc… n services. Do we need to create a separate Chart for each service? Additionally, each service will be deployed to three environments, so do we need to create another separate Chart for each environment? Ultimately, we would need to create Charts through a combination of services and environments.

If this problem is not well solved, the number of Charts would explode. How does Helm solve this?

It uses templating. In other words, it separates the easily changeable parts of the resource files in the Chart into variables, leaving the rest as templates.

The variable configurations are placed together in the values.yaml file within the Chart package. So we often refer to this part as the values configuration or values file.

With this, the structure of our Chart package becomes like this (there are some other files in reality, but they are not within this article’s scope):

For the unchanging parts of the Chart, Helm uses the Go template language. This means we can write Go templates directly in files like deployments.yaml, as shown in Code 1:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.name }}
labels:
{{- include "demo.labels" . | nindent 4 }}
...omitted for brevity
resources:
{{- toYaml .Values.resources | nindent 12 }}

We don’t intend to start a template language war — we don’t have a fondness for Go templates. You need to carefully maintain the number of spaces in the template files. In Code 1’s last line, it means that the YAML block should be indented 12 spaces.

But the key question is, how do we determine it should be 12 and not 10? And if we refactor this code, I have to recalculate the number of spaces again!

Using Go templates as its templating language is Helm’s biggest mistake. We may need to write another article introducing methods to avoid this problem.

When writing Helm Go templates, we recommend not writing overly complex logic — it’s better to have some repetition in the code, or even create a new Chart.

Executing Resource Changes

Once the values and Chart are prepared, we can deploy all x-svc resources to the specified namespace with the following command:

helm install x-svc ./svc-chart-1.0.tgz -f x-svc-value.yaml

Note that for a new service, you use the install subcommand for the initial deployment. For future updates, you can only execute the upgrade subcommand.

Following this pattern, the deployment command for y-svc would be:

helm install y-svc ./svc-chart-1.0.tgz -f y-svc-value.yaml

After a successful install, if you need to modify that release, you need to execute the upgrade command, like so:

helm upgrade y-svc ./svc-chart-2.0.tgz -f y-svc-value.yaml

But how does Helm know whether to create/change resources or delete resources? The svc-chart-2.0.tgz may be missing a Deployment resource compared to version 1.0.

This relates to tracking resource changes.

Tracking Resource Changes

Before introducing “resource change tracking”, let’s go over a few important related subcommands:

  1. upgrade: Update an existing release. e.g. helm upgrade y-svc ./svc-chart-1.0.tgz -f y-svc-value.yaml
  2. list: List all installed releases
  3. rollback: Roll back a release to a previous revision. You can even specify rolling back to a particular version with: helm rollback <RELEASE> [REVISION] [flags]
  4. history: List the revision history for a release

Some readers may have noticed an issue: when executing Helm commands, there is no specified or default persistent storage for release information — so where is this release information recorded?

And how does this relate to the “resource change tracking” mentioned above?

They are related. Helm’s core principle lies here:

  1. On initial deployment using install, Helm will directly create a secret of type helm.sh/release in the specified namespace (default is default). The secret name follows the convention: sh.helm.release.v1.<release>.v1. The secret content is the YAML contents of all the Kubernetes resources executed in this release.
  2. When using upgrade to update, Helm retrieves all the YAML resource contents from the sh.helm.release.v1.<release>.v1 secret and compares them with the YAML resources for the current upgrade. It calculates the operations needed for this update — deletions, changes, etc. (Source code: https://github.com/helm/helm/blob/main/pkg/action/upgrade.go#L286)
  3. When the upgrade is successful, Helm creates a new secret named sh.helm.release.v1.<release>.v2. When you see this v2, you know that Helm is recording each release by convention of the secret name combined with its content. On the next upgrade, Helm will retrieve the v2 secret, perform the update, and create the v3 secret. And so on.

For a friendlier display, Helm hides all these low-level details. So when you execute the history command, you’ll see:

Screenshot taken from the Helm website

At this point, the essence of Helm has been covered. Remaining details can be learned by consulting the documentation.

Summary

Although this article’s title is about the essence of Helm, it’s really about the essence of a Kubernetes package manager:

  1. Automate execution of Kubernetes resource updates
  2. Track records of Kubernetes resource updates (essentially versioning)

You can use these two points to evaluate tools like Kustomize or other package managers.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Jack Zhai
Jack Zhai

Written by Jack Zhai

DevOps,SRE,Bazel The Author of 《Jenkins2.x In Practice》, https://showme.codes

No responses yet

Write a response