目录

使用code-generator生成crd的clientset、informer、listers

概念简介

code-generator

k8s.io/client-go 提供了对k8s原生资源的informer和clientset等等,但对于自定义资源的操作则相对低效,需要使用 rest api 和 dynamic client 来操作,并自己实现反序列化等功能。

code-generator 提供了以下工具用于为k8s中的资源生成相关代码,可以更加方便的操作自定义资源:

  • deepcopy-gen: 生成深度拷贝对象方法

    使用方法:

    • 在文件中添加注释// +k8s:deepcopy-gen=package
    • 为单个类型添加自动生成// +k8s:deepcopy-gen=true
    • 为单个类型关闭自动生成// +k8s:deepcopy-gen=false
  • client-gen: 为资源生成标准的操作方法(get;list;watch;create;update;patch;delete)

    在pkg/apis/${GROUP}/${VERSION}/types.go中使用,使用// +genclient标记对应类型生成的客户端, 如果与该类型相关联的资源不是命名空间范围的(例如PersistentVolume), 则还需要附加// + genclient:nonNamespaced标记,

    • // +genclient - 生成默认的客户端动作函数(create, update, delete, get, list, update, patch, watch以及 是否生成updateStatus取决于.Status字段是否存在)。
    • // +genclient:nonNamespaced - 所有动作函数都是在没有名称空间的情况下生成
    • // +genclient:onlyVerbs=create,get - 指定的动作函数被生成.
    • // +genclient:skipVerbs=watch - 生成watch以外所有的动作函数.
    • // +genclient:noStatus - 即使.Status字段存在也不生成updateStatus动作函数
  • informer-gen: 生成informer,提供事件机制(AddFunc,UpdateFunc,DeleteFunc)来响应kubernetes的event

  • lister-gen: 为get和list方法提供只读缓存层

  • conversion-gen是用于自动生成在内部和外部类型之间转换的函数的工具

    一般的转换代码生成任务涉及三套程序包:

    • 一套包含内部类型的程序包,
    • 一套包含外部类型的程序包
    • 单个目标程序包(即,生成的转换函数所在的位置,以及开发人员授权的转换功能所在的位置)。包含内部类型的包在Kubernetes的常规代码生成框架中扮演着称为peer package的角色。

    使用方法:

    • 标记转换内部软件包 // +k8s:conversion-gen=<import-path-of-internal-package>
    • 标记转换外部软件包// +k8s:conversion-gen-external-types=<import-path-of-external-package>
    • 标记不转换对应注释或结构 // +k8s:conversion-gen=false
  • defaulter-gen 用于生产Defaulter函数

    • 为包含字段的所有类型创建defaulters,// +k8s:defaulter-gen=<field-name-to-flag>
    • 所有都生成// +k8s:defaulter-gen=true|false
  • go-to-protobuf 通过go struct生成pb idl

  • import-boss 在给定存储库中强制执行导入限制

  • openapi-gen 生成openAPI定义

    使用方法:

    • +k8s:openapi-gen=true 为指定包或方法开启
    • +k8s:openapi-gen=false 指定包关闭
  • register-gen 生成register

  • set-gen

code-generator整合了这些gen,使用脚本generate-groups.shgenerate-internal-groups.sh可以为自定义资源生产相关代码。

kubebuilder

Kubebuilder是用于使用 自定义资源定义(CRD)构建Kubernetes API的框架。

类似于Ruby on RailsSpringBoot之类的Web开发框架,Kubebuilder可以提高速度并降低开发人员管理的复杂性,以便在Go中快速构建和发布Kubernetes API。它建立在用于构建核心Kubernetes API的规范技术的基础之上,以提供减少样板和麻烦的简单抽象。

Resource + Controller = Operator,可以利用Kubebuilder编写自定义资源的Operator。

结合背景

Kubebuildercode-generator 都可以为CRD生成Kubernetes API相关代码,从代码生成层面来讲, 两者的区别在于:

  • Kubebuilder不会生成informers、listers、clientsets,而code-generator会。
  • Kubebuilder会生成Controller、Admission Webhooks,而code-generator不会。
  • Kubebuilder会生成manifests yaml,而code-generator不会。
  • Kubebuilder还带有一些其他便利性设施。

使用Kubebuilder可以快捷生成CRD以及相关的控制器框架,然而由于Kubebuilder不会生成clientset等包,当别的服务想要操作CRD时将会很麻烦。

两者结合后可以使用Kubebuilder生成CRD和一整套控制器架构,再使用code-generator生成informers、listers、clientsets等。

初始化项目

创建一个项目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
[root@kubebuilder ~]# mkdir -p $GOPATH/src/my.domain/example
[root@kubebuilder ~]# cd $GOPATH/src/my.domain/example
[root@kubebuilder example]# kubebuilder init --domain my.domain 
[root@kubebuilder example]# tree -CL 2
.
├── config
│   ├── default
│   ├── manager
│   ├── prometheus
│   └── rbac
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT



创建一个api

 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
[root@kubebuilder example]# kubebuilder create api --group example --version v1 --kind Guestbook
Create Resource [y/n]
y
Create Controller [y/n]
y
...

[root@kubebuilder example]# 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:

1
make && make manifests

使用code-generator

更新依赖版本

初始化项目后的go.mod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[root@kubebuilder example]# cat go.mod
module my.domain

go 1.16

require (
	github.com/onsi/ginkgo v1.14.1
	github.com/onsi/gomega v1.10.2
	k8s.io/apimachinery v0.20.2
	k8s.io/client-go v0.20.2
	sigs.k8s.io/controller-runtime v0.8.3
)

需要将初始化的k8s库更新到要使用的版本,如:

1
2
3
[root@kubebuilder example]# K8S_VERSION=v0.18.6
[root@kubebuilder example]# go get k8s.io/client-go@$K8S_VERSION
[root@kubebuilder example]# go get k8s.io/apimachinery@$K8S_VERSION

安装code-generator

k8s的版本号与 go.mod 中的 k8s.io/client-go 的版本保持一致即可。

**注意:**需要将依赖复制到vendor中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
最新版本已经不包含 github.com/googleapis/gnostic/OpenAPIv2 如果不改版本会报如下错误
...
	github.com/googleapis/gnostic/OpenAPIv2: module github.com/googleapis/gnostic@latest found (v0.5.5), but does not contain package github.com/googleapis/gnostic/OpenAPIv2
Error: failed to create API: unable to run post-scaffold tasks of "base.go.kubebuilder.io/v3": exit status 1
...
[root@kubebuilder example]# go get github.com/googleapis/gnostic@v0.4.0

[root@kubebuilder example]# go get k8s.io/code-generator@$K8S_VERSION
[root@kubebuilder example]# go mod vendor

创建&修改所需文件

需要在api目录下创建code-generator所需的文件,并添加相关注释。

  • 新增 api/v1/doc.go

    **注意:**修改groupName,package与api的version保持一致。

    1
    2
    3
    4
    5
    6
    7
    
    [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保持一致。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    [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

1
2
3
4
5
6
7
8
[root@kubebuilder example]# cat api/v1/guestbook_types.go
...
// +genclient
//+kubebuilder:object:root=true

// GuestbookList contains a list of Guestbook
type GuestbookList struct {
...

准备脚本

在项目 hack 目录下准备以下文件:

  • 新建 hack/tools.go 文件

    1
    2
    3
    4
    5
    6
    
    [root@kubebuilder example]# cat hack/tools.go
    // +build tools
      
    package tools
      
    import _ "k8s.io/code-generator"
    
  • 新建 hack/update-codegen.sh,注意根据项目修改相应变量:

    MODULEgo.mod 保持一致

    API_PKG=api,和 api 目录保持一致

    OUTPUT_PKG=generated/example,与生成Resource时指定的group保持一致

    GROUP=example, 和生成Resource时指定的group 保持一致

    VERSION=v1, 和生成Resource时指定的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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
    [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}/../../.."
      
    

    注意:

    1. kubebuilder2.3.2版本生成的api目录结构code-generator无法直接使用,需要在sh脚本中进行处理。
    2. 修改脚本执行参数可以选择生成的代码,如:“client,informer,lister”。**注意无需再次生成deepcopy:**code-generator 生成的deepcopy不适配 kubebuilder 所生成的api。
  • 修改 Makefile ,添加生成命令

1
2
3
4
5
[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 即可,将生成如下代码结构:

 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
[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。