1. Overview

A Helm chart packages a Kubernetes application in an easily distributable format. Through the Helm chart, we can express all the necessary Kubernetes resources that should be installed along with the main application. This vastly simplifies the installation process of a Kubernetes application for end users. Instead of performing manual steps to create and install all the necessary Kubernetes resources, we’ll run a single command, helm install, to install a package.

Crucially, we can parameterize a Helm chart to make it highly configurable. This allows us to externalize properties of our application that are environment-specific.

In this tutorial, we’ll learn how to parameterize our Helm chart to make our chart configurable.

2. Parameterizing a Helm Chart

A Helm chart consists of templates for Kubernetes resources that define the application. Within the template, we can set a static value for the different fields of the resources. Alternatively, we can parameterize the value of a field by referencing one of the Helm’s built-in objects. Let’s look at an example:

$ cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}-deployment
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}-container
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: 80

In the Deployment resource template above, we’ve parameterized various fields by referencing the Values and Chart built-in objects. To reference a built-in object in our template, we enclose the reference using the double curly braces operator.

For instance, the value of the replicas field refers to the replicaCount key within the Values built-in object. In essence, this makes the replica count of the Deployment resource configurable. During installation, the end users can specify a different r**eplicaCount value to customize the installation based on their needs.

Contrast the replica count field to the container’s containerPort field, which has a static value of 80. Because it’s not referencing any of the variables, the values will be the same for all the installations using the same Helm chart.

Let’s look at the different built-in objects in detail.

2.1. The Values Built-in Object

The Values built-in object loads the values from the various value files and values passed using the –set flag during the installation. When the key is repeated, the helm command resolves the conflict by following a specific order of precedence.

At the lowest order of precedence is the values.yaml file in the Helm chart. The chart developer typically defines the default values of all the configurable parameters in the chart’s templates.

Next, the helm command loads the externally passed-in value files to the Value object. These external value files are passed to the command during the chart installation using the -f option. Let’s see an example:

$ helm install -f values-standalone.yaml

Essentially, the values we define in these external value files will override the default value in the chart’s values.yaml. Lastly, the helm command loads the values set using the –set option during the installation.

2.2. The Chart Built-in Objects

The Chart built-in variable provides access to data in the Chart.yaml. For example, we can access the chart’s name, the appVersion, and the description data through the Chart object.

One common pattern is referencing the Name variable of the Chart object when we need to get the application’s name. For example, we can set the name of our Deployment resource to follow the chart name using the double curly braces operator:

$ cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}-deployment
  labels:
    app: {{ .Chart.Name }}
    version: {{ .Chart.AppVersion }}   # Reference to .Chart.AppVersion
spec:
...

The advantage of this pattern is that any changes in the Chart.yaml will automatically propagate to the templates.

3. Demonstration: Configuring a Helm Chart Installation

Let’s consider a Kubernetes application that runs pods to serve web requests. The number of pods is dependent on the environments we’re in. In the dev environment, the traffic is generally lower so it can use a lower count of pods. On the other hand, we’re expecting larger traffic in the prod environment, so we’ll need to set higher replication counts. Besides the replication count, every other application’s parameters should remain the same.

To solve this problem, we can externalize the replicas parameter of a Kubernetes Deployment resource. Instead of setting it to a static value, we reference the Values built-in object. Then, we can pass in the replica count using the value file during installation.

Let’s dive in.

3.1. Creating a Helm Chart

To demonstrate the idea, we’ll create a simple Helm chart. Firstly, we’ll instantiate the Helm chart by creating a directory using the mkdir command:

$ mkdir web && cd web

Then, we’ll create a Chart.yaml to define properties for the chart:

$ cat >Chart.yaml<<EOF
> apiVersion: v2
> name: web
> description: A simple web application
> version: 0.1.0
> appVersion: "0.1.0"
>
> EOF

The command above uses the cat command and the heredoc syntax to create the Chart.yaml. Crucially, every valid Helm chart must include the Chart.yaml file. The helm command doesn’t recognize a directory as a valid Helm chart without the Chart.yaml.

Subsequently, we’ll create the deployment.yaml in the templates directory:

$ cat >templates/deployment.yaml<<EOF
> apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}-deployment
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
        - name: ubuntu-container
          image: ubuntu:latest
          command: ["sleep", "infinity"]
> EOF

Again, we’re using the cat command and the heredoc syntax to write the deployment.yaml template file. Notably, we’ve parameterized the replicas field of the Deployment resource by setting its value to the value of Values.replicaCount. Essentially, we’re expecting one of the value files to specify the replica count using the property key replicaCount.

3.2. Default Value File

Importantly, we should define the default value for the parameters we’re referencing in our templates. To do that, we can create a values.yaml file in our chart directory and give all the properties a sensible default value. Let’s create a value.yaml file in our Helm chart directory with the cat command:

$ cat >values.yaml<<EOF
> replicaCount: 1
> EOF

Through the values.yaml file, we’ve set the default value of the replicas of our Deployment resource to one.

3.3. Installing the Helm Chart

Now, we’re ready to test out the Helm chart. Let’s install the chart using the helm install command:

$ helm install -n dev web-dev .
NAME: web-dev
LAST DEPLOYED: Sat Apr 13 13:17:17 2024
NAMESPACE: dev
STATUS: deployed
REVISION: 1
TEST SUITE: None

We use the -n option of the helm install command to specify the Kubernetes namespace to install. Additionally, we name our release web-dev. Lastly, the final positional parameter specifies the location of the Chart, which is our current directory.

Since we did not pass in any external value files to override the default value, we would expect the Helm chart to create a Deployment resource with the replicas value of one.

Let’s verify that expectation using the kubectl get po command to get all the pods in the dev namespace:

$ kubectl get po -n dev
NAME                              READY   STATUS    RESTARTS   AGE
web-deployment-694744bf97-84nqc   1/1     Running   0          74s

As expected, our Helm chart installation creates a Deployment resource that deploys a single pod.

3.4. Customizing the Helm Chart Installation

Now, let’s create a values-prod.yaml file that represents the configuration for the production environment:

$ cat >values-prod.yaml<<EOF
> replicaCount: 2
> EOF

In our values-prod.yaml file, we set the replicaCount property to a value of 2.

Then, we’ll install the Helm chart on the prod Kubernetes namespace while passing the values-prod.yaml file using the -f option:

$ helm install -n prod -f values-prod.yaml web-prod .
NAME: web-prod
LAST DEPLOYED: Sat Apr 13 13:20:43 2024
NAMESPACE: prod
STATUS: deployed
REVISION: 1
TEST SUITE: None

Let’s check the pods in our prod namespace using the kubectl get po:

$ kubectl get po -n prod
NAME                              READY   STATUS        RESTARTS   AGE
web-deployment-694744bf97-sggjs   1/1     Running       0          20s
web-deployment-694744bf97-r4fhg   1/1     Running       0          20s

Because the external value files take precedence over the default values.yaml, we’ve effectively set the replicas property of the Deployment resource to two.

4. Conclusion

In this tutorial, we’ve learned about parameterizing Helm charts. Specifically, we’ve looked at Helm’s built-in objects, which we can reference in our template, including the Values and Chart objects. Then, we walked through an example demonstrating the idea of externalizing properties to make a Helm chart reusable across different environments.