Patch Any Helm Chart Template Using a Kustomize Post-Renderer

Photo by Every Angle on Unsplash

When I first started using Helm, I assumed that the sky was the limit in terms of what you could configure in a Helm chart. Need to add volumes? I believed you could always change that. Labels? “No problem,” I thought.

Being much more experienced with Helm today, I know that my assumption was not correct because there was no way to change parts of a Helm chart that were not exposed by values…until now. Helm 3.1 introduced the ability to use post-renderers, which allow you to patch any resource that a Helm chart manages before they are sent off to the API server.

There are a few different types of post-renderers, but in this post, we’ll take a look at the Kustomize post-renderer and how you can use it to make changes to your chart’s resources, even if the chart doesn’t expose any values that allow you to do so.

To understand the Kustomize post-renderer, you first need to know how the Kustomize CLI works. Let’s quickly explore this tool before diving into the post-renderer functionality.

What is Kustomize?

At a high level, Kustomize is used to patch Kubernetes resources. It is used to make small changes to already-defined resources to prevent you from rewriting large amounts of boilerplate.

Imagine, for example, you wanted to add an environment variable to your deployment when deploying to a dev environment, but wanted to add a different environment variable when deploying to test. Rather than rewrite the same deployment resource twice, you can simply write two small kustomize.yaml files.

The dev kustomize.yaml file would look like this:

bases:
  - ../../base
patches:
  - dev_env.yaml

Bases refers to the deployment file that you are patching. Patches refers to the file that contains your desired patch. So, the “dev_env.yaml” patch could look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    spec:
      containers:
        - name: nginx
          env:
            name: ENVIRONMENT
            value: dev

When you run kustomize against this kustomization.yaml file, it will merge this environment variable into the base deployment. Below is the command used to perform this merge:

kustomize build $PATH_TO_KUSTOMIZATION_FILE

You could follow the same idea for the test kustomize.yaml file and additional environments where you plan to deploy.

With a brief intro to Kustomize out of the way, let’s see how it can help make changes to any resource we want in a Helm chart.

Using Kustomize as a Helm Post-Renderer

Imagine that you want to install an NGINX Helm chart. During installation, you want to add a specific environment variable, and you want to change the service type to NodePort, but the Helm chart doesn’t allow you to set these through values. This situation is a prime use case for a Kustomize post-renderer. Let’s step through how you can achieve this. The first step is to write your Kustomize patches.

Writing Kustomize Patches

Kustomize patches will allow you to patch your desired configuration over the rendered Helm templates. Adding the desired deployment labels is similar to before, by creating a dev_env.yaml file and specifying the portion of the deployment you want to patch:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: thisCanBeAnything
spec:
  template:
    spec:
      containers:
        - name: nginx
          env:
            - name: ENVIRONMENT
              value: dev

We’ll get to why the name looks strange in a moment.

Next, you can create the service patch to change the type to NodePort in a file called nodeport.yaml.

apiVersion: v1
kind: Service
metadata:
  name: thisCanBeAnything
spec:
  type: NodePort

Now that the patches are done, let’s create the kustomization.yaml file.

Creating the kustomization.yaml File

The kustomization.yaml file applies your patches to the rendered Helm templates. Here’s an example:

resources:
  - all.yaml
patches:
  - path: dev_env.yaml
    target:
      kind: Deployment
      name: "*"
  - path: nodeport.yaml
    target:
      kind: Service
      name: "*"

Let’s break this file down. Under “resources”, you’ll see a single file called all.yaml. This file will contain the rendered Helm templates before the post renderer executes. The “patches” field lists each of your patch files. You’ll notice that each patch defines a target, which tells Kustomize to apply it to any deployment or service, regardless of its name. That’s why the patch files used the name “thisCanBeAnything”, since the name in that patch won’t matter.

A Helm post-renderer needs to be an executable, which we haven’t created yet. Let’s create an executable in the next step to tie everything together.

Creating the Post-Renderer Executable

For this step, we’ll create a bash script that Helm can use to execute the post renderer. Below is an executable called hook.yaml (borrowed from this repository):

#!/bin/bash

cat <&0 > all.yaml

kustomize build . && rm all.yaml

This post-renderer begins by redirecting the rendered Helm templates to “all.yaml”. Then, the post-renderer runs kustomize build to apply the patches and removes the “all.yaml” file. Be sure to give this file executable permission by running this command:

chmod u+x hook.yaml

By now, you should have the following files:

dev_env.yaml
hook.sh
kustomization.yaml
nodeport.yaml

Let’s move on to the exciting part – seeing this post-renderer in action!

Running the Post-Renderer

To run a post-renderer with Helm, you need to pass the post-renderer flag and set it to your executable name (hook.yaml, in this case). I have a sample chart called “nginx” in my GitHub repository that we’ll apply the post-renderer to.

You must be in the same directory as your kustomization.yaml file to run the post-renderer. Once you’re there, run this command:

helm install my-nginx $PATH_TO_CHART --post-renderer=./hook.sh

The output will look nothing out of the ordinary, but you can be sure that the post renderer was applied correctly by observing the manifest that Helm created with the helm get manifest command:

→ helm get manifest my-nginx
apiVersion: v1
kind: Service
...
spec:
...
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
...
spec:
...
  template:
...
    spec:
      containers:
      - env:
        - name: ENVIRONMENT
          value: dev
        image: docker.io/nginx:1.19
        name: nginx

Not bad! Using a post renderer, you modified the fields that the “nginx” chart did not allow you to change with Helm values!

A Note about Post-Renderers

While the ability to create post-renderers is an interesting feature, I must call out one thing. The post-renderer must be run in every subsequent upgrade if you want to apply the patches in later revisions. This limitation means that the renderer needs to be easily sharable among users, and users need to remember to apply it each time they upgrade the release. If a post renderer is necessary, try to incorporate the Helm commands in a pipeline or automated process so you and other users don’t forget to apply the post renderer when you install or upgrade.

Thanks for Reading!

Helm post-renderers allow you to easily patch rendered Helm templates when the Helm chart you want to install doesn’t allow you to configure key fields. For more information on post-renderers, see the Helm documentation at https://helm.sh/docs/topics/advanced/.

Austin Dewey

Austin Dewey is a DevOps engineer focused on delivering a streamlined developer experience on cloud and container technologies. Austin started his career with Red Hat’s consulting organization, where he helped drive success at many different Fortune 500 companies by automating deployments on Red Hat’s Kubernetes-based PaaS, OpenShift Container Platform. Currently, Austin works at fintech startup Prime Trust, building automation to scale financial infrastructure and support developers on Kubernetes and AWS. Austin is the author of "Learn Helm", a book focused on packaging and delivering applications to Kubernetes, and he enjoys writing about open source technologies at his blog in his free time, austindewey.com.

Leave a Reply