code-generator的简单介绍-《GO开发知识笔记》

admin 2025-11-04 01:01:18 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 代码生成器
    • 介绍
    • code-generator
      • 示例
    • 代码生成tag
      • 全局tag
      • 局部tag
    • 补充

    代码生成器

    介绍

    client-go为每种k8s内置资源提供了对应的clientset和informer。那么我们要监听和操作自定义资源对象,应该如何做呢?方式一:使用client-go提供的dynamicClient来操作自定义资源对象,当然由于dynamicClient是基于RESTClient实现的,所以我们可以使用RESTClient来达到同样的目的。方式二: 使用conde-generator来帮我们生成我们需要的代码,这样我们就可以像使用client-go为k8s内置资源对象提供的方式监听和操作自定义资源了。

    code-generator

    code-generator 就是 Kubernetes 提供的一个用于代码生成的项目,它提供了以下工具为 Kubernetes 中的资源生成代码:

    • deepcopy-gen: 生成深度拷贝方法,为每个 T 类型生成 func (t T) DeepCopy() T 方法,API 类型都需要实现深拷贝
    • client-gen: 为资源生成标准的 clientset
    • informer-gen: 生成 informer,提供事件机制来响应资源的事件
    • lister-gen: 生成 Lister,为 get 和 list 请求提供只读缓存层(通过 indexer 获取)

    Informer 和 Lister 是构建控制器的基础,使用这4个代码生成器可以创建全功能的、和 Kubernetes 上游控制器工作机制相同的 production-ready 的控制器。code-generator 还包含一些其它的代码生成器,例如 Conversion-gen 负责产生内外部类型的转换函数、Defaulter-gen 负责处理字段默认值。大部分的生成器支持—input-dirs参数来读取一系列输入包,处理其中的每个类型,然后生成代码: 1、部分代码生成到输入包所在目录,例如 deepcopy-gen 生成器,也可以使用参数—output-file-base “zz_generated.deepcopy” 来定义输出文件名 2、其它代码生成到 —output-package 指定的目录,例如 client-gen、informer-gen、lister-gen 等生成器

    示例

    接来下我们使用code-generator进行实战演示:首先我们将项目拉到本地:

    1. $ git clone https://github.com/kubernetes/code-generator.git
    2. $ git checkout 0.23.3

    然后我们进入到cmd目录下,就会看到我们上面介绍的工具:image.png接着我们对client-gen,deepcopy-gen,infromer-gen,lister-gen进行安装,会安装到GOPATH的bin目录下:

    1. # 进行安装
    2. $ go install ./cmd/{client-gen,deepcopy-gen,informer-gen,lister-gen}
    3. # 获取GOPATH路径
    4. $ go env | grep GOPATH
    5. GOPATH="/Users/Christian/go"
    6. # 查看
    7. ls /Users/Christian/go/bin
    8. client-gen deepcopy-gen goimports lister-gen
    9. controller-gen defaulter-gen informer-gen type-scaffold

    发现我们已经成功的安装了,这时候我们就可以直接使用这些工具了,比如我们可以使用—help命令来查看如何使用client-gen:image.png当然通常情况下我们不会去单独的使用某一个工具。接下来我们来创建我们的项目,此处我们可以仿照sample controller项目进行编写:

    1. $ mkdir operator-test && cd operator-test
    2. $ go mod init operator-test
    3. $ mkdir -p pkg/apis/example.com/v1
    4. ➜ operator-test tree
    5. .
    6. ├── go.mod
    7. ├── go.sum
    8. └── pkg
    9. └── apis
    10. └── example.com
    11. └── v1
    12. ├── doc.go
    13. ├── register.go
    14. └── types.go
    15. 4 directories, 5 files

    接下来我们对v1下面的三个go文件进行填充(可以直接复制sample-controller,对其进行做简单修改):doc.go主要是用来声明要使用deepconpy-gen以及groupName。

    1. // pkg/crd.example.com/v1/doc.go
    2. // +k8s:deepcopy-gen=package
    3. // +groupName=example.com
    4. package v1

    types.go主要是定义crd资源对应的go中的结构。

    1. // pkg/crd.example.com/v1/types.go
    2. package v1
    3. import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    4. // +genclient
    5. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    6. // Bar is a specification for a Bar resource
    7. type Bar struct {
    8. metav1.TypeMeta `json:",inline"`
    9. metav1.ObjectMeta `json:"metadata,omitempty"`
    10. Spec BarSpec `json:"spec"`
    11. // Status BarStatus `json:"status"`
    12. }
    13. // BarSpec is the spec for a Bar resource
    14. type BarSpec struct {
    15. DeploymentName string `json:"deploymentName"`
    16. Image string `json:"image"`
    17. Replicas *int32 `json:"replicas"`
    18. }
    19. // BarStatus is the status for a Bar resource
    20. type BarStatus struct {
    21. AvailableReplicas int32 `json:"availableReplicas"`
    22. }
    23. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    24. // BarList is a list of Bar resources
    25. type BarList struct {
    26. metav1.TypeMeta `json:",inline" :"metav1.TypeMeta"`
    27. metav1.ListMeta `json:"metadata" :"metav1.ListMeta"`
    28. Items []Bar `json:"items" :"items"`
    29. }

    register.go顾名思义,就是注册资源。

    1. package v1
    2. import (
    3. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    4. "k8s.io/apimachinery/pkg/runtime"
    5. "k8s.io/apimachinery/pkg/runtime/schema"
    6. )
    7. // SchemeGroupVersion is group version used to register these objects
    8. var SchemeGroupVersion = schema.GroupVersion{Group: "example.com", Version: "v1"}
    9. // Kind takes an unqualified kind and returns back a Group qualified GroupKind
    10. func Kind(kind string) schema.GroupKind {
    11. return SchemeGroupVersion.WithKind(kind).GroupKind()
    12. }
    13. // Resource takes an unqualified resource and returns a Group qualified GroupResource
    14. func Resource(resource string) schema.GroupResource {
    15. return SchemeGroupVersion.WithResource(resource).GroupResource()
    16. }
    17. var (
    18. // SchemeBuilder initializes a scheme builder
    19. SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    20. // AddToScheme is a global function that registers this API group & version to a scheme
    21. AddToScheme = SchemeBuilder.AddToScheme
    22. )
    23. // Adds the list of known types to Scheme.
    24. func addKnownTypes(scheme *runtime.Scheme) error {
    25. scheme.AddKnownTypes(SchemeGroupVersion,
    26. &Bar{},
    27. &BarList{},
    28. )
    29. metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    30. return nil
    31. }

    这时候会发现&Bar{},&BarLis{}会报错,这是因为我们还没有为其实现deepcopy方法。由于在自动生成代码的时候,需要指定header的信息,所以我们为了方便,可以将code-generator项目下的hack包直接拷贝到我们当前项目根目录下。接下来我们使用code-generator来为我们自动生成代码:

    1. # 运行 code-generator/generate-group.sh
    2. ./../../github/code-generator/generate-groups.sh all \
    3. # 指定 group 和 version,生成deeplycopy以及client
    4. operator-test/pkg/client operator-test/pkg/apis crd.example.com:v1 \
    5. # 指定头文件
    6. --go-header-file=./hack/boilerplate.go.txt \
    7. # 指定输出位置,默认为GOPATH
    8. --output-base ../
    9. Generating deepcopy funcs
    10. Generating clientset for crd.example.com:v1 at operator-test/pkg/client/clientset
    11. Generating listers for crd.example.com:v1 at operator-test/pkg/client/listers
    12. Generating informers for crd.example.com:v1 at operator-test/pkg/client/informers

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

    1. ➜ operator-test tree
    2. .
    3. ├── go.mod
    4. ├── go.sum
    5. ├── hack
    6. │ └── boilerplate.go.txt
    7. └── pkg
    8. ├── apis
    9. │ └── crd.example.com
    10. │ └── v1
    11. │ ├── doc.go
    12. │ ├── register.go
    13. │ ├── types.go
    14. │ └── zz_generated.deepcopy.go
    15. └── client
    16. ├── clientset
    17. │ └── versioned
    18. │ ├── clientset.go
    19. │ ├── doc.go
    20. │ ├── fake
    21. │ │ ├── clientset_generated.go
    22. │ │ ├── doc.go
    23. │ │ └── register.go
    24. │ ├── scheme
    25. │ │ ├── doc.go
    26. │ │ └── register.go
    27. │ └── typed
    28. │ └── crd.example.com
    29. │ └── v1
    30. │ ├── bar.go
    31. │ ├── crd.example.com_client.go
    32. │ ├── doc.go
    33. │ ├── fake
    34. │ │ ├── doc.go
    35. │ │ ├── fake_bar.go
    36. │ │ └── fake_crd.example.com_client.go
    37. │ └── generated_expansion.go
    38. ├── informers
    39. │ └── externalversions
    40. │ ├── crd.example.com
    41. │ │ ├── interface.go
    42. │ │ └── v1
    43. │ │ ├── bar.go
    44. │ │ └── interface.go
    45. │ ├── factory.go
    46. │ ├── generic.go
    47. │ └── internalinterfaces
    48. │ └── factory_interfaces.go
    49. └── listers
    50. └── crd.example.com
    51. └── v1
    52. ├── bar.go
    53. └── expansion_generated.go
    54. 22 directories, 29 files

    这时候我们就可以像操作内置资源一样,操作我们的自定义资源了。我们先准备crd以及对应的cr,这边也是可以直接从sample-controller项目进行拷贝,做简单的修改即可。

    1. # manifests/example.com_bars.yaml
    2. ---
    3. apiVersion: apiextensions.k8s.io/v1
    4. kind: CustomResourceDefinition
    5. metadata:
    6. annotations:
    7. controller-gen.kubebuilder.io/version: (devel)
    8. creationTimestamp: null
    9. name: bars.crd.example.com
    10. spec:
    11. group: crd.example.com
    12. names:
    13. kind: Bar
    14. listKind: BarList
    15. plural: bars
    16. singular: bar
    17. scope: Namespaced
    18. versions:
    19. - name: v1
    20. schema:
    21. openAPIV3Schema:
    22. description: Bar is a specification for a Bar resource
    23. properties:
    24. apiVersion:
    25. description: 'APIVersion defines the versioned schema of this representation
    26. of an object. Servers should convert recognized schemas to the latest
    27. internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
    28. type: string
    29. kind:
    30. description: 'Kind is a string value representing the REST resource this
    31. object represents. Servers may infer this from the endpoint the generated
    32. submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
    33. type: string
    34. metadata:
    35. type: object
    36. spec:
    37. description: BarSpec is the spec for a Bar resource
    38. properties:
    39. deploymentName:
    40. type: string
    41. image:
    42. type: string
    43. replicas:
    44. format: int32
    45. type: integer
    46. required:
    47. - deploymentName
    48. - image
    49. - replicas
    50. type: object
    51. required:
    52. - spec
    53. type: object
    54. served: true
    55. storage: true
    56. # manifests/cr.yaml
    57. ---
    58. apiVersion: crd.example.com/v1
    59. kind: Bar
    60. metadata:
    61. name: bar-demo
    62. namespace: default
    63. spec:
    64. image: "nginx:1.17.1"
    65. deploymentName: example-bar
    66. replicas: 2

    接下来我们来编写main函数,这时候我们就可以使用client-go像操作我们内置资源一样,操作crd资源了。

    1. package main
    2. import (
    3. "context"
    4. "fmt"
    5. v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    6. "k8s.io/client-go/tools/cache"
    7. "k8s.io/client-go/tools/clientcmd"
    8. "log"
    9. clientSet "operator-test/pkg/client/clientset/versioned"
    10. "operator-test/pkg/client/informers/externalversions"
    11. )
    12. func main() {
    13. config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
    14. if err != nil {
    15. log.Fatalln(err)
    16. }
    17. clientset, err := clientSet.NewForConfig(config)
    18. if err != nil {
    19. log.Fatalln(err)
    20. }
    21. list, err := clientset.CrdV1().Bars("default").List(context.TODO(), v1.ListOptions{})
    22. if err != nil {
    23. log.Fatalln(err)
    24. }
    25. for _, bar := range list.Items {
    26. fmt.Println(bar.Name)
    27. }
    28. factory := externalversions.NewSharedInformerFactory(clientset, 0)
    29. factory.Crd().V1().Bars().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    30. AddFunc: nil,
    31. UpdateFunc: nil,
    32. DeleteFunc: nil,
    33. })
    34. // todo
    35. }
    36. // ====
    37. // 程序输出结果:
    38. bar-demo

    代码生成tag

    在我们上面的示例中,我们在源码中添加了很多tag,我们使用这些tag来标记一些供生成器使用的属性。这些tag主要分为两类:

    • 在doc.go的package语句智商提供的全局tag
    • 在需要被处理的类型上提供局部tag

    tag的使用方法如下所示:

    1. // +tag-name
    2. // 或者
    3. // +tag-name=value

    我们可以看到 tag 是通过注释的形式存在的,另外需要注意的是 tag 的位置非常重要,很多 tag 必须直接位于 type 或 package 语句的上一行,另外一些则必须和 go 语句隔开至少一行空白。

    全局tag

    必须在目标包的doc.go文件中声明,一般路径为pkg/apis///doc.go,如下所示:

    1. // 为包中任何类型生成深拷贝方法,可以在局部 tag 覆盖此默认行为
    2. // +k8s:deepcopy-gen=package
    3. // groupName 指定 API 组的全限定名
    4. // 此 API 组的 v1 版本,放在同一个包中
    5. // +groupName=crd.example.com
    6. package v1

    注意:空行不能省略

    局部tag

    局部tag要么直接声明在类型之前,要么位于类型之前的第二个注释块中。下面的 types.go 中声明了 CR 对应的类型:

    1. // 为当前类型生成客户端,如果不加此注解则无法生成 lister、informer 等包
    2. // +genclient
    3. // 提示此类型不基于 /status 子资源来实现 spec-status 分离,产生的客户端不具有 UpdateStatus 方法
    4. // 否则,只要类型具有 Status 字段,就会生成 UpdateStatus 方法
    5. // +genclient:noStatus
    6. // 为每个顶级 API 类型添加,自动生成 DeepCopy 相关代码
    7. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    8. // K8S 资源,数据库
    9. type Database struct {
    10. metav1.TypeMeta `json:",inline"`
    11. metav1.ObjectMeta `json:"metadata,omitempty"`
    12. Spec DatabaseSpec `json:"spec"`
    13. }
    14. // 不为此类型生成深拷贝方法
    15. // +k8s:deepcopy-gen=false
    16. // 数据库的规范
    17. type DatabaseSpec struct {
    18. User string `json:"user"`
    19. Password string `json:"password"`
    20. Encoding string `json:"encoding,omitempty"`
    21. }
    22. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    23. // 数据库列表,因为 list 获取的是列表,所以需要定义该结构
    24. type DatabaseList struct {
    25. metav1.TypeMeta `json:",inline"`
    26. metav1.ListMeta `json:"metadata"`
    27. Items []Database `json:"items"`
    28. }

    在上面 CR 的定义上面就通过 tag 来添加了自动生成相关代码的一些注释。此外对于集群级别的资源,我们还需要提供如下所示的注释:

    1. // +genclient:nonNamespaced
    2. // 下面的 Tag 不能少
    3. // +genclient

    另外我们还可以控制客户端提供哪些 HTTP 方法:

    1. // +genclient:noVerbs
    2. // +genclient:onlyVerbs=create,delete
    3. // +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch
    4. // 仅仅返回 Status 而非整个资源
    5. // +genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/meta/v1.Status
    6. // 下面的 Tag 不能少
    7. // +genclient

    使用 tag 定义完需要生成的代码规则后,执行上面提供的代码生成脚本即可自动生成对应的代码了。

    补充

    除了上面介绍的代码生成方式,我们还可以直接使用sample-controller项目提供的hack/update-condegen.sh脚本。

    1. #!/usr/bin/env bash
    2. set -o errexit
    3. set -o nounset
    4. set -o pipefail
    5. SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
    6. # 代码生成器包的位置
    7. CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
    8. # generate-groups.sh <generators> <output-package> <apis-package> <groups-versions>
    9. # 使用哪些生成器,可选值 deepcopy,defaulter,client,lister,informer,逗号分隔,all表示全部使用
    10. # 输出包的导入路径
    11. # CR 定义所在路径
    12. # API 组和版本
    13. bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \
    14. k8s.io/sample-controller/pkg/generated k8s.io/sample-controller/pkg/apis \
    15. samplecontroller:v1alpha1 \
    16. --output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \
    17. --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt
    18. # 自动生成的源码头部附加的内容:
    19. # --go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt

    执行上面的脚本后,所有 API 代码会生成在 pkg/apis 目录下,clientsets、informers、listers 则生成在 pkg/generated 目录下。不过从脚本可以看出需要将 code-generator 的包放置到 vendor 目录下面,现在我们都是使用 go modules 来管理依赖保,我们可以通过执行 go mod vendor 命令将依赖包放置到 vendor 目录下面来。我们还可以进一步提供 hack/verify-codegen.sh 脚本,用于判断生成的代码是否 up-to-date:

    1. #!/usr/bin/env bash
    2. set -o errexit
    3. set -o nounset
    4. set -o pipefail
    5. # 先调用 update-codegen.sh 生成一份新代码
    6. # 然后对比新老代码是否一样
    7. SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
    8. DIFFROOT="${SCRIPT_ROOT}/pkg"
    9. TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg"
    10. _tmp="${SCRIPT_ROOT}/_tmp"
    11. cleanup() {
    12. rm -rf "${_tmp}"
    13. }
    14. trap "cleanup" EXIT SIGINT
    15. cleanup
    16. mkdir -p "${TMP_DIFFROOT}"
    17. cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}"
    18. "${SCRIPT_ROOT}/hack/update-codegen.sh"
    19. echo "diffing ${DIFFROOT} against freshly generated codegen"
    20. ret=0
    21. diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$?
    22. cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}"
    23. if [[ $ret -eq 0 ]]
    24. then
    25. echo "${DIFFROOT} up to date."
    26. else
    27. echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh"
    28. exit 1
    29. fi
    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  4