Universal Helm Chart for your applications
Today we’ll talk about Helm and how we came to the universal chart, which we use as a standard tool in Nixys and why it has made our work more efficient.
Our universal Helm chart you can find in Nixys Github profile
A little bit of theory
To begin with, let’s remember what this is all about. Helm is a package manager that allows you to work with applications in K8s as easily and conveniently as with a familiar package manager such as apt or yum, helps you deliver the same application to different environments with different parameters and allows you to manage the life cycle of the application easily and conveniently. In practice, it is one of the most common and easiest ways to release applications to the cube.
Helm uses go-templates to get k8s manifests. This is a powerful and handy tool that allows you to implement a lot of interesting things and has flexible logic.
Artifact hubs are used to work with charts. Finding and installing the right version is very easy. If public charts don’t work for you, you can use your own registry hub or someone else’s. Some k8s distributions (OS, OKD, Rancher) even provide a GUI for this functionality out of the box, allowing you to work with Helm in the most convenient way possible. .
When you install an application through Helm, a release is created. A Helm release is the complete state of the application, including its configurations and environment variables (if they are in k8s). Switching between Helm releases, such as rolling back a release, also gives you exactly the parameters the application was run with, unlike, for example, switching between ReplicaSet versions in a deployment.
There are hooks in Helm. It is a very handy tool that allows you to do any job before other k8s manifests are updated. For example, if your application consists of deployment and cronjobs, and database migration is performed in init-container, you are not safe from the situation when migration is not yet completed, but cronjobs with new code are already crashing, because the database version is still old. Hook in this case solves the problem.
Work with dependencies is conveniently implemented. The parameters for starting a dependency are configured just like the application itself: all you need to do is add the dependency to the chart description and its parameters to the values file.
Helm has a large community, and almost all open-source projects have their own chart. There is nothing to stop you from downloading any chart, and changing everything in it locally.
If you use k8s manifests without templating for deploating, you’ll need to work with significantly more data to set up your application in multiple environments. For example, a 4kb file generates 48kb manifests.
List of disadvantages:
- When using public charts, there’s a chance of hitting bugs
- Complex logic is difficult to implement, there are some limitations
- In order to create a minimalistic values file, the chart must be customized for the application
How does work with charts generally happen in IT companies?
– A typical scenario for creating charts for a new project is to copy one of the old projects.
This method has its advantages.
- For example, up to 90% of work is already done, you only need to adapt the chart to your tasks, change titles, labels, cut out unnecessary things and add missing things. Besides, you can make a chart tailored to the application, taking out the parameters necessary for the project and passing a minimum of arguments for setting up environments.
Of course, this method has disadvantages:
- Copy-paste of patterns, that includes unnecessary solutions in the current project, and, as a consequence, the need to separate unnecessary solutions require attention and time.
- Good solutions are not replicated across projects, old projects rarely get updates.
If you have a single project with a few applications consisting of a deployment and a couple of vorkers, this approach is justified, even considering the drawbacks.
But what if there are more than three applications? In a microservice architecture, there could be dozens of them. In that case, it can be very time-consuming to prepare charts, let alone apply good practices on all of them at once.
How we made our own universal chart?
At Nixys, we regularly faced the need to create nearly identical charts.
On one project with 14 identical microservices, we came up with a chart format that essentially became a prototype. The chart solved the problem of deploying each microservice separately, while taking into account the specifics of each. We got a prototype and tested the “universal” concept in practice.
Later, another client required us to prepare the CI/CD for 60 almost identical projects. Here the concept of the universal chart was more relevant than ever before. So, by using the universal chart, we reduced the preparation time for release from 6 hours to 1.
In the end, we came to the conclusion that the concept of a “universal” chart works. It combines accumulated experience and good solutions, while allowing us to add what each particular project lacks.
Key Features
Here we should dwell on each feature in more detail.
Reduced deploy preparation time
As I wrote before, we managed to reduce the time needed to prepare the deploat of new projects to 1 hour through the use of a universal chart. Of course, it all depends on the complexity of your project and its dependencies, but once you understand the structure of the chart, you can quickly prepare everything you need to run your application in different k8s environments.
Generating any manifests you might need
Chart allows you to create any number of deployments, jobs and cronjobs, configmaps and secrets, and allows you to create any other additional manifests you need to run your application. You have the ability to add the templates you need to the current chart via the extraDeploy section, such as networkPolicy.
extraDeploy:
- |
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "helpers.app.fullname" (dict "name" "nw-policy" "context" $) }}
namespace: {{ .Release.Namespace | quote }}
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
You can also use go-templates as settings for your values, for example, to add a feed annotation with an application configuration check-sum.
Compatibility with different k8s versions.
deployments: api: podAnnotations: checksum/api-key: '{{ include "helpers.workload.checksum" (index $.Values.secrets "api-key") }}'
Thanks to the built-in k8s version checking mechanisms, compatible versions of api manifests are available. The k8s version is determined automatically at the manifest generation stage, but also can be manually set up in values.
kubeVersion: v1.22.3
So, you can deploy your application to any version of k8s. Without worrying if the correct api version has been used in the manifest.
However, this cool thing also has its disadvantages:
- If you need to do modifications, you must have a high level of Helm skills.
- There are limitations associated with using go-templates in values. In particular, there are limitations on string length and some characters.
Instead of an epilogue
In practice this solution has already proved its best side and works on client projects, so we will continue to use it actively. But we also want to share our work with the community to make the universal chart even better and more convenient, so that you’ll have more free time.
The source code of the chart is published on GitHub.
If you have new ideas and suggestions to improve the universal Helm Chart, we’d love to hear from you.