使用code-Generator编写CRD

使用k8s.io/code-generator编写CRD。

本项目代码在 https://github.com/chanjarster/k8s-code-gen-how-to

概览

k8s.io/code-generator是一个代码生成工具,用于为你的CRD生成kubernetes-style API实现,这样你就可以管理K8S上你的自定义CRD资源了。

第一步:初始化项目

1
2
MODULE=example.com/foo-controller
go mod init $MODULE

第二步:定义API

新建目录api/<group>/<version>,这个目录下得有以下几个文件:

1
2
3
4
5
6
7
.
└── api
    └── foo
    └── v1alpha1
        ├── doc.go
        ├── register.go
        └── types.go

执行命令:

1
2
3
GROUP=foo
VERSION=v1alpha1
mkdir -p api/$GROUP/$VERSION

编写文件api/<group>/<version>/doc.go,注意//+groupName=foo.example.com,你需要视情况修改:

1
2
3
4
5
// +k8s:deepcopy-gen=package
// +groupName=foo.example.com

// Package v1alpha1 is the v1alpha1 version of the API.
package v1alpha1

编写文件api/<group>/<version>/types.go,注意视情况修改类名以及相关常量,Status字段并非必须的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// These const variables are used in our custom controller.
const (
	GroupName string = "foo.example.com"
	Kind      string = "Foo"
	Version   string = "v1alpha1"
	Plural    string = "foos"
	Singluar  string = "foo"
	ShortName string = "foo"
	Name      string = Plural + "." + GroupName
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Foo is a specification for a Foo resource
type Foo struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   FooSpec   `json:"spec"`
	Status FooStatus `json:"status"`
}

// FooSpec is the spec for a Foo resource
type FooSpec struct {
	DeploymentName string `json:"deploymentName"`
	Replicas       *int32 `json:"replicas"`
}

// FooStatus is the status for a Foo resource
type FooStatus struct {
	AvailableReplicas int32 `json:"availableReplicas"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// FooList is a list of Foo resources
type FooList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Foo `json:"items"`
}

编写文件api/<group>/register.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
	// SchemeBuilder initializes a scheme builder
	SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
	// AddToScheme is a global function that registers this API group & version to a scheme
	AddToScheme = SchemeBuilder.AddToScheme
)

// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{
	Group:   GroupName,
	Version: Version,
}

func Resource(resource string) schema.GroupResource {
	return SchemeGroupVersion.WithResource(resource).GroupResource()
}

func addKnownTypes(scheme *runtime.Scheme) error {
	scheme.AddKnownTypes(SchemeGroupVersion,
		&Foo{},
		&FooList{},
	)
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}

然后添加依赖:

1
2
K8S_VERSION=v0.18.5
go get k8s.io/apimachinery@$K8S_VERSION

第三步:代码生成

1)准备脚本

新建目录hack,我们需要在hack目录下有以下几个文件:

1
2
3
4
5
6
.
└── hack
    ├── boilerplate.go.txt
    ├── tools.go
    ├── update-codegen.sh
    └── verify-codegen.sh

执行命令:

1
2
mkdir hack
touch hack/boilerplate.go.txt

新建hack/tools.go文件:

1
2
3
4
5
// +build tools

package tools

import _ "k8s.io/code-generator"

新建hack/update-codegen.sh,注意修改几个变量:

  • MODULEgo.mod保持一致
  • API_PKG=api,和我们的目录保持一致
  • OUTPUT_PKG
  • GROUP_VERSION=foo.v1alpha1而不是foo.example.com:v1alpha1,因为code generator会读取我们之前写的api/<group>/<version>下的代码,因此得要对应上这个路径:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

# corresponding to go mod init <module>
MODULE=example.com/foo-controller
# api package
APIS_PKG=api
# generated output package
OUTPUT_PKG=generated
# group-version such as foo:v1alpha1
GROUP_VERSION=foo:v1alpha1

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)}

# 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.
bash "${CODEGEN_PKG}"/generate-groups.sh "all" \
  ${MODULE}/${OUTPUT_PKG} ${MODULE}/${APIS_PKG} \
  ${GROUP_VERSION} \
  --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt \
  --output-base "${SCRIPT_ROOT}"
#  --output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \

# To use your own boilerplate text append:
#   --go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt

新建hack/verify-codegen.sh(文件内容请看github项目)。

2)下载code-generator

先把code-generator下载下来,注意这里的K8S版本号,得和前面是一致的:

1
2
3
K8S_VERSION=v0.18.5
go get k8s.io/code-generator@$K8S_VERSION
go mod vendor

然后给generate-groups.sh添加可执行权限:

1
chmod +x vendor/k8s.io/code-generator/generate-groups.sh

3)生成代码

执行hack/update-codegen.sh

1
./hack/update-codegen.sh

代码会生成在example.com/foo-controller目录下(回忆前面的MODULE=example.com/foo-controllerOUTPUT_PKG=generated参数):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
example.com
└── foo-controller
    ├── api
    │   └── foo
    │       └── v1alpha1
    │           └── zz_generated.deepcopy.go
    └── generated
        ├── clientset
        ├── informers
        └── listers

移动文件:

  • zz_generated.deepcopy.go移动到api/<group>/<version>
  • example.com/foo-controller/generated直接移出来,放到项目根下面generated
  • 如果后面你修改了types.go,重新执行./hack/update-codegen.sh就行了。

对于生成代码的说明:

  • clientset:用于操作foos.foo.example.comCRD资源
  • informers:Informer接收来自服务器的CRD的变更事件
  • listers:Lister提供只读的cache layer for GET和LIST请求

关于Controller

controller代码可以看项目的main.go

本例子里controller读取环境变量KUBECONFIG来启动Clientset以及和K8S通信,这个也符合k8s.io/client-go out-of-cluster example。在实际生产环境中,可以参考k8s.io/client-go in-cluster example

如果你想要更灵活的做法,比如当提供了--kubeconfig的时候采用out-of-cluster模式,否则则尝试in-cluster模式(看/var/run/secrets/kubernetes.io/serviceaccount),可以参考prometheus-operator k8sutil.go的做法

参考文档

版权

评论