controller-tools实战:自动生成代码
初始化项目
- 初始化go项目,并get client-go
1
2
3
4
5cd controller-tools-demo
go mod init controller-tools-demo
go get k8s.io/client-go
go get k8s.io/apimachinery
使用type-scaffold工具生成types.go
- 需要注意:
- type-scaffold并不会生成文件,而是生成types.go的内容,打印到控制台,我们需要手动copy到types.go文件中去
- 不过使用kubebuilder的时候,会帮我们生成types.go文件的
- 执行 type-scaffold –kind=Application,得到types.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[root@master controller-tools-demo]# type-scaffold --kind=Application
// ApplicationSpec defines the desired state of Application
type ApplicationSpec struct {
// INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
}
// ApplicationStatus defines the observed state of Application.
// It should always be reconstructable from the state of the cluster and/or outside world.
type ApplicationStatus struct {
// INSERT ADDITIONAL STATUS FIELDS -- observed state of cluster
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Application is the Schema for the applications API
// +k8s:openapi-gen=true
type Application struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApplicationSpec `json:"spec,omitempty"`
Status ApplicationStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ApplicationList contains a list of Application
type ApplicationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Application `json:"items"`
} - 在 v1alpha1 目录下创建 types.go 文件,将控制台的内容copy进去,记得导包
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
35package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// ApplicationSpec defines the desired state of Application
type ApplicationSpec struct {
// INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
}
// ApplicationStatus defines the observed state of Application.
// It should always be reconstructable from the state of the cluster and/or outside world.
type ApplicationStatus struct {
// INSERT ADDITIONAL STATUS FIELDS -- observed state of cluster
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Application is the Schema for the applications API
// +k8s:openapi-gen=true
type Application struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApplicationSpec `json:"spec,omitempty"`
Status ApplicationStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ApplicationList contains a list of Application
type ApplicationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Application `json:"items"`
}
使用controller-gen生成deepcopy和crd文件
controller-gen –help 查看帮助文档
- 帮助文档给出了很多 examples
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
50
51
52
53
54
55
56[root@master v1alpha1]# controller-gen --help
Generate Kubernetes API extension resources and code.
Usage:
controller-gen [flags]
Examples:
# Generate RBAC manifests and crds for all types under apis/,
# outputting crds to /tmp/crds and everything else to stdout
controller-gen rbac:roleName=<role name> crd paths=./apis/... output:crd:dir=/tmp/crds output:stdout
# Generate deepcopy/runtime.Object implementations for a particular file
controller-gen object paths=./apis/v1beta1/some_types.go
# Generate OpenAPI v3 schemas for API packages and merge them into existing CRD manifests
controller-gen schemapatch:manifests=./manifests output:dir=./manifests paths=./pkg/apis/...
# Run all the generators for a given project
controller-gen paths=./apis/...
# Explain the markers for generating CRDs, and their arguments
controller-gen crd -ww
Flags:
-h, --detailed-help count print out more detailed help
(up to -hhh for the most detailed output, or -hhhh for json output)
--help print out usage and a summary of options
--version show version
-w, --which-markers count print out all markers available with the requested generators
(up to -www for the most detailed output, or -wwww for json output)
Options
generators
+webhook package generates (partial) {Mutating,Validating}WebhookConfiguration objects.
+schemapatch[:generateEmbeddedObjectMeta=<bool>],manifests=<string>[,maxDescLen=<int>] package patches existing CRDs with new schemata.
+rbac:roleName=<string> package generates ClusterRole objects.
+object[:headerFile=<string>][,year=<string>] package generates code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
+crd[:allowDangerousTypes=<bool>][,crdVersions=<[]string>][,generateEmbeddedObjectMeta=<bool>][,ignoreUnexportedFields=<bool>][,maxDescLen=<int>] package generates CustomResourceDefinition objects.
generic
+paths=<[]string> package represents paths and go-style path patterns to use as package roots.
output rules (optionally as output:<generator>:...)
+output:artifacts[:code=<string>],config=<string> package outputs artifacts to different locations, depending on whether they're package-associated or not.
+output:dir=<string> package outputs each artifact to the given directory, regardless of if it's package-associated or not.
+output:none package skips outputting anything.
+output:stdout package outputs everything to standard-out, with no separation.
使用controller-gen生成deepcopy
先刷新一下包
1
go mod tidy
执行命令
1
2cd controller-tools-demo
controller-gen object paths=pkg/apis/appcontroller/v1alpha1/types.go执行后,查看目录文件,发现生成了一个 zz_generated.deepcopy.go 文件
1
2
3
4
5
6
7
8
9
10[root@master controller-tools-demo]# tree
.
├── go.mod
├── go.sum
└── pkg
└── apis
└── appcontroller
└── v1alpha1
├── types.go
└── zz_generated.deepcopy.go查看 zz_generated.deepcopy.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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Application) DeepCopyInto(out *Application) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application.
func (in *Application) DeepCopy() *Application {
if in == nil {
return nil
}
out := new(Application)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Application) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationList) DeepCopyInto(out *ApplicationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Application, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationList.
func (in *ApplicationList) DeepCopy() *ApplicationList {
if in == nil {
return nil
}
out := new(ApplicationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ApplicationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
使用controller-gen生成crd
先刷新一下包
1
go mod tidy
执行命令
1
2cd controller-tools-demo
controller-gen crd paths=./... output:crd:dir=config/crdpaths=./… 表示将当前目录下的所有子目录都包括在生成过程中
output:crd:dir=config/crd 指定了输出目录为 config/crd
执行后,生成目录 config 和 文件 _.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13[root@master controller-tools-demo]# tree
.
├── config
│ └── crd
│ └── _.yaml
├── go.mod
├── go.sum
└── pkg
└── apis
└── appcontroller
└── v1alpha1
├── types.go
└── zz_generated.deepcopy.go文件 _.yaml 没有名字?指定groupName后重新生成 crd 文件
- 因为我们没有给它指定 groupName,所以生成的yaml文件默认没有名字
- 我们在 pkg/apis/appcontroller/v1alpha1 下创建一个 doc.go 文件,并在里面写上如下内容
1
2// +groupName=appcontroller.k8s.io
package v1alpha1
然后删除原config后,重新生成crd
1
2
3cd controller-tools-demo
rm -rf config
controller-gen crd paths=./... output:crd:dir=config/crd生成完成后,目录如下
- crd文件名称为:appcontroller.k8s.io_applications.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[root@master controller-tools-demo]# tree
.
├── config
│ └── crd
│ └── appcontroller.k8s.io_applications.yaml
├── go.mod
├── go.sum
└── pkg
└── apis
└── appcontroller
└── v1alpha1
├── doc.go
├── types.go
└── zz_generated.deepcopy.go
6 directories, 6 files
- crd文件名称为:appcontroller.k8s.io_applications.yaml
appcontroller.k8s.io_applications.yaml 内容如下
- 可以看到,openAPIV3Schema.properties.spec 下面,没有 properties 项,因为我们的ApplicationSpec 为空,内部一个属性都没有。
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---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: applications.appcontroller.k8s.io
spec:
group: appcontroller.k8s.io
names:
kind: Application
listKind: ApplicationList
plural: applications
singular: application
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Application is the Schema for the applications API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ApplicationSpec defines the desired state of Application
type: object
status:
description: ApplicationStatus defines the observed state of Application.
It should always be reconstructable from the state of the cluster and/or
outside world.
type: object
type: object
served: true
storage: true
- 可以看到,openAPIV3Schema.properties.spec 下面,没有 properties 项,因为我们的ApplicationSpec 为空,内部一个属性都没有。
为ApplicationSpec添加内容,并重新生成crd文件
修改types.go,在 ApplicationSpec 中添加两个字段
1
2
3
4
5type ApplicationSpec struct {
// INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
Name string `json:"name"`
Replicas int32 `json:"replicas"`
}删除原config后,重新生成crd文件
1
2
3cd controller-tools-demo
rm -rf config
controller-gen crd paths=./... output:crd:dir=config/crd新的crd文件内容如下:
- 可以看到,openAPIV3Schema.properties.spec 下面,出现了 properties 项,因为我们为ApplicationSpec 添加了Name、Replicas两个值。
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
50
51
52
53
54
55---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: applications.appcontroller.k8s.io
spec:
group: appcontroller.k8s.io
names:
kind: Application
listKind: ApplicationList
plural: applications
singular: application
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Application is the Schema for the applications API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ApplicationSpec defines the desired state of Application
properties:
name:
description: INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
type: string
replicas:
format: int32
type: integer
required:
- name
- replicas
type: object
status:
description: ApplicationStatus defines the observed state of Application.
It should always be reconstructable from the state of the cluster and/or
outside world.
type: object
type: object
served: true
storage: true
- 可以看到,openAPIV3Schema.properties.spec 下面,出现了 properties 项,因为我们为ApplicationSpec 添加了Name、Replicas两个值。
手动注册版本v1alpha1的CRD资源
- 在生成了客户端代码后,我们还需要手动注册版本v1alpha1的CRD资源,才能真正使用这个client,不然在编译时会出现 undefined: v1alpha1.AddToScheme 错误、undefined: v1alpha1.Resource 错误。
- v1alpha1.AddToScheme、v1alpha1.Resource 这两个是用于 client 注册的
- 编写 v1alpha1/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
34
35
36
37
38
39package v1alpha1
import (
"controller-tools-demo/pkg/apis/appcontroller"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: appcontroller.GroupName, Version: "v1alpha1"}
// 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 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
)
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Application{},
&ApplicationList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
在kubernetes集群中应用CRD
kubectl apply crd 资源
上面我们已经使用 controller-gen 自动生成了 CRD 文件,名称为 appcontroller.k8s.io_applications.yaml,再 config/crd 目录下
我们需要使用 kubectl apply 命令创建CR资源
1
2cd controller-tools-demo
kubectl apply -f config/crd/appcontroller.k8s.io_applications.yaml有可能报错:
1
The CustomResourceDefinition "applications.appcontroller.k8s.io" is invalid: metadata.annotations[api-approved.kubernetes.io]: Required value: protected groups must have approval annotation "api-approved.kubernetes.io", see https://github.com/kubernetes/enhancements/pull/1111
解决方法
- 这是kubernetes的保护机制,防止外部随意创建crd,破坏环境
- 报错中已经给了提示,查看github:https://github.com/kubernetes/enhancements/pull/1111
- 只需要在crd中,添加一条 annotation,然后再执行 kubectl apply -f 命令就可以了
1
2
3metadata:
annotations:
api-approved.kubernetes.io: "https://github.com/kubernetes/kubernetes/pull/78458"
添加之后,完整的 crd 文件内容如下:
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
50
51
52
53
54
55
56---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
api-approved.kubernetes.io: "https://github.com/kubernetes/kubernetes/pull/78458"
creationTimestamp: null
name: applications.appcontroller.k8s.io
spec:
group: appcontroller.k8s.io
names:
kind: Application
listKind: ApplicationList
plural: applications
singular: application
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Application is the Schema for the applications API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ApplicationSpec defines the desired state of Application
properties:
name:
description: INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
type: string
replicas:
format: int32
type: integer
required:
- name
- replicas
type: object
status:
description: ApplicationStatus defines the observed state of Application.
It should always be reconstructable from the state of the cluster and/or
outside world.
type: object
type: object
served: true
storage: true
编写 crd 资源的 example
在 config 目录下,创建一个example目录,内部可以编写一些 application资源的yaml,用于测试Application资源的创建是否可以成功
在config/example目录下,编写一个test_app.yaml
1
2
3
4
5
6
7
8apiVersion: appcontroller.k8s.io/v1alpha1
kind: Application
metadata:
name: testapp
namespace: tcs
spec:
name: testapp1
replicas: 2创建Application资源
1
2cd controller-tools-demo
kubectl apply -f ./config/example/test_app.yaml
测试Application资源的获取
在项目根目录下,创建一个cmd目录,里面创建一个main.go文件
下面我们演示如何使用 code-generator 为 Application 的 v1alpha1 生成的客户端 AppcontrollerV1alpha1Client
编写main.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
42
43package main
import (
"context"
"controller-tools-demo/pkg/apis/appcontroller/v1alpha1"
"fmt"
"log"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
if err != nil {
log.Fatalln(err)
}
config.APIPath = "/apis/"
config.GroupVersion = &v1alpha1.SchemeGroupVersion
config.NegotiatedSerializer = scheme.Codecs
client, err := rest.RESTClientFor(config)
if err != nil {
log.Fatalln(err)
}
app := v1alpha1.Application{}
err = client.Get().Namespace("tcs").Resource("applications").Name("testapp").Do(context.TODO()).Into(&app)
if err != nil {
log.Fatalln(err)
}
newObj := app.DeepCopy()
newObj.Spec.Name = "testapp2"
fmt.Println(app.Spec.Name)
fmt.Println(app.Spec.Replicas)
fmt.Println(newObj.Spec.Name)
}输出结果
1
2
3
4[root@master controller-tools-demo]# go run cmd/main.go
testapp1
2
testapp2如果没有在kubernetes集群中 应用 CRD资源,直接执行上面的代码,会报错:找不到这个资源。
1 | go run cmd/main.go |