Containerized KRM Functions

Guide to writing containerized KRM functions for use as Kustomize plugins

Authoring Containerized KRM Functions

A containerized KRM Function is any container whose entrypoint accepts a ResourceList as input on stdin and emits a ResourceList as output on stdout, in accordance with the KRM Functions Specification.

Configuration

Containerized KRM Function plugins are referenced directly by the metadata of the KRM object used to configure them.

For example, the plugin configuration might look like:

apiVersion: someteam.example.com/v1
kind: ChartInflator
metadata:
  name: notImportantHere
  annotations:
    config.kubernetes.io/function: |
      container:
        image: example.docker.com/my-functions/chart-inflator:0.1.6      
spec:
  chartName: minecraft

Guided example

This is a (no reading allowed!) 60 second copy/paste guided example.

This demo writes and uses a somewhat ridiculous containerized plugin (written in Go) that follows the KRM Function Specification and generates a ConfigMap.

Prerequisites:

  • linux or osx
  • curl
  • bash
  • docker
  • Go 1.16

Make a place to work

DEMO=$(mktemp -d)

Install kustomize

Per the instructions:

curl -s "https://raw.githubusercontent.com/\
kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
mkdir -p $DEMO/bin
mv kustomize $DEMO/bin

Create a kustomization

Make a kustomization directory to hold all your config:

MYAPP=$DEMO/myapp
mkdir -p $MYAPP

Make a service config:

# $MYAPP/service.yaml
kind: Service
apiVersion: v1
metadata:
  name: the-service
spec:
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 8666
    targetPort: 8080

Now make a config file for the plugin you’re about to write.

This config file is just another Kubernetes resource object. The config.kubernetes.io/function annotation is used to find the docker container that implements the ValueAnnotator type.

# $MYAPP/annotator.yaml
apiVersion: transformers.example.co/v1
kind: ValueAnnotator
metadata:
  name: notImportantHere
  annotations:
    config.kubernetes.io/function: |
      container:
        image: example.docker.com/my-functions/valueannotator:1.0.0      
value: 'important-data'

Finally, make a kustomization file referencing all of the above:

# $MYAPP/kustomization.yaml
resources:
- service.yaml
transformers:
- annotator.yaml

Review the files

ls -C1 $MYAPP

Make a home for plugins

Since Kustomize accesses your code indirectly through the container, you can develop containerized plugins anywhere you’d like within your Go path.

Let’s create a new directory for our code:

mkdir $GOPATH/src/kustomize-plugin-demo

Create the plugin

First initialize the plugin directory with go mod:

cd $GOPATH/src/kustomize-plugin-demo

Create the main.go with the demo code:

// $GOPATH/src/kustomize-plugin-demo/main.go
package main

import (
  "os"

  "sigs.k8s.io/kustomize/kyaml/fn/framework"
  "sigs.k8s.io/kustomize/kyaml/fn/framework/command"
  "sigs.k8s.io/kustomize/kyaml/kio"
  "sigs.k8s.io/kustomize/kyaml/yaml"
)

type ValueAnnotator struct {
  Value string `yaml:"value" json:"value"`
}

func main() {
  config := new(ValueAnnotator)
  fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
    for i := range items {
      err := items[i].PipeE(yaml.SetAnnotation("custom.io/the-value", config.Value))
      if err != nil {
        return nil, err
      }
    }
    return items, nil
  }
  p := framework.SimpleProcessor{Config: config, Filter: kio.FilterFunc(fn)}
  cmd := command.Build(p, command.StandaloneDisabled, false)
  command.AddGenerateDockerfile(cmd)
  if err := cmd.Execute(); err != nil {
    os.Exit(1)
  }
}

Create the go module

go mod init
go mod tidy

Generate the dockerfile

Because our program uses command.AddGenerateDockerfile(cmd), go can generate a default Dockerfile for us:

go run main.go gen .

Build the container

docker build . -t example.docker.com/my-functions/valueannotator:1.0.0

Note that since this is a quick local-only demo, we do not need to push the image, and the tag doesn’t need to refer to a real docker registry.

Build your app

$DEMO/bin/kustomize build --enable-alpha-plugins $MYAPP

You should see the “important-data” added to the Service’s annotations.

Clean up

rm -r $GOPATH/src/kustomize-plugin-demo