在Istio和Knative,以及众多中间件operator中都会经常看到有使用informer去watch自定义CRD资源,然后reconsile的代码。其实每一个controller/operator都是使用api-server的客户端在操作资源。对于k8s支持的核心资源类型可以直接引用client-go,对于CRD,当前主要有三种方案。
- 使用原生k8s的code-generator生成client代码
- 直接使用kubebuilder,生成controller框架的同时自动生成client代码
- 使用coreos和redhat提供的operator framework(有点类似于第二种)
code-generator介绍
项目目录: k8s.io/code-generator
对于初学者来讲,后两种方式都比较容易上手,开发者可以将关注点集中在怎么实现controller的业务上面,而不用去关心client的实现。但是,考虑到经常阅读源代码,时常接触到一些高度一致的目录结构而一头雾水;同时,便于更高效的阅读k8s相关项目的角度,我们接下来重点聊下k8s原生code-generator生成client库的过程,以及对应的目录结构的内容。
代码生成
为啥要生成client库的代码?显然是因为我们定义了k8s core resource之外的CRD,那么在使用脚本自动生成cliet代码之前,很显然我们需要做一些对资源的定义(除了资源的定义之外,重点还有一系列的注释,这些注释有特定的语法,这里不做详述,具体可以参考后记中推荐的官方文档),这样脚本才可能生成我们所需的代码。接下来分两步演示:1. 预定义资源 2. 生成客户端代码
定义资源
准备controller项目路径,这一步需要考虑好controller的命名,CRD资源的版本号,这些信息需要用来命名目录。
1 | # 创建目录 (在$GOPATH/k8s.io下执行命令) |
这里需要预先按照对应的结构生成几个文件, 目录结构如图所示:
1 | ~/gopath/src/k8s.io/project tree |
- 最顶层的register.go定义了groupName;
- 在版本号目录下又有三个文件:
- doc.go定义了版本的包名;
- types.go定义资源的数据结构;
- register.go真正用于注册该版本下的资源。
顶层register.go
1
2
3
4
5
6
7
8# 生成顶层register.go
cat << EOF >> register.go
package controller
const (
GroupName = "controller.ljchen.net"
)
EOFdoc.go
注意,类似于下面的注释语句// +k8s:deepcopy-gen=package
和// +groupName=controller.ljchen.net
都是code-generator的语法,详情请参考 #后记 中推荐的文章。1
2
3
4
5
6
7
8
9
10
11# 进入v1目录,生成doc.go/types.go/register.go
cd v1
# 生成doc.go
cat << EOF >> doc.go
// +k8s:deepcopy-gen=package
// +groupName=controller.ljchen.net
// Package v1 is the v1 version of the API.
package v1
EOFtypes.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
34
35
36
37
38
39
40
41# 生成types.go
cat << EOF >> types.go
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +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"`
}
EOFregister.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
34
35
36
37
38
39
40# 生成register.go
cat << EOF >> register.go
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
samplecontroller "k8s.io/project/pkg/apis/controller"
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: samplecontroller.GroupName, Version: "v1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Foo{},
&FooList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
EOF
生成客户端代码
自动生成client代码的脚本位于code-generator项目中,generate-groups.sh
是对项目中多个二进制文件编排的整合。
脚本用法如下:
generate-groups.sh {generators} {output-package} {apis-package} {groups-versions} …
下面就使用以上脚本来自动生成刚才我们所写资源的client库。请千万注意这里的执行路径和参数的路径!
1 | ~$ pwd |
生成后的目录结构如下所示:
1 | ~/gopath/src/k8s.io/project tree |
后记
客户端代码生成后,接下来就是实现自己的controller代码了。这块可以这里就不再详述,具体可以参考k8s官方的sample-controller 代码。
code-generator有自己的语法,当然,如果你只是简单的要实现一个operator可以直接复制上面的代码来修改,否则请参考这里。
另外,对于controller中的原理,这篇文章有讲的比较详细,可以参考。