Kubernetes operator(三)code-generator 篇

云原生学习路线导航页(持续更新中)

  • 本文是 Kubernetes operator学习 系列第三篇,主要对 使用 code-generator 进行 CRD 自动代码生成进行学习
  • Kubernetes operator学习系列 快捷链接
    • Kubernetes operator(一)client-go篇
    • Kubernetes operator(二)CRD篇
    • Kubernetes operator(三)code-generator 篇
    • Kubernetes operator(四)controller-tools 篇

1.code-generator 简介

1.1.存在的问题

  • 在 client-go篇 文章中,我们详细讲述了 如何使用 RESTClient、ClientSet 操作kubernetes的内建资源。然而,对于CRD资源,我们只能使用 DynamicClient 这种通用的Client去操作。
  • DynamicClient 是为操作所有资源而编写的通用Client,自然没有 某种资源 对应的Informer、Lister代码,所以使用DynamicClient,就无法像 使用ClientSet 那样,操作某一个特定资源的Informer、Lister等。
  • 为此我们想,能否为 CRD资源 生成和内建资源一样的Informer、Lister代码,这样的话,开发控制器的时候,我们就可以像使用内建资源一样,为CRD的informer注册事件方法,并使用Lister从缓存中获取数据。

1.2.code-generator是什么

  • kubernetes的开发中,有很多代码是相似而重复的,开发起来耗时耗力,因此就希望制作成了自动化工具,提高代码的可维护性和一致性。code-generator应运而生。

  • code-generator 是一个 代码生成工具集合,内部包含很多gen工具,用于自动生成与 Kubernetes 相关的客户端库、API 服务器(API server)和其他与 Kubernetes 相关的代码。
    Kubernetes operator(三)code-generator 篇_第1张图片

  • code-generator 位于 Kubernetes 代码库中的 staging/src/k8s.io/code-generator 包中,github地址为:https://github.com/kubernetes/code-generator

  • code-generator 的主要功能包括:

    • 自动生成客户端库clientset:通过定义自定义资源的 API 规范,code-generator 可以自动生成用于访问和操作这些自定义资源的客户端库。
    • 自动生成 API 服务器代码:code-generator 可以根据自定义资源的 API 规范,自动生成与之对应的 API 服务器代码。这使得开发者可以快速构建自己的自定义资源的 API 服务器,并将其部署到 Kubernetes 集群中。
    • 自动生成 DeepCopy 方法:对于 Kubernetes 对象,深拷贝是一种常见的操作,用于创建对象的副本。
    • 自动生成其他辅助代码:除了上述功能外,code-generator 还可以生成其他与 Kubernetes 相关的辅助代码,如列表(List)、转换器(Converter)等。

1.3.code-generator的常见应用场景

  • 为CRD编写自定义controller时,可以使用它来生成我们需要的versioned clientinformerlister以及其他工具方法
  • 编写自定义API Server时,可以用它来生成 internalversioned类型的转换defaultersinternalversionedclientsinformers

我们这里主要学习CRD编写自定义controller的场景

2.code-generator 的使用方法

  • code-generator是使用注释标记工作的,不同的gen工具,有不同的注释标记

2.1.code-generator 常用的gen工具

Kubernetes operator(三)code-generator 篇_第2张图片

  • deepcopy-gen:为每个 T 类型生成 func (t* T) DeepCopy() *T 方法,API 类型都需要实现深拷贝
  • client-gen:生成类型化客户端集合(typed client sets),即与 Kubernetes API 服务器通信的客户端代码。
  • informer-gen:用于生成基于 Kubernetes API 资源的 Informer,提供事件机制来响应资源的事件,方便开发者执行资源监视操作。
  • lister-gen:用于生成 Kubernetes API 资源的 Lister,为 get 和 list 请求提供只读缓存层(通过 indexer 获取),可以实现高效的资源列表查询。
  • register-gen:自动生成 API 资源类型的注册表代码
  • conversion-gen:用于生成 Kubernetes 对象之间的转换器(Converter),方便在不同版本之间进行对象转换。
  • openapi-gen:用于生成 Kubernetes API 的 OpenAPI 规范,以便进行文档化和验证。

2.2.code-generator标记Tag

2.2.1.tag分类

  • code-generator 将 tag 分为了两种:
    • Global tags: 全局的tag,放在具体版本的doc.go文件中
    • Local tags: 本地的tag,放在types.go文件中的具体的struct上

2.2.2.tag使用语法

  • 使用语法:
    // +tag-name // +tag-name=value
    

2.3.deepcopy-gen的常用标记

  • 在要生成 client 代码的类型type上方使用
    告诉代码生成器不生成该对象的深拷贝方法
    // +k8s:deepcopy-gen=false
    
    告诉代码生成器生成该对象的深拷贝方法
    // +k8s:deepcopy-gen=true
    
    指定生成的代码中实现的接口,这里指定了实现 k8s.io/apimachinery/pkg/runtime.Object 接口的代码
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
  • 在doc.go文件中使用
    告诉代码生成器生成整个包的对象深拷贝方法。这意味着生成的代码将包含用于深拷贝包中所有对象的相关代码
    // +k8s:deepcopy-gen=package
    
    指定自定义 API 组的名称。这个注释用于定义自定义 API 资源的 API 组,使其能够与其他资源进行区分
    // +groupName=foo.example.com
    
    package v1
    

2.4.client-gen的常用标记

  • 在要生成 client 代码的类型type上方使用
    指示代码生成器生成用于客户端库的代码
    // +genclient
    
    不要为自定义资源生成状态字段相关的代码
    // +genclient:noStatus
     
    表示这是一个集群级别的资源
    // +genclient:nonNamespaced
    
    不要生成默认的 HTTP 动词(verbs)相关的代码,这可能是因为该资源的操作方式与常规的 RESTful 操作并不完全相同
    // +genclient:noVerbs
    
    指示代码生成器只生成用于创建和删除操作的代码
    // +genclient:onlyVerbs=create,delete
    
    告诉代码生成器跳过指定的动词相关的代码生成,这表明生成的客户端库将不包含与这些操作相关的代码
    // +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch
    
    为特定的方法指定生成代码的细节,这里指定了当执行创建操作时所使用的方法以及其返回结果
    // +genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/meta/v1.Status
    

2.5.informer-gen的常用标记

  • 在要生成 client 代码的类型type上方使用
    使用informer-gen必须要写这一行
    // +k8s:informers
    
    指定生成的 Informer 代码对应的 API 组和版本
    // +k8s:informers:groupVersion=${group}/${version}
    
    指定生成的 Informer 代码对应的内部 API 版本
    // +k8s:informers:internalVersion=internal/version
    
    指定生成的 Informer 代码是否需要为版本化的客户端集(Versioned ClientSet)生成代码
    // +k8s:informers:versionedClientSet=false
    
    指定生成的 Informer 代码是否需要使用缓存
    // +k8s:informers:cache
    

2.6.code-generator的运行脚本

  • 手动使用code-generator中某个gen工具,存在很多重复参数,也比较繁琐
  • 因此 code-generator 为我们提供了 两个脚本:generate-groups.shgenerate-internal-groups.sh
  • 在kubernetes 1.28 alpha 之后。提供了表述更清晰更好维护的脚本 kube_codegen.sh
    Kubernetes operator(三)code-generator 篇_第3张图片

3.kubernetes 对外API结构

  • K8s 相关项目对外 API 都会包含 3 文件:
    • types.go:编写crd的资源结构,code-generator 的注释 tag一般写在这边里面
    • doc.go:声明了按照 package 维度,为所有 structs 提供生成的声明
    • register.go:提供注册到 runtime.Scheme 的函数。code-generator 的 register-gen 可以读取 doc.go 中的 // +groupName,并生成 register.go

4.code-generator实战:为crd生成代码

  • 本文源码已放入仓库:https://github.com/graham924/share-code-operator-study
    Kubernetes operator(三)code-generator 篇_第4张图片

4.1.初始化go项目

# 创建并进入项目目录
mkdir code-generator-demo && cd code-generator-demo

# 初始化项目
go mod init code-generator-demo

# 获取依赖
go get k8s.io/[email protected]
go get k8s.io/[email protected]
go get k8s.io/[email protected]

4.2.创建crd相关目录和文件

  • 我们希望创建的资源,groupName为appcontroller.k8s.io,版本为v1alpha1
  • 创建所需目录及文件
    Kubernetes operator(三)code-generator 篇_第5张图片

4.3.编写doc.go

// +k8s:deepcopy-gen=package
// +groupName=appcontroller.k8s.io

// Package v1alpha1 v1alpha1版本的api包
package v1alpha1

4.4.编写types.go

package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type Application struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ApplicationSpec   `json:"spec"`
	Status ApplicationStatus `json:"status"`
}

type ApplicationSpec struct {
	DeploymentName string `json:"deploymentName"`
	Replicas       *int32 `json:"replicas"`
}

type ApplicationStatus struct {
	AvailableReplicas int32 `json:"availableReplicas"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ApplicationList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Application `json:"items"`
}

4.5.编写register.go

  • 注意,这是appcontroller目录下的register.go,并非某个版本目录下的,版本目录下的,再使用 code-generator 脚本后,再进行编写
    package appcontroller
    
    const (
    	GroupName = "appcontroller.k8s.io"
    )
    

4.6.编写boilerplate.go.txt

  • 该文件是文件开头统一的注释,会在使用 code-generator 脚本时,指定 boilerplate.go.txt 文件的所在目录
    /*
    Copyright 2019 The Kubernetes Authors.
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    
    @Time : 2024/1
    @Author : grahamzhu
    @Software: GoLand
    */
    

4.7.编写tools.go

  • 我们要使用 code-generator,可代码中还没有任何位置 导入过 code-generator 的包,所以我们需要一个类,专门用于将 code-generator 的包导入。一般使用tools.go来做这件事
    //go:build tools
    // +build tools
    
    /*
    Copyright 2019 The Kubernetes Authors.
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    */
    
    // This package imports things required by build scripts, to force `go mod` to see them as dependencies
    package tools
    
    import _ "k8s.io/code-generator"
    

4.8.编写使用code-generator的脚本update-codegen.sh

#!/usr/bin/env bash

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# 设置脚本在执行过程中遇到任何错误时立即退出
set -o errexit
# 设置脚本在使用未定义的变量时立即退出
set -o nounset
# 设置脚本在管道命令中任意一条命令失败时立即退出
set -o pipefail

# 对generate-groups.sh 脚本的调用
../vendor/k8s.io/code-generator/generate-groups.sh \
  "deepcopy,client,informer,lister" \
  code-generator-demo/pkg/generated \
  code-generator-demo/pkg/apis \
  appcontroller:v1alpha1 \
  --go-header-file $(pwd)/boilerplate.go.txt \
  --output-base $(pwd)/../../

解释每个参数和选项的含义:

  • "deepcopy,client,informer,lister":指定要生成的代码类型,这里包括深拷贝方法、客户端、informer 和 lister。
  • code-generator-demo/pkg/generated:指定生成的代码的输出目录。
  • code-generator-demo/pkg/apis:指定包含 API 定义的目录。
  • appcontroller:v1alpha1:指定要生成的 API 版本和组。
  • --go-header-file $(pwd)/boilerplate.go.txt:指定用于生成的代码文件头部的文本文件,这里使用了当前目录下的 boilerplate.go.txt 文件。
  • --output-base $(pwd)/../../:指定生成的代码的基础目录,即输出目录的上一级目录。

总结起来,该命令的作用是调用 Kubernetes 代码生成器脚本,根据指定的 API 定义和选项生成深拷贝方法、客户端、informer 和 lister 等代码,并将生成的代码输出到指定的目录中。

4.9.执行脚本生成代码

  • 将代码上传至linux环境下,依次执行如下命令:
    go mode tidy
    
    # 生成vendor文件夹
    go mod vendor
    
    # 为vendor中的code-generator赋予权限
    chmod -R 777 vendor
    
    # 为hack中的update-codegen.sh脚本赋予权限
    chmod -R 777 hack
    
    # 调用脚本生成代码
    $ cd hack && ./update-codegen.sh
    Generating deepcopy funcs
    Generating clientset for appcontroller:v1alpha1 at code-generator-demo/pkg/generated/clientset
    Generating listers for appcontroller:v1alpha1 at code-generator-demo/pkg/generated/listers
    Generating informers for appcontroller:v1alpha1 at code-generator-demo/pkg/generated/informers
    
    #此时目录变为如下情况
    $ cd ../ && tree -L 5
    .
    ├── go.mod
    ├── go.sum
    ├── hack
    │   ├── boilerplate.go.txt
    │   ├── tools.go
    │   └── update-codegen.sh
    ├── pkg
    │   ├── apis
    │   │   └── appcontroller
    │   │       ├── register.go
    │   │       └── v1alpha1
    │   │           ├── doc.go
    │   │           ├── types.go
    │   │           └── zz_generated.deepcopy.go
    │   └── generated
    │       ├── clientset
    │       │   └── versioned
    │       │       ├── clientset.go
    │       │       ├── doc.go
    │       │       ├── fake
    │       │       ├── scheme
    │       │       └── typed
    │       ├── informers
    │       │   └── externalversions
    │       │       ├── appcontroller
    │       │       ├── factory.go
    │       │       ├── generic.go
    │       │       └── internalinterfaces
    │       └── listers
    │           └── appcontroller
    │               └── v1alpha1
    └── vendor
        ├── github.com
    

4.10.手动注册版本v1alpha1的CRD资源

  • 在生成了客户端代码后,我们还需要手动注册版本v1alpha1的CRD资源,才能真正使用这个client,不然在编译时会出现 undefined: v1alpha1.AddToScheme 错误、undefined: v1alpha1.Resource 错误。
  • v1alpha1.AddToSchemev1alpha1.Resource 这两个是用于 client 注册的
  • 编写 v1alpha1/register.go
    package v1alpha1
    
    import (
    	"code-generator-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
    }
    

4.11.测试CRD资源的使用

  • 在项目根目录下,创建一个cmd目录,里面创建一个main.go文件
  • 下面我们演示如何使用 code-generator 为 Application 的 v1alpha1 生成的客户端 AppcontrollerV1alpha1Client
  • 编写main.go
    package main
    
    import (
    	"code-generator-demo/pkg/generated/clientset/versioned/typed/appcontroller/v1alpha1"
    	"fmt"
    	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/client-go/tools/clientcmd"
    )
    
    func main() {
    	config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
    	if err != nil {
    		panic(err.Error())
    	}
    
    	// 在 pkg/generated/clientset/versioned/typed/appcontroller/v1alpha1/appcontroller_client.go 中
    	// 自动生成的 AppcontrollerV1alpha1Client,用于操作这种GVR
    	appClient, err := v1alpha1.NewForConfig(config)
    	if err != nil {
    		panic(err.Error())
    	}
    
    	// 获取default命名空间下的所有Application
    	appList, err := appClient.Applications("default").List(metav1.ListOptions{})
    	if err != nil {
    		panic(err.Error())
    	}
    
    	for _, app := range appList.Items {
    		fmt.Println(app.Name)
    	}
    }
    

5.code-generator编写CRD控制器的缺点

  • 上面的项目,存在的问题:

    • 问题一:如果要在kubernetes集群中,运行上面编写的项目,还需要手动编写CRD的yaml,即让集群可以认识Application的CRD。否则,直接执行项目,是找不到 Application 资源的。
    • 问题二:types.go文件全部内容都需要我们手写,无法自动生成框架
  • 解决方法:

    • 有一个工具,controller-tools,可以解决这些问题,自动生成 crd+controller 框架
    • 请移步下一小节:Kubernetes operator(四)controller-tools 篇

5.参考优质博客

  • Kubernetes Deep Dive: Code Generation for CustomResources
  • code-generator使用
  • 使用code-generator生成crd的clientset、informer、listers
  • kubernetes开发教程(4)–自动代码生成
  • 最不厌其烦的 K8s 代码生成教程

你可能感兴趣的:(云原生学习专栏,kubernetes,容器,云原生,golang)