Generating CRDs with Kubebuilder and k8s.io/code-generator
This guide shows how to combine two toolchains to build CustomResourceDefinitions (CRDs) and typed clients:
- Use Kubebuilder to scaffold API types and CRD/manifests
- Use k8s.io/code-generator to produce clientsets, listers, and informers
This workflow is useful when you don’t need a controller/operator but still want Kubernetes-style APIs and typed clients.
Prerequisites
- Go 1.18+
- Kubebuilder installed
- A working Kubernetes cluster or kubeconfig for applying CRDs
1. Initialize a project with Kubebuilder
MODULE=example.com/gb-controller
go mod init "$MODULE"
kubebuilder init --domain example.com
kubebuilder edit --multigroup=true
You should see a structure similar to:
.
├── Dockerfile
├── Makefile
├── PROJECT
├── bin
│ └── manager
├── config
│ ├── certmanager
│ ├── default
│ ├── manager
│ ├── prometheus
│ ├── rbac
│ └── webhook
├── hack
│ └── boilerplate.go.txt
└── main.go
2. Create API types and manifests
Scaffold the API group/version and kind without a controller:
kubebuilder create api --group webapp --version v1 --kind Guestbook
# Create Resource: y
# Create Controller: n
New files will be created under apis and config:
.
├── apis
│ └── webapp
│ └── v1
│ ├── groupversion_info.go
│ ├── guestbook_types.go
│ └── zz_generated.deepcopy.go
└── config
├── crd
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── patches
│ ├── cainjection_in_guestbooks.yaml
│ └── webhook_in_guestbooks.yaml
├── rbac
│ ├── guestbook_editor_role.yaml
│ ├── guestbook_viewer_role.yaml
└── samples
└── webapp_v1_guestbook.yaml
Add RBAC markers for aggregated RBAC generasion:
Create apis/webapp/v1/rbac.go:
// +kubebuilder:rbac:groups=webapp.example.com,resources=guestbooks,verbs=list;watch;get;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.example.com,resources=guestbooks/status,verbs=get;patch;update
package v1
Generate CRD and RBAC manifests:
make manifests
You should see:
config
├── crd
│ └── bases
│ └── webapp.example.com_guestbooks.yaml
└── rbac
└── role.yaml
Note: When you edit apis/webapp/v1/guestbook_types.go, regenerate code and manifests:
make generate && make manifests
3. Produce clients, listers, and informers with code-generator
3.1 Create codegen helper scripts
Add the folllowing files under hack:
./hack
├── tools.go
├── update-codegen.sh
└── verify-codegen.sh
hack/tools.go:
//go:build tools
// +build tools
package tools
import (
_ "k8s.io/code-generator"
)
hack/update-codegen.sh (adjust variables to match your module and package layout):
#!/usr/bin/env bash
set -euo pipefail
# Go module path (same value used in: go mod init <module>)
MODULE_PATH=example.com/gb-controller
# API package containing your types
API_PKG=apis
# Where to write generated client code (import path will be ${MODULE_PATH}/${OUT_PKG})
OUT_PKG=generated/webapp
# Space-separated list of group:version pairs to generate for
gv_list=("webapp:v1")
GVs=$(IFS=' '; echo "${gv_list[*]}")
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${ROOT_DIR}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
# Ensure the generator is runnable
chmod +x "${CODEGEN_PKG}/generate-groups.sh"
# Write generated code next to the repo to avoid touching GOPATH
bash "${CODEGEN_PKG}/generate-groups.sh" "client,lister,informer" \
"${MODULE_PATH}/${OUT_PKG}" "${MODULE_PATH}/${API_PKG}" \
${GVs} \
--go-header-file "${ROOT_DIR}/hack/boilerplate.go.txt" \
--output-base "${ROOT_DIR}"
hack/verify-codegen.sh can simply check for a clean git tree after running update-codegen.sh.
3.2 Add code-generator and align dependencies
Choose a Kubernetes release and use the same version for client-go, apimachinery, and code-generator. For example:
K8S_VERSION=v0.18.5
go get k8s.io/code-generator@${K8S_VERSION}
go get k8s.io/client-go@${K8S_VERSION}
go get k8s.io/apimachinery@${K8S_VERSION}
go get sigs.k8s.io/controller-runtime@v0.6.0
go mod vendor
If needed, ensure generate-groups.sh is executable inside vendor:
chmod +x vendor/k8s.io/code-generator/generate-groups.sh
3.3 Annotate types and add supporting files
In apis/webapp/v1/guestbook_types.go, add the genclient marker to the top-level type:
// +genclient
// +kubebuilder:object:root=true
// Guestbook is the Schema for the guestbooks API
type Guestbook struct {
// ... existing fields
}
Create apis/webapp/v1/doc.go with the group name:
// +groupName=webapp.example.com
package v1
Create apis/webapp/v1/register.go to support client code:
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeGroupVersion is used to register these objects with a scheme.
var SchemeGroupVersion = GroupVersion
// Resource returns a GroupResource for the given resource name.
func Resource(res string) schema.GroupResource {
return SchemeGroupVersion.WithResource(res).GroupResource()
}
3.4 Generate clients, listers, and informers
Run the codegen script:
./hack/update-codegen.sh
The generated code will appear under your module path, for example:
example.com
└── gb-controller
└── generated
└── webapp
├── clientset
├── informers
└── listers
You may move the generated directory too your preferred location (e.g., project root or pkg/generated) and update imports accordingly.
4. Try it out
Apply the CRD and a sample instance:
kubectl apply -f config/crd/bases/webapp.example.com_guestbooks.yaml
kubectl apply -f config/samples/webapp_v1_guestbook.yaml
Run you're main program or integrate the generated client into your application to list or watch Guestbook resources.