介绍

在上一篇code-generator简单介绍中重点介绍了如何使用code-generator来自动生成代码,通过自动生成的代码可以帮助我们像访问k8s内置资源那样来操作我们的CRD,其实就是帮助我们生成ClientSet、Informer、Lister等工具包。
但是我们需要自己定义types.go文件以及需要自己去编写crd文件。工作量其实也是很大的,那么有没有工具像code-generator那样帮助我们生成代码呢?答案是肯定的,那就是接下来要介绍的controller-tools

controller-tools

controller-tools主要可以帮我们自动生成types.go所需要的内容以及自动帮我们生成crd。
同样首先将其clone到本地:

  1. $ git clone https://github.com/kubernetes-sigs/controller-tools.git

在项目的cmd目录下,我们可以看到有controller-gen、helpgen、type-scaffold三个工具。
其中type-scaffold可以用来生成我们需要的types.go文件,controller-gen可以生成zz_xxx.deepcopy.go文件以及crd文件。
我们使用go install进行安装:

  1. $ cd controller-gen
  2. $ go install ./cmd/{controller-gen,type-scaffold}

安装完成后我们可以去GOPATH下的bin目录下查看。
image.png
接着我们就可以新建一个项目,来使用controller-tools提供的工具为我们自动生成代码了。

  1. $ mkdir controller-test && cd controller-test
  2. $ go mod init controller-test
  3. $ mkdir -p pkg/apis/example.com/v1
  4. $ tree
  5. .
  6. ├── go.mod
  7. └── pkg
  8. └── apis
  9. └── example.com
  10. └── v1
  11. 4 directories, 1 file

接下来我们就可以使用工具来生成我们所需要的代码了,首先我们生成types.go所需要的内容,由于type-scaffold不支持导入文本,所以生成后我们需要复制到types.go文件中:

  1. $ type-scaffold --kind Foo
  2. // FooSpec defines the desired state of Foo
  3. type FooSpec struct {
  4. // INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
  5. }
  6. // FooStatus defines the observed state of Foo.
  7. // It should always be reconstructable from the state of the cluster and/or outside world.
  8. type FooStatus struct {
  9. // INSERT ADDITIONAL STATUS FIELDS -- observed state of cluster
  10. }
  11. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  12. // Foo is the Schema for the foos API
  13. // +k8s:openapi-gen=true
  14. type Foo struct {
  15. metav1.TypeMeta `json:",inline"`
  16. metav1.ObjectMeta `json:"metadata,omitempty"`
  17. Spec FooSpec `json:"spec,omitempty"`
  18. Status FooStatus `json:"status,omitempty"`
  19. }
  20. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  21. // FooList contains a list of Foo
  22. type FooList struct {
  23. metav1.TypeMeta `json:",inline"`
  24. metav1.ListMeta `json:"metadata,omitempty"`
  25. Items []Foo `json:"items"`
  26. }

然后在types.go文件中将import metav1 “k8s.io/apimachinery/pkg/apis/meta/v1”添加上就行。
当然自动生成只是一个模版,里面的具体细节还是需要我们自己去填写,比如我们填充FooSpec。
资源类型定义好了,那么如何能让client-go识别我们的资源呢,这里就需要其注册进去。我们可以在register.go中定义GV(Group Version),以及通过标签指定groupName。

  1. // register.go
  2. // +groupName=example.com
  3. package v1
  4. import (
  5. "k8s.io/apimachinery/pkg/runtime"
  6. "k8s.io/apimachinery/pkg/runtime/schema"
  7. "k8s.io/apimachinery/pkg/runtime/serializer"
  8. )
  9. var (
  10. Scheme = runtime.NewScheme()
  11. GroupVersion = schema.GroupVersion{
  12. Group: "example.com",
  13. Version: "v1",
  14. }
  15. Codec = serializer.NewCodecFactory(Scheme)
  16. )

在types.go中调用Scheme.AddKnownTypes方法即可:

  1. // types.go
  2. package v1
  3. import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  4. // FooSpec defines the desired state of Foo
  5. type FooSpec struct {
  6. // INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
  7. Name string `json:"name"`
  8. Replicas int32 `json:"replicas"`
  9. }
  10. // FooStatus defines the observed state of Foo.
  11. // It should always be reconstructable from the state of the cluster and/or outside world.
  12. type FooStatus struct {
  13. // INSERT ADDITIONAL STATUS FIELDS -- observed state of cluster
  14. }
  15. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  16. // Foo is the Schema for the foos API
  17. // +k8s:openapi-gen=true
  18. type Foo struct {
  19. metav1.TypeMeta `json:",inline"`
  20. metav1.ObjectMeta `json:"metadata,omitempty"`
  21. Spec FooSpec `json:"spec,omitempty"`
  22. Status FooStatus `json:"status,omitempty"`
  23. }
  24. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  25. // FooList contains a list of Foo
  26. type FooList struct {
  27. metav1.TypeMeta `json:",inline"`
  28. metav1.ListMeta `json:"metadata,omitempty"`
  29. Items []Foo `json:"items"`
  30. }
  31. func init() {
  32. Scheme.AddKnownTypes(GroupVersion, &Foo{}, &FooList{})
  33. }

接下来就需要生成deepcopy.go文件了:

  1. $ controller-gen object paths=./pkg/apis/example.com/v1/types.go

同样,我们使用controller-gen生成crd:

  1. $ mkdir config
  2. $ go mod tidy
  3. $ controller-gen crd paths=./... output:crd:dir=config/crd

这时候我们查看项目结构:

  1. .
  2. ├── config
  3. └── crd
  4. └── example.com_foos.yaml
  5. ├── go.mod
  6. ├── go.sum
  7. └── pkg
  8. └── apis
  9. └── example.com
  10. └── v1
  11. ├── register.go
  12. ├── types.go
  13. └── zz_generated.deepcopy.go
  14. 6 directories, 6 files

最后我们来进行验证,首先创建一个cr:

  1. apiVersion: example.com/v1
  2. kind: Foo
  3. metadata:
  4. name: crd-test
  5. spec:
  6. name: test
  7. replicas: 2

将crd和cr添加到集群后,我们来编写main.go文件来进行验证:

  1. package main
  2. import (
  3. "context"
  4. v1 "controller-test/pkg/apis/example.com/v1"
  5. "fmt"
  6. "k8s.io/client-go/rest"
  7. "k8s.io/client-go/tools/clientcmd"
  8. "log"
  9. )
  10. func main() {
  11. config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
  12. if err != nil {
  13. log.Fatalln(err)
  14. }
  15. // 这边需要使用原始的 RESTClient
  16. config.APIPath = "/apis/"
  17. config.NegotiatedSerializer = v1.Codec
  18. config.GroupVersion = &v1.GroupVersion
  19. client, err := rest.RESTClientFor(config)
  20. if err != nil {
  21. log.Fatalln(err)
  22. }
  23. foo := &v1.Foo{}
  24. err = client.Get().Namespace("default").Resource("foos").Name("crd-test").Do(context.TODO()).Into(foo)
  25. if err != nil {
  26. log.Fatalln(err)
  27. }
  28. newObj := foo.DeepCopy()
  29. newObj.Spec.Name = "test2"
  30. fmt.Println(foo.Spec.Name)
  31. fmt.Println(newObj.Spec.Name)
  32. }
  33. //=======
  34. // 输出结果
  35. test
  36. test2