Architecture

From their site:

  • Kustomize helps customizing config files in a template free way.
  • Kustomize provides a number of handy methods like generators to make customization easier.
  • Kustomize uses patches to introduce environment specific changes on an already existing standard config file without disturbing it.

It’s a perfect tool to create environment based customizations to your k8s deployments. kustomize uses a concept of bases and overlays, where you define a base and then you create overlays which customize the configuration depending on your environment. There is a pretty cool diagram in their github:

overlay.jpg

And here is a simple directory hierarchy:

~/someApp
├── base
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── development
    │   ├── cpu_count.yaml
    │   ├── kustomization.yaml
    │   └── replica_count.yaml
    └── production
        ├── cpu_count.yaml
        ├── kustomization.yaml
        └── replica_count.yaml

It’s also a perfect tool to be added into your CI/CD pipeline. From Workflow for off the shelf applications, here is a nice overview:

ots.jpg

So let’s run through a couple of examples to see how it works. As a starting point, I created the following directory structure:

> mkdir -p {overlays/{dev,prod},base}
> touch {base/kustomization.yaml,overlays/{dev,prod}/kustomization.yaml}
> tree
.
├── base
│   └── kustomization.yaml
└── overlays
    ├── dev
    │   └── kustomization.yaml
    └── prod
        └── kustomization.yaml

4 directories, 3 files

Container Images

First let’s try out modifying the container image version (this example is covered in images). As an example let’s create a simple deployment manifest:

> cat base/deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
  annotations:
    app: nginx
spec:
  template:
    spec:
      containers:
      - name: nginxapp
        image: nginx:1.7.9

And here is a simple kustomization to modify the tag:

> cat base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deploy.yaml

images:
- name: nginx
  newTag: 1.8.0

Now to see how it gets parsed, we can use kubectl kustomize:

> k kustomize base           
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app: nginx
  name: the-deployment
spec:
  template:
    spec:
      containers:
      - image: nginx:1.8.0
        name: nginxapp

To actually apply it, we can run kubectl apply -k (vs kubectl apply -f):

k apply -k base
deployment.apps/the-deployment created

That was pretty easy.

Patching

kustomize has two approaches to patching files: strategic merge patch and json 6902 patch.

Strategic Merge Patch

This is useful if you are making a lot of changes, so you can just provide a YAML file that looks like a k8s manifest file with all the resources you want to add/change. From the Patching multiple resources at once example, let’s inject a side car container into our deployment in our dev overlay. First let’s add a kustomization.yaml file into the dev overlay:

> cat overlays/dev/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

patches:
- path: deploy-patch.yaml

And now let’s add the patch file to specify the container we want to add:

> cat overlays/dev/deploy-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
spec:
  template:
    spec:
      containers:
        - name: istio-proxy
          image: docker.io/istio/proxyv2
          args:
          - proxy
          - sidecar

And here is how the parsed manifest will end up looking like:

> k kustomize overlays/dev           
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app: nginx
  name: the-deployment
spec:
  template:
    spec:
      containers:
      - args:
        - proxy
        - sidecar
        image: docker.io/istio/proxyv2
        name: istio-proxy
      - image: nginx:1.8.0
        name: nginxapp

That looks good.

JSON 6902 Patch

This is good for adding/modifying one field at a time (also the parent path has to exist, so it doesn’t create sub fields, for more information check out this github feature request). Let’s say I want add a new annotation and to a serviceaccount, we could create the following patch:

> cat overlays/dev/deploy-more-patch.yaml
- op: add
  path: /metadata/annotations/app.io~1owner
  value: "me"
- op: add
  path: /spec/template/spec/serviceAccountName
  value: app

Notice that I had to use ~1 to specify a forward slash / (this is covered in this github issue). And here is update kustomization.yaml to include the new patch file. Notice that I also added a target to specify which resource to modify:

> cat overlays/dev/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

patches:
- path: deploy-patch.yaml
- path: deploy-more-patch.yaml
  target:
    kind: Deployment

And now here is the resulted manifest:

> k kustomize overlays/dev                
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app: nginx
    app.io/owner: me
  name: the-deployment
spec:
  template:
    spec:
      containers:
      - args:
        - proxy
        - sidecar
        image: docker.io/istio/proxyv2
        name: istio-proxy
      - image: nginx:1.8.0
        name: nginxapp
      serviceAccountName: app

yay, that worked out as well.

ConfigGenerator

Let’s cover one more example, the configMapGenerator, this supports multiple approaches as well. I will cover two.

Using Literals to Specify Configs as Strings

There is also a nice example at ConfigMap generation and rolling updates. Let’s say I create the following in my base:

> cat base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deploy.yaml

images:
- name: nginx
  newTag: 1.8.0

configMapGenerator:
- name: config
  literals:
    - common="Common Variable"

Now at the overlay kustomization:

> cat overlays/dev/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

patches:
- path: deploy-patch.yaml
- path: deploy-more-patch.yaml
  target:
    kind: Deployment

configMapGenerator:
- name: config
  behavior: merge
  literals:
    - custom="My Custom Config"

Now I can modify the the deployment in the base to add the common configMap:

> cat base/deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
  annotations:
    app: nginx
spec:
  template:
    spec:
      containers:
      - name: nginxapp
        image: nginx:1.7.9
        env:
        - name: COMMON
          valueFrom:
            configMapKeyRef:
              name: config
              key: common

And then I added a patch in the overlay to append the environment variable (since I my sidecar proxy patch from before added up adding the container first, I had to basically append it to the second container of my deployment… 0 is the first and 1 is the second):

> cat overlays/dev/deploy-more-patch.yaml 
- op: add
  path: /metadata/annotations/app.io~1owner
  value: "me"
- op: add
  path: /spec/template/spec/serviceAccountName
  value: app
- op: add
  path: /spec/template/spec/containers/1/env/-
  value:
      name: CUSTOM
      valueFrom:
        configMapKeyRef:
          name: config
          key: custom

Parsing the config, here is what it creates:

> k kustomize overlays/dev
apiVersion: v1
data:
  common: Common Variable
  custom: My Custom Config
kind: ConfigMap
metadata:
  name: config-k96cb96f75
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app: nginx
    app.io/owner: me
  name: the-deployment
spec:
  template:
    spec:
      containers:
      - args:
        - proxy
        - sidecar
        image: docker.io/istio/proxyv2
        name: istio-proxy
      - env:
        - name: COMMON
          valueFrom:
            configMapKeyRef:
              key: common
              name: config-k96cb96f75
        - name: CUSTOM
          valueFrom:
            configMapKeyRef:
              key: custom
              name: config-k96cb96f75
        image: nginx:1.8.0
        name: nginxapp
      serviceAccountName: app

We can see it creates a dynamic configMap name and adds it to our container accordingly… that is pretty cool :)

Using Files to Specify Configs

Another approach is to provide configuration files in the configMaps. Each file ends up being a new data entry in the configMap. Another cool example at Demo: combining config data from devops and developers. Let’s add it to our prod overlay. First let’s modify our base and add a new configMapGenerator:

> cat base/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deploy.yaml

images:
- name: nginx
  newTag: 1.8.0

configMapGenerator:
- name: config
  literals:
    - common="Common Variable"
- name: file-config
  files:
    - common.properties

Now let’s add it to our basedeployment as a volume, so each entry is added as a file under the config directory:

> cat base/deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
  annotations:
    app: nginx
spec:
  template:
    spec:
      containers:
      - name: nginxapp
        image: nginx:1.7.9
        env:
        - name: COMMON
          valueFrom:
            configMapKeyRef:
              name: config
              key: common
        volumeMounts:
        - name: config-volume
          mountPath: /etc/config
      volumes:
      - name: config-volume
        configMap:
          name: file-config

Now in the overlay, let’s add to our configMapGenerator:

> cat overlays/prod/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

configMapGenerator:
- name: file-config
  behavior: merge
  files:
  - custom.properties

And now generating the config here is how it looks like:

> k kustomize overlays/prod 
apiVersion: v1
data:
  common: Common Variable
kind: ConfigMap
metadata:
  name: config-mdd5k4b6c5
---
apiVersion: v1
data:
  common.properties: |-
    common=common_config
    other_common=common2_config
  custom.properties: |-
    custom1=custom1_config
    custom2=custom2_config
kind: ConfigMap
metadata:
  name: file-config-965d47772g
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app: nginx
  name: the-deployment
spec:
  template:
    spec:
      containers:
      - env:
        - name: COMMON
          valueFrom:
            configMapKeyRef:
              key: common
              name: config-mdd5k4b6c5
        image: nginx:1.8.0
        name: nginxapp
        volumeMounts:
        - mountPath: /etc/config
          name: config-volume
      volumes:
      - configMap:
          name: file-config-965d47772g
        name: config-volume

Now the container can parse all the files specified under the /etc/config directory… all in all kustomize is pretty cool :) There are actually a lot more that it does, and this was just a quick overview of some of the functions.