Kustomize is my favorite tool to manage the bunch of YAML we need to manage operating our clusters and applications. Since it is integrated in kubectl and oc, it is possible to apply a config to your cluster using one command. Or if you like Gitops, ArgoCD can sync your kustomize layer(s) stored in git automatically.

A project usually consists of a base layer and a few subsidiary layers that are customized versions of the base. Hence its name. There are many so called transformations supported, to customize things such as image tag, namespace, … So building a deploy process based on Kustomize is also easy (change image tag).

Each layer contains a config file Kustomization.yaml and kustomize is the binary you can use to manipulate these, build configs, apply, … As mentioned before, also kubectl and oc have support: apply, diff, …

The problem

We manage OKD infra using Kustomize and one of the many ClusterOperators allows you to manage MachineConfig resources. This allows you to specify for example systemd service units, enable and start them.

Or any system files for that matter. Using the operator assures us system config is identical on all nodes of the same type without using any other config management tool such as Ansible.

Let’s build a simple example MachineConfig:

apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
  labels:
    machineconfiguration.openshift.io/role: worker
  name: myconfig
spec:
  config:
    ignition:
      version: 3.2.0
    storage:
      files:
      - contents:
          source: data:text/plain;charset=utf-8;base64,Y29uZmlnOiB2YWx1ZQ==
        filesystem: root
        mode: 420
        path: /etc/myconfig.yaml

If you would push this to Openshift, the Machine operator will update all your worker nodes with a new config file /etc/myconfig.yaml. As you can see the file contents are base64 encoded. Feel free to ‘decrypt’.

Now let’s write a few words about GitOps. One of the promises is: easy accountability of actions using git history, it should be obvious what has changed using git diff. I don’t know about you but a change in a base64 blob is not obvious to me. I have to decode the ‘from’ and ’to’ and diff those to make sense of what has changed. Big fail if you ask me.

I want to add that my problem also had a duplication: we want the same config for masters and workers. But to keep it simple, we only consider the base64 problem.

Solution: Go templating

I have quite some experience with Helm and the Go templating that is used there. So I immediately had the idea to use a simple template to accomplish this.

Some might call this an anti-pattern for Kustomize since it sells as a simpler to use tool as Helm. And here, we make it a bit more complicated again. Might be the reason why I could not find an existing plugin. Luckily it is actually pretty simple to do yourself.

The Kustomize project

This is how our project looks like. A folder that you can name to your liking and probably store in git. Let’s study the file contents.

Kustomization.yaml

The kustomize config file:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generators:
- generateMachineConfigs.yaml

We use a generator extension defined in generateMachineConfigs.yaml that will yield a valid YAML resource, in our case of type MachineConfig.

generateMachineConfigs.yaml

apiVersion: kustomize.plugins.dev/v1
kind: GoTemplate
metadata:
  name: goTemplate
template: machineconfig-config.yaml.template
config: generateMC-config.yaml

This defines the generator plugin and its inputs: the template and the config file. Kustomize will look for your plugin using apiVersion and kind, as explained later.

machineconfig-config.yaml.template

Here you finally see a Go template. We use the b64enc internal function to encode the configuration read in from the config file generateMC-config.yaml.

apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
  labels:
    machineconfiguration.openshift.io/role: worker
  name: myconfig
spec:
  config:
    ignition:
      version: 3.2.0
    storage:
      files:
      - contents:
          source: data:text/plain;charset=utf-8;base64,{{ $.config | b64enc }}
        filesystem: root
        mode: 420
        path: /etc/myconfig.yaml

The template config, revealing our very exciting config we want on all our worker machines:

config: |
    config: value

Tying it all together: the plugin, location and a kustomize wrapper

Plugin

The plugin is a very simple bash script:

#!/bin/bash

template=$(cat "$1" | yq e .template -)
config=$(cat "$1" | yq e .config -)

gucci -f $config $template

The plugin processes the template using gucci: https://github.com/noqcks/gucci yq is another dependency as you can see. Kustomize includes the generator definition file as first argument of the plugin.

Wrapper and script location

GoTemplate:

function k {
  XDG_CONFIG_HOME="$PWD/PLUGINS/" \
  kustomize build --enable-alpha-plugins --stack-trace $1
}

This wrapper can be used to enrich kustomize with your plugin. If you source this function in your shell first, you can use simply the command k to build a config using kustomize and your plugin.

The script should have execution permissions and have as path relative to the working dir while sourcing the wrapper:

PLUGINS/kustomize/plugin/kustomize.plugins.dev/v1/gotemplate/GoTemplate