One Chart to rule them all – How to implement Helm Subcharts
As part of my work as a DevOps Engineer in my company, I was in charge of building services Helm charts for deployments. One of the things I personally hated the most was working with multiple microservices with their own Helm Chart. As the company began its journey to Kubernetes, we decided to include the microservice chart inside its application git repo. So the repo tree would look something like this:
├── apps
├── deploy
│ └── chart
│ ├── templates
│ └── values.yml
├── docs
├── ...
.
.
The problem is that, when you decide to upgrade a microservice chart(for example, including a way to mount secrets and configmaps dynamically), it is almost certain that the other services will need that same feature. At first, we started adding these features to the other services, but of course, this isn’t very efficient (or fun, for what matters…)
At first, we developed a common chart based on the microservice framework. We had a separate group in GitLab that hosted the Helm Charts for apps based on Django, React, AioHTTP, and so on. He had our own Chartmuseum as a chart repository. This wasn’t all bad, but when you wanted to install an application in a different namespace or locally, that meant that you had to customize the values from the chart completely (even the docker registry).
That’s how we came across Helm Subcharts. It’s Helm’s approach to Inheritance. Charts can have dependencies, called subcharts, that also have their own values and templates.
Getting things cleared out…
Before we dive into the code, there are a few important details to learn about subcharts.
- A subchart is considered “stand-alone”, which means a subchart can never explicitly depend on its parent chart.
- For that reason, a subchart cannot access the values of its parent.
- A parent chart can override values for subcharts.
- Helm has a concept of global values that can be accessed by all charts.
What we will build?
Here, we are going to create a Django Helm Chart. This chart will be the base for our Django applications to inherit from to create their own charts.
Getting started with Helm Chart
First, let’s take a look at our basic Django Helm Chart:
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── ingress.yaml
│ └── service.yaml
└── values.yaml
As we can see, it’s very simple. It has the deployment for the app itself, a service to connect to the Django server, and an ingress to define its own URL.
The most important part of the chart itself it’s defined in the global part of the values.yaml. Here, we tell our Chart where the application’s Docker image is (image.registry), the name of the application (projectName), and its version (image.tag).
global:
# enabled is the master enabled switch. Setting this to true or false
# will enable or disable all the components within this chart by default.
enabled: true
# Used to define custom django.fullname in template.
projectName: ""
# image sets the repo,tag and pullPolicy of the project
image:
registry: ""
tag: latest
pullPolicy: Always
deployment:
# Run migrations at re-deploy?
migrations:
enabled: true
# Run collectstatic at re-deploy?
collectstatic:
enabled: true
# Kubernetes secrets pulled on pod creation
secretName: ""
replicaCount: 1
resources: {}
# resources:
# requests:
# memory: 256Mi
# cpu: 250m
# limits:
# memory: 256Mi
# cpu: 250m
ingress:
enabled: false
annotations: |
kubernetes.io/tls-acme: "true"
hosts:
- host: microservice1.mycompany.com
paths:
- /myapp1
- /myapp2
tls:
enabled: true
service:
enabled: false
port: 8000
targetPort: 8000
So imagine that the company has a Helm repo in https://charts.mycompany.com, whenever you would like to install the project “microservice1” in your Kubernetes Cluster, you have to define these variables explicitly:
$ helm install mycompany/django --set global.projectName microservice1 --set global.image.registry mycompany/microservice1 --set global.image.tag v1.11.2
Creating your helm subcharts
In order to make this simpler, we can create a Chart for “microservice1” that inherits from the parent Django Chart. This can be done by defining the Django Chart as a dependency:
apiVersion: v2
name: microservice1
version: 1.0.0
Appversion: 1.11.12
description: This Chart deploys microservice1.
dependencies:
- name: django
version: 0.0.1
repository: https://charts.mycompany.com
The values.yaml changes a little bit since now we have to define what will be rendered for the Django subchart.
django:
global:
# enabled is the master enabled switch. Setting this to true or false
# will enable or disable all the components within this chart by default.
enabled: true
# Used to define custom django.fullname in template.
projectName: "microservice1"
# image sets the repo,tag and pullPolicy of the project
image:
registry: "mycompany/microservice1"
tag: v1.11.12
pullPolicy: Always
.
.
.
In order to publish this chart in our Chartmuseum repo, we first need to download its dependencies:
$ helm dependency update
This will create a folder named “charts” inside our microservice1 Chart folder and download the Django Helm Chart inside (this is because a subchart is considered “stand-alone”, so in order to create a package, the parent chart should be inside the subchart package). The final package should look something like this:
├── Chart.yaml
├── charts
│ └── django
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ ├── ingress.yaml
│ │ └── service.yaml
│ └── values.yaml
├── templates
└── values.yaml
Now we can publish our chart for the microservice1 app:
$ helm repo add mycompany https://charts.mycompany.com
$ helm push . mycompany
And that’s it! You can install the microservice1 chart in your Kubernetes cluster just like this:
$ helm repo add mycompany https://charts.mycompany.com
$ helm repo update
$ helm install mycompany/microservice1 --namespace test
Questions?
Please feel free to join us on Craftech’s community Slack and ask any questions.