Photo by Alejandro Escamilla on Unsplash

It’s wonderful that we could use Kubernetes custom controller to work with the core resources. However those core resources are not editable. In many occasions, you would want to introduce your own resources which you would have full control of it. CRD (Custom Resource Definitions)[1] is exactly designed for that.

However, there are a lot of boilerplate codes for each CRD resource created. To avoid this situation, we could use k8s.io/code-generator to auto-generate all the informer, listers etc that we need for a CRD to work.

Note[2]: kubernetes/code-generator is synced from k8s.io/code-generator . Code changes are made in that location, merged into k8s.io/kubernetes and later synced back to kubernetes/code-generator .

In this article, we will focus on how we could use the generator from scratch.

Firstly let’s assume we want to create a CRD that belongs to group foo.com and there is a type called “HelloType” with a “message” field in the spec:

# Definition

---

apiVersion: apiextensions.k8s.io/v1beta1

kind: CustomResourceDefinition

metadata:

name: hellotypes.foo.com

spec:

group: foo.com

version: v1

scope: Namespaced

names:

kind: HelloType

shortNames: ht

plural: hellotypes

singular: hellotype # HelloType

---

apiVersion: foo.com/v1

kind: HelloType

metadata:

name: superman-hello

spec:

message: hello world

To generate this CRD resource codes, there are two steps involved.

Write the type definition codes with proper code generator tags Run the code generator to automatically create the client codes which include clientset, informers, listers for your customer resource

We will be using latest go 1.11.2 for the walkthrough.

CRD type definition

Firstly let’s create a repo called github.com/superman/demo . The skeleton files we will be creating are laid out as follows:

pkg/

├── apis

└── foo

└── v1

├── doc.go

├── register.go

└── types.go

foo is the new resource group we are defining. All types under it belong to foo.com .

If you are writing resource version less than 1.0.0, all the codes under v1 could be put right under foo directory. Any other versions, following the convention, create a separate directory such as v1 , v1beta1 , v2 under foo . In this example, we will be creating version 1 for the custom resource.

foo/v1/doc.go

This file provide controls of the global tags that will apply to every type in the same version with default setting. Tags reside in comment blocks. Individual types can choose to opt-out the default setting or have different tags. That is referred as “local tags”.

The tags compose of two parts, the function name and the value to pass in normally. For example in +k8s:deepcopy-gen=package , deepcopy-gen is the function to call for generating the deep copies codes for types. package denotes applying the function to the whole v1 package in this case. The deepcopy-gen function and other similar generator functions can be found in “k8s.io/code-generator/cmd/”. By looking up the comments in the main.go files under those directories, you can find the explanations for the related tags.

If you are intrigued on how the codes are actually generated, you could look further into “k8s.io/gengo”.

The other two tags in the file indicate generating defaulter for struct TypeMeta and set the group name as “foo.com”.

foo/v1/types.go

In this file, we create the custom type “HelloType”. We also define the custom Status and Spec . Any custom type must also have a plural struct. In this case, we have “HelloTypeList”.

For tags we are using in this file:

+genclient means generating API client for the type.

+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object means when generating deepcopy for the type, use runtime.Object interface.

+optional indicates the field is optional. The data can be empty for the field.

foo/v1/register.go

The register.go file contains the functions registering the new types we just created to the schema so the API server can recognise them.

Client Code generation

Now we have all the files we need for code generation. There are a few options for this process with go 1.11+.

Option 1: Without Go Modules

All codes under $GOPATH/src won’t be using Go Modules by default. So assuming you put your codes in $GOPATH/src/github.com/superman/demo .

Then just run below to get the generator and the lib for your codes:

$ go get k8s.io/code-generator $ go get k8s.io/apimachinery

Go into $GOPATH/src/k8s.io/code-generator , run below command:

$ ./generate-groups.sh all \

" github.com/superman/demo /pkg/client" \

" github.com/superman/demo /pkg/apis" \

foo:v1

Option 2: Using Go Modules

Go modules got introduced in go 1.11. It provides an intuitive way handling dependancies. You can put your repositories anywhere including outside the GOPATH .

Currently code-generator doesn’t deal with go modules unfortunately. Therefore it’s a bit tricky to get that working.

One solution is to use docker. Below is an example of the Dockerfile and the shell script. Again, assuming your go module name is github.com/superman/demo .

After the code generation is completed using one of the above options, your pkg folder structure will look something like:

pkg/

├── apis

│ └── foo

│ ├── register.go

│ └── v1

└── client

├── clientset

│ └── versioned

├── informers

│ └── externalversions

└── listers

└── foo

The newly created client folder contains all the generated codes, also the deepcopy codes for the types will reside in v1 next to the types.

To finish, you could use the generated clientset, informers for the custom controller like normally you would’ve done with other core resources.

Resources:

Reference:

Disclaimer: The views expressed here are solely those of the author in his private capacity and do not in any way represent the views of any organisation.