k8s自定义资源: 使用Kubebuilder 与 code-generator生成代码
1. 安装kubebuilder
依赖组件:
go
version v1.17+
docker
version 20.10.10
kubectl
version v1.25
kustomize version v4.5.7
kustomize使用git执行如下操作:
[root@kubebuilder example]# git clone git@github.com:kubernetes-sigs/kustomize.git
[root@kubebuilder example]# cd kustomize
[root@kubebuilder example]# git checkout kustomize/v4.5.2
[root@kubebuilder example]# go install .
[root@kubebuilder example]# mv kustomize /usr/local/bin/
安装组件
Kubebuilder使用controller-gen工具来生成程序代码和Kubernetes的YAML对象,比如CustomResourceDefinitions,同时使用了Kustomize工具通过kustomization文件定制kubernetes对象。因此我们需要安装kubebuilder, kind, controller-gen, kustomize及设置goproxy。
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.3.0/kubebuilder_linux_amd64
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.12.0/kind-linux-amd64
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
mv kubebuilder_linux_amd64 /usr/local/bin/kubebuilder
mv kind-linux-amd64 /usr/local/bin/kind
export GOPROXY=https://proxy.golang.com.cn,direct
创建集群
使用kind创建一个kubernetes集群
[root@kubebuilder example]# kind create cluster --name local --image kindest/node:v1.23.5
[root@kubebuilder example]# kind get kubeconfig --name local >~/.kube/config
2. 初始化项目
- 创建项目
[root@kubebuilder example]# mkdir -p $GOPATH/src/my.domain/example
[root@kubebuilder example]# cd $GOPATH/src/my.domain/example
[root@kubebuilder example]# go mod init example
[root@kubebuilder example]# kubebuilder init --domain my.domain
[root@kubebuilder example]# tree -CL 3
.
├── config
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ └── manager_config_patch.yaml
│ ├── manager
│ │ ├── controller_manager_config.yaml
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ └── rbac
│ ├── auth_proxy_client_clusterrole.yaml
│ ├── auth_proxy_role_binding.yaml
│ ├── auth_proxy_role.yaml
│ ├── auth_proxy_service.yaml
│ ├── kustomization.yaml
│ ├── leader_election_role_binding.yaml
│ ├── leader_election_role.yaml
│ ├── role_binding.yaml
│ └── service_account.yaml
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT
- 创建api
[root@kubebuilder example]# kubebuilder create api --group example --version v1 --kind Guestbook
Create Resource [y/n]
y
Create Controller [y/n]
y
...
tree -CL 3
.
├── api
│ └── v1
│ ├── groupversion_info.go
│ ├── guestbook_types.go
│ └── zz_generated.deepcopy.go
├── bin
│ └── controller-gen
├── config
│ ├── crd
│ │ ├── kustomization.yaml
│ │ ├── kustomizeconfig.yaml
│ │ └── patches
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ └── manager_config_patch.yaml
│ ├── manager
│ │ ├── controller_manager_config.yaml
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── guestbook_editor_role.yaml
│ │ ├── guestbook_viewer_role.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── role_binding.yaml
│ │ └── service_account.yaml
│ └── samples
│ └── example_v1_guestbook.yaml
├── controllers
│ ├── guestbook_controller.go
│ └── suite_test.go
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT
注意:
如果修改了 api/v1/guestbook_types.go ,需要执行以下命令来更新代码和manifests:
make && make manifests
3. 使用code-generator
更新依赖版本
初始化项目后的go.mod:
[root@kubebuilder example]# cat go.mod
module example
go 1.16
require (
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
k8s.io/apimachinery v0.22.1
k8s.io/client-go v0.22.1
sigs.k8s.io/controller-runtime v0.10.0
)
初始化项目后的go.mod:
[root@kubebuilder example]# K8S_VERSION=v0.18.6
[root@kubebuilder example]# go get k8s.io/client-go@$K8S_VERSION
go: downloading k8s.io/client-go v0.18.6
go: downloading k8s.io/component-base v0.18.6
go: downloading sigs.k8s.io/controller-runtime v0.3.0
go: downloading k8s.io/apiextensions-apiserver v0.15.13-beta.0
go get: downgraded k8s.io/apiextensions-apiserver v0.22.1 => v0.15.13-beta.0
go get: downgraded k8s.io/client-go v0.22.1 => v0.18.6
go get: downgraded k8s.io/component-base v0.22.1 => v0.18.6
go get: downgraded sigs.k8s.io/controller-runtime v0.10.0 => v0.3.0
[root@kubebuilder example]# go get k8s.io/apimachinery@$K8S_VERSION
go: downloading k8s.io/apimachinery v0.18.6
go: downloading k8s.io/api v0.18.6
go get: downgraded k8s.io/api v0.22.1 => v0.18.6
go get: downgraded k8s.io/apimachinery v0.22.1 => v0.18.6
安装code-generator
k8s的版本号与 go.mod 中的 k8s.io/client-go 的版本保持一致即可。
**注意:**需要将依赖复制到vendor中
go get github.com/googleapis/gnostic@v0.4.0
go get k8s.io/code-generator@$K8S_VERSION
或者go get k8s.io/code-generator@v0.18.6
go mod vendor
创建&修改所需文件
需要在api目录下创建code-generator所需的文件,并添加相关注释。
-
新增 api/v1/doc.go
**注意:**修改groupName,package与api的version保持一致。
[root@kubebuilder example]# cat api/v1/doc.go
// +k8s:deepcopy-gen=package
// Package v1 is the v1alpha1 version of the API.
// +groupName=example.my.domain
package v1
-
新增 api/v1/register.go
**注意:**package与api的version保持一致。
[root@kubebuilder example]# cat api/v1/register.go
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = GroupVersion
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
- 修改 api/v1/{crd}_types.go 文件,添加注释 // +genclient
cat api/v1/guestbook_types.go
...
// +genclient
//+kubebuilder:object:root=true
// GuestbookList contains a list of Guestbook
type GuestbookList struct {
...
准备脚本
在项目 hack 目录下准备以下文件:
- 新建 hack/tools.go 文件
[root@kubebuilder example]# cat hack/tools.go
// +build tools
package tools
import _ "k8s.io/code-generator"
-
新建 hack/update-codegen.sh,注意根据项目修改相应变量:
MODULE 和 go.mod 保持一致
API_PKG=api,和 api 目录保持一致
OUTPUT_PKG=generated/example,与生成Resource时指定的group保持一致
GROUP=example, 和生成Resource时指定的group 保持一致
VERSION=v1, 和生成Resource时指定的version保持一致
[root@kubebuilder example]# cat ./hack/update-codegen.sh
#!/usr/bin/env bash
#表示有报错即退出 跟set -e含义一样
set -o errexit
#执行脚本的时候,如果遇到不存在的变量,Bash 默认忽略它 ,跟 set -u含义一样
set -o nounset
# 只要一个子命令失败,整个管道命令就失败,脚本就会终止执行
set -o pipefail
#kubebuilder项目的MODULE
MODULE=my.domain/example
#api包
APIS_PKG=api
#代码生出输出,生成Resource时指定的group一样
OUTPUT_PKG=generated/example
# group-version such as cronjob:v1
GROUP=example
VERSION=v1
GROUP_VERSION=$GROUP:$VERSION
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
# kubebuilder2.3.2版本生成的api目录结构code-generator无法直接使用
rm -rf "${APIS_PKG}/${GROUP}" && mkdir -p "${APIS_PKG}/${GROUP}" && cp -r "${APIS_PKG}/${VERSION}/" "${APIS_PKG}/${GROUP}"
# generate the code with:
# --output-base because this script should also be able to run inside the vendor dir of
# k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir
# instead of the $GOPATH directly. For normal projects this can be dropped.
#client,informer,lister(注意: code-generator 生成的deepcopy不适配 kubebuilder 所生成的api)
bash "${CODEGEN_PKG}"/generate-groups.sh "client,informer,lister" \
${MODULE}/${OUTPUT_PKG} ${MODULE}/${APIS_PKG} \
${GROUP_VERSION} \
--go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt
# --output-base "${SCRIPT_ROOT}"
# --output-base "${SCRIPT_ROOT}/../../.."
注意
:
kubebuilder2.3.2版本生成的api目录结构code-generator无法直接使用,需要在sh脚本中进行处理。
修改脚本执行参数可以选择生成的代码,如:“client,informer,lister”。**注意无需再次生成deepcopy:**code-generator 生成的deepcopy不适配 kubebuilder 所生成的api。
修改 Makefile ,添加生成命令
[root@kubebuilder example]# chmod +x hack/update-codegen.sh
[root@kubebuilder example]# vim Makefile
71 update-codegen: ## generetor clientset informer inderx code
72 ./hack/update-codegen.sh
生成代码
项目根目录下执行make update-codegen即可,将生成如下代码结构:
[root@kubebuilder example]# tree generated/
generated/
└── example
├── clientset
│ └── versioned
│ ├── clientset.go
│ ├── doc.go
│ ├── fake
│ │ ├── clientset_generated.go
│ │ ├── doc.go
│ │ └── register.go
│ ├── scheme
│ │ ├── doc.go
│ │ └── register.go
│ └── typed
│ └── example
│ └── v1
│ ├── doc.go
│ ├── example_client.go
│ ├── fake
│ │ ├── doc.go
│ │ ├── fake_example_client.go
│ │ └── fake_guestbook.go
│ ├── generated_expansion.go
│ └── guestbook.go
├── informers
│ └── externalversions
│ ├── example
│ │ ├── interface.go
│ │ └── v1
│ │ ├── guestbook.go
│ │ └── interface.go
│ ├── factory.go
│ ├── generic.go
│ └── internalinterfaces
│ └── factory_interfaces.go
└── listers
└── example
└── v1
├── expansion_generated.go
└── guestbook.go
之后便可以通过clientset等包对自定义资源对象进行操作。
注意事项
:
kubebuilder2.3.2版本生成的api目录结构为 api/v1,而code-generator需要的api目录结构为 api/example/v1,相比较增加了group这一层。
hack/update-codegen.sh 脚本会自动根据kubebuilder的api生成code-generator所需目录结构。
code-generator 生成的deepcopy不适配 kubebuilder 所生成的api。
code-generator 生成代码后再次使用make操作时可能由于生成的代码影响命令正常执行,例如: make manifests 在生成CRD模版时,需先删除api以及generated目录中为code-generator生成的代码,才可正常生成CRD模版。
使用时kubebuilder正常使用 api/v1 中的types,而code-generator生成的clientset等则需要使用 api/example/v1 中的types。
参考文献