arrow-rightgithublinkedinscroll-to-topzig-zag

Kubernetes Kustomize - JsonPatches6902 overview

Last Updated On

Table of contents

Introduction

Kustomize is a supplement and very useful tool for kuberentes that is responsible for template management. One of the core functionalities is to create overriding rules on top of an existing template without changing the latter. While official documentation provides a great overview of the basic features such as adding namespace, prefixes, annotations, labels, it lacks an explanation of common scenarios where the need to add, remove or replace values of a base template. More precisely, I'll be focusing on JsonPatches6902 and how to create them in, native for kubernetes, yaml format.

What is JsonPatches6902?

Let's start with the number first. There is an RFC6902 standard defines how to apply JSON patches. The original purpose of this standard is to eliminate bandwidth and CPU waste while processing large resources for HTTP requests: instead of sending entire JSON object (resource) via HTTP PUT/POST method, it allows to send only the modified part (patch).

According to the standard, there are 6 types of operations that can be performed on an object: add, remove, replace, move, copy, test. We will take a look at the first three since they have a more applicable sense to our use case. From a syntactical point of view, each operation must have op member indicating one of the mentioned types, e.g. add:

{ "op": "add" }

In addition to that, operations must include a path key. The value of the path is a location within the JSON document need to be modified and standardized by RFC6901:

{ "op": "add", "path": "/a/b" }

The other key-value pairs of operation depend on the particular operation being performed.

Example with gitea

After we had a brief overview of the official standard, let's switch to kubernetes and see how this functionality can be used within kustomize.

Sometimes it's best to explain the concept by example and that's what we're going to do. Out test subject will be represented by gitea - self-hosted git service. This docker image is an excellent candidate for the scenario as it does support multiple environment variables and have a front-facing UI interface allows to see the changes applied through the patches.

Project structure

As it's in the case for most of my articles, the full project can be found on GitHub. The structure can be represented in the following diagram:

|-- base
    gitea-pod.yaml
    gitea-service.yaml
    kustomization.yaml
|-- overlays
    |-- add
        ...
        kustomization.yaml
    |-- remove
        ...
        kustomization.yaml
    |-- replace
        ...
        kustomization.yaml

At the simplest level, the codebase contains gitea pod and gitea service where both represent the base layer. There are two ways of how to deploy this configuration to, let's say, minikube environment:

  • If kustomize package is installed: kustomize build ./base | kubectl apply -f -
  • if kubernetes version is higher than 1.14: kubectl apply -k ./base

The base might not very useful in the beginning since the service is only reachable within kubernetes environment. In the next section, we will see how patches can help to expose the service so we can see a nice UI in the browser.

Going further through the overview, we have "overlays" that modify a common base. Each overlay is represented by a specific operation such as add, remove or replace which customizes and applies patches on top of the base while leaving the latter untouched. Later we will take a look into details of each operation.

To remove deployed configuration at any point of time run:

kubectl delete pod,svc --selector=app=gitea

Kustomization file overview

kustomize can work properly only when kustomization.yaml is present in the target folder. Here's an example from the codebase:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
patchesJson6902:
- target:
    version: v1 # apiVersion
    kind: Pod
    name: gitea
  path: gitea-pod-patch-image-version.yaml

As you may notice, each key under the target uses exactly the same value from the resource it's being applied to.

However, for some type of kubernetes primitives with the prefix before the slash (which called a "group") in apiVersion field it's required to specify additional property called group. Here's an example of StatefulSet:

#...
patchesJson6902:
- target:
    group: "apps" 
    version: v1 # apiVersion
    kind: StatefulSet
    name: example-statefulset
  path: my-statefulset-patch.yaml

The full list of such objects and groups can be found in the article: Which Kubernetes apiVersion Should I Use?

Operations

Add

To apply this configuration, run:

kubectl apply -k ./overlays/add

When kubernetes finishes to prepare the environment you should be able to see gitea homepage by navigating to http://<minikube-url>:30000 (usually, but not always, minikube IP is 192.168.99.100). Thanks to one of the applied patches which turned regular kuberetes service into a NodePort service, it became possible to reach the web-page from the browser. However, there are different types of add operations and let's look at them at a closer level.

Add a member to an object

The patch illustrating this functionality is gitea-pod-http-app-name-patch.yaml. It changes the default gitea website title to the value of APP_NAME environment variable:

- op: add
  path: "/spec/containers/0/env"
  value:
    - name: APP_NAME
      value: ABC Inc. Private Git Repository

The path field specifies a place in an object where to insert the required property:

#...
spec:
  containers:
  - name: gitea
    image: gitea/gitea:1.8
#...

Since containers is an array, we need to specify the item of the array needed to be updated. We only have one item and that's why the number is 0. Alternatively, we can indicate an exact index of an element or use - which represents the last element of the array.

Patches applied to a service, work in exactly the same way:

- op: add
  path: "/spec/type"
  value: "NodePort"
- op: add
  path: "/spec/ports/0/nodePort"
  value: 30000

By turning simple gitea-service.yaml:

kind: Service
apiVersion: v1
metadata:
  name: gitea-service
  labels:
    app: gitea
spec:
  selector:
    app: gitea
  ports:
  - name: ui-port
    port: 3000

into NodePort:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: gitea
  name: gitea-service
spec:
  type: NodePort
  selector:
    app: gitea
  ports:
  - name: ui-port
    nodePort: 30000
    port: 3000

Append to a List

The gitea-pod-patch-sidecar-container.yaml patch appends sidecar container to the containers list in the pod:

- op: add
  path: "/spec/containers/-"
  value:
    name: sidecar
    image: busybox
    args:
    - sleep
    - "3600"

Which results in the following output:

#...
containers:
  - name: gitea
    image: gitea/gitea:1.8
    env:
    - name: APP_NAME
      value: ABC Inc. Private Git Repository
    ports:
    - containerPort: 3000
  - name: sidecar
    image: busybox
      args:
    - sleep
    - "3600"
#...

The sidecar container is not very useful in this particular example, but it demonstrates very well how to append an object to a list.

Replace

Replace allows changing a value on specified path. This particular patch changes the image version of the gitea. To apply the patch:

kubectl apply -k ./overlays/replace

gitea-service-patch-remove-port-name.yaml:

- op: replace
  path: "/spec/containers/0/image"
  value: "gitea/gitea:1.7"

Produced result:

#...
containers:
  - name: gitea
    image: gitea/gitea:1.7 # <-- Replaced version

Remove

As a name suggests, removes a key on a specified path. To apply the configuration, run

kubectl apply -k ./overlays/remove

gitea-service-patch-remove-port-name.yaml:

- op: remove
  path: "/spec/ports/0/name"

This example removes name key from the ports item within a service:

spec:
  selector:
    app: gitea
  ports:
  - port: 3000
      # name: ui-port <-- removed

Summary

In this project, we used the name of the operations such as add, replace or remove as overlays. This allows to highlight the syntactical part in great detail, however, in the real world scenario, you may consider using more descriptive name relative such as development, production or staging for overlays.