Helm is a deployment tool for Kubernetes objects that supports package management, dependencies, and templating. In this article, we will explore how to optimize your Helm charts. To follow along, you’ll need a basic understanding of Helm and will have ideally written and deployed some basic Helm charts.
Helm 2 vs. Helm 3
Let’s talk about the differences between Helm 2 and Helm 3. The main difference is that Helm 2 required the deployment of a component named “Tiller” into your Kubernetes cluster. This was rendered obsolete when Kubernetes made RBAC (rule-based access control) authorization enabled by default in version 1.6. Now, Tiller has been removed from Helm 3, greatly simplifying its installation.
The other main difference is the use of a three-way merge when upgrading an application, which essentially allows Helm to take into account changes that have been manually applied to the application in the cluster.
In short, you should always use Helm 3 (and upgrade your Helm 2 charts and releases to Helm 3). If you still have Helm 2 charts, these instructions will help you migrate them.
Generally speaking, you should follow the official Helm best practices. These usually make a lot of sense, and your Helm charts will be more streamlined and easier to understand. Note that Kubernetes configuration best practices also apply, so make sure you follow them whenever relevant (which is mostly when writing templates).
Interestingly, we noticed that Helm itself does not always follow its own best practices. For example, when creating a boilerplate chart using helm create, the values file uses mostly nested values (i.e., maps), rather than a flat structure as Helm advises. In the same vein, it is almost impossible to find a published Helm chart using a flat structure for the values file. So, you might take this particular rule with a grain of salt…
The main question to ask at this stage is: Do we write our Helm charts for internal purposes only or do we want to publish them on the web and let the public use them? This question is relevant because if you want to publish them, they will be much more complex, since they will need to be more generic and cater for many different needs. In such a case, it is even more important to follow the best practices to ensure you respect the principle of least astonishment. In this way, members of the public will be able to understand your code and happily use your Helm charts.
Keep It Simple
Whether your Helm charts are for internal use only or published widely, it’s important to keep them simple so that people can use them successfully. The first rule here is to keep the number of variables low. In the real world, a lot of publicly available charts have too many “variables,” which, in many cases, aren’t variables at all, in the sense that changing their values will actually break the chart.
A typical example is a variable that allows you to override the Docker image tag, which is typically dangerous because a particular version of a Helm chart is meant to deploy a particular version of the Docker image. An application can evolve quite a bit between different versions; for example, with different configuration options. Using the chart to deploy an older version of the application may very well fail because of such incompatibilities.
A rule of thumb: If the Helm chart can’t work when a certain variable is changed, that variable should be removed. In addition, Helm does not support constraints on variables, which can be mitigated by clear and unambiguous documentation that should especially detail the allowed range of values for each variable.
One of the Kubernetes best practices is to not specify a certain configuration element in a manifest file if using a default value. Following that recommendation will keep the templates simple.
Finally, resist the temptation to put in more code to handle potential future scenarios. This tendency is part of what we call “the quest for genericity,” and often makes the code more complicated and buggy, requiring more tests. Dead code that anticipates such potential features should not live in the chart code, but you can certainly store it elsewhere so you can use it when needed.
In short, keep your Helm charts simple by removing as much unnecessary code as possible (variables that can’t vary, config that specifies default values, dead code, etc.).
It is important to reiterate here that Helm does not support adding constraints to variables, so if a variable’s value must be within a certain range of allowed values, this should be properly documented. Don’t try to save time by leaving variables undocumented, as this is the most important part of the Helm chart for people who will try to use it in their own environments.
You should also spend some time properly documenting the Chart.yaml file, as that will be the first thing other people see from your chart. Fill in as many fields as possible.
In older versions of Helm, the name of the directory where the Helm chart was located and the name of the chart as specified in the Charts.yaml had to match exactly. This is no longer a requirement, but probably still a good idea. Again, you’ll want to follow the principle of least astonishment and avoid any confusion that could arise in users’ minds.
Using a templates/NOTES.txt file is optional. However, it is a good practice to provide the user with some proper documentation in this file. Using templating might be a bit time consuming, so it’s fine to write plain text without templating too. The goal is to ensure the user knows precisely what to do after deploying the Helm chart.
Tips and Tricks
ConfigMaps often hold whole config files, so they can quickly become difficult to read. If the content of the config file is not templated, you can save the content of the config file in its own file in the Helm chart and import its content using this method. That will make the ConfigMap a lot easier to read and reason with, but won’t work if the content of the config file needs to be templated.
In many cases, a given Helm chart depends on other Helm charts. The question is: When you write both, is it better to write each Helm chart on its own or use subcharts? This is a debated question with no clear answer. Using subcharts keeps everything together and limits the requirements of the subchart, which usually allows you to write a simpler chart for the subchart. A flat chart structure is easier to understand and reason with, but the code is spread over many charts and can be difficult to follow. Usually, if there is reason to use the subchart on its own, it makes sense to use a flat structure.
Helm allows you to create so-called library charts to factor in often-used code, but, in practice, they are not that useful. You have to balance the inconvenience of copy/paste with the added complexity of and need for genericity from library charts. At the end of the day, the amount of code contained in charts is quite small, so attempting to factor out some generic elements can be a lot of trouble for little actual gain.
Helm allows you to sign charts and verify third-party charts’ signatures. This feature is seldom used and definitely deserves more recognition. Signing a chart isn’t that complicated: You just need a PGP key and add the –sign option when running the helm package command.
Verifying a chart is also very easy: Just run helm verify. When the Helm charts are used only internally, this is probably overkill, but in the case of a CI/CD pipeline deploying to production, spending some time to verify signatures is a good security practice. Additionally, publishing signed Helm charts will increase the security of the charts’ users and should be considered a best practice.
Adding tests in your chart so that you can run helm test after deploying your chart is optional. It’s usually useful only when deploying manually. When deployed as part of CI/CD, the deployment is usually smoke-tested by other means. Often, writing some basic tests, such as ensuring a certain port is open and responsive, is easy. We recommend spending a little time writing basic tests, but not more than that.
The command helm create MYCHART will create a new boilerplate chart deploying NGINX. It’s useful to have a basic structure to get started, even though this boilerplate chart doesn’t follow Helm’s best practices, especially in terms of the “flatness” of the variables contained in the values file.
Helm allows users to create plugins in order to extend its features without having to write code directly and recompile Helm’s executable. helm plugin list lists the plugins currently installed, and helm plugin install installs a plugin. Examples of plugins are helm-sudo, which allows you to run Helm commands in the admin context, and helm-push, which allows you to push your charts to a chart museum repository.
Generally speaking, for an engineer proficient with Kubernetes manifests files, writing Helm charts is fairly easy. The templating aspects are really the only part that can get complicated. It could be argued that writing good Helm charts mostly requires stripping down unnecessary code and variables (or not adding them in the first place). Keeping charts as simple as possible should always be in the mind of the engineer writing them.