code-generator的简单介绍
代码生成器
介绍
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进行实战演示:
首先我们将项目拉到本地:
$ git clone https://github.com/kubernetes/code-generator.git
$ git checkout 0.23.3
然后我们进入到cmd目录下,就会看到我们上面介绍的工具:
接着我们对client-gen
,deepcopy-gen
,infromer-gen
,lister-gen
进行安装,会安装到GOPATH的bin目录下:
# 进行安装
$ go install ./cmd/{client-gen,deepcopy-gen,informer-gen,lister-gen}
# 获取GOPATH路径
$ go env | grep GOPATH
GOPATH="/Users/Christian/go"
# 查看
ls /Users/Christian/go/bin
client-gen deepcopy-gen goimports lister-gen
controller-gen defaulter-gen informer-gen type-scaffold
发现我们已经成功的安装了,这时候我们就可以直接使用这些工具了,比如我们可以使用--help
命令来查看如何使用client-gen
:
当然通常情况下我们不会去单独的使用某一个工具。
接下来我们来创建我们的项目,此处我们可以仿照sample controller项目进行编写:
$ mkdir operator-test && cd operator-test
$ go mod init operator-test
$ mkdir -p pkg/apis/example.com/v1
➜ operator-test tree
.
├── go.mod
├── go.sum
└── pkg
└── apis
└── example.com
└── v1
├── doc.go
├── register.go
└── types.go
4 directories, 5 files
接下来我们对v1下面的三个go文件进行填充(可以直接复制sample-controller,对其进行做简单修改):
doc.go主要是用来声明要使用deepconpy-gen
以及groupName。
// pkg/crd.example.com/v1/doc.go
// +k8s:deepcopy-gen=package
// +groupName=example.com
package v1
types.go主要是定义crd资源对应的go中的结构。
// pkg/crd.example.com/v1/types.go
package v1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Bar is a specification for a Bar resource
type Bar struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BarSpec `json:"spec"`
// Status BarStatus `json:"status"`
}
// BarSpec is the spec for a Bar resource
type BarSpec struct {
DeploymentName string `json:"deploymentName"`
Image string `json:"image"`
Replicas *int32 `json:"replicas"`
}
// BarStatus is the status for a Bar resource
type BarStatus struct {
AvailableReplicas int32 `json:"availableReplicas"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// BarList is a list of Bar resources
type BarList struct {
metav1.TypeMeta `json:",inline" :"metav1.TypeMeta"`
metav1.ListMeta `json:"metadata" :"metav1.ListMeta"`
Items []Bar `json:"items" :"items"`
}
register.go顾名思义,就是注册资源。
package v1
import (
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: "example.com", Version: "v1"}
// 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,
&Bar{},
&BarList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
这时候会发现&Bar{},&BarLis{}
会报错,这是因为我们还没有为其实现deepcopy
方法。
由于在自动生成代码的时候,需要指定header的信息,所以我们为了方便,可以将code-generator
项目下的hack
包直接拷贝到我们当前项目根目录下。
接下来我们使用code-generator
来为我们自动生成代码:
# 运行 code-generator/generate-group.sh
./../../github/code-generator/generate-groups.sh all \
# 指定 group 和 version,生成deeplycopy以及client
operator-test/pkg/client operator-test/pkg/apis crd.example.com:v1 \
# 指定头文件
--go-header-file=./hack/boilerplate.go.txt \
# 指定输出位置,默认为GOPATH
--output-base ../
Generating deepcopy funcs
Generating clientset for crd.example.com:v1 at operator-test/pkg/client/clientset
Generating listers for crd.example.com:v1 at operator-test/pkg/client/listers
Generating informers for crd.example.com:v1 at operator-test/pkg/client/informers
这时候我们再来查看项目结构:
➜ operator-test tree
.
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── pkg
├── apis
│ └── crd.example.com
│ └── v1
│ ├── doc.go
│ ├── register.go
│ ├── types.go
│ └── zz_generated.deepcopy.go
└── client
├── clientset
│ └── versioned
│ ├── clientset.go
│ ├── doc.go
│ ├── fake
│ │ ├── clientset_generated.go
│ │ ├── doc.go
│ │ └── register.go
│ ├── scheme
│ │ ├── doc.go
│ │ └── register.go
│ └── typed
│ └── crd.example.com
│ └── v1
│ ├── bar.go
│ ├── crd.example.com_client.go
│ ├── doc.go
│ ├── fake
│ │ ├── doc.go
│ │ ├── fake_bar.go
│ │ └── fake_crd.example.com_client.go
│ └── generated_expansion.go
├── informers
│ └── externalversions
│ ├── crd.example.com
│ │ ├── interface.go
│ │ └── v1
│ │ ├── bar.go
│ │ └── interface.go
│ ├── factory.go
│ ├── generic.go
│ └── internalinterfaces
│ └── factory_interfaces.go
└── listers
└── crd.example.com
└── v1
├── bar.go
└── expansion_generated.go
22 directories, 29 files
这时候我们就可以像操作内置资源一样,操作我们的自定义资源了。
我们先准备crd以及对应的cr,这边也是可以直接从sample-controller
项目进行拷贝,做简单的修改即可。
# manifests/example.com_bars.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: bars.crd.example.com
spec:
group: crd.example.com
names:
kind: Bar
listKind: BarList
plural: bars
singular: bar
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: Bar is a specification for a Bar resource
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 generated
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: BarSpec is the spec for a Bar resource
properties:
deploymentName:
type: string
image:
type: string
replicas:
format: int32
type: integer
required:
- deploymentName
- image
- replicas
type: object
required:
- spec
type: object
served: true
storage: true
# manifests/cr.yaml
---
apiVersion: crd.example.com/v1
kind: Bar
metadata:
name: bar-demo
namespace: default
spec:
image: "nginx:1.17.1"
deploymentName: example-bar
replicas: 2
接下来我们来编写main函数,这时候我们就可以使用client-go像操作我们内置资源一样,操作crd资源了。
package main
import (
"context"
"fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"log"
clientSet "operator-test/pkg/client/clientset/versioned"
"operator-test/pkg/client/informers/externalversions"
)
func main() {
config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
if err != nil {
log.Fatalln(err)
}
clientset, err := clientSet.NewForConfig(config)
if err != nil {
log.Fatalln(err)
}
list, err := clientset.CrdV1().Bars("default").List(context.TODO(), v1.ListOptions{})
if err != nil {
log.Fatalln(err)
}
for _, bar := range list.Items {
fmt.Println(bar.Name)
}
factory := externalversions.NewSharedInformerFactory(clientset, 0)
factory.Crd().V1().Bars().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: nil,
UpdateFunc: nil,
DeleteFunc: nil,
})
// todo
}
// ====
// 程序输出结果:
bar-demo
代码生成tag
在我们上面的示例中,我们在源码中添加了很多tag
,我们使用这些tag
来标记一些供生成器使用的属性。这些tag
主要分为两类:
- 在
doc.go
的package语句智商提供的全局tag - 在需要被处理的类型上提供局部tag
tag的使用方法如下所示:
// +tag-name
// 或者
// +tag-name=value
我们可以看到 tag 是通过注释的形式存在的,另外需要注意的是 tag 的位置非常重要,很多 tag 必须直接位于 type 或 package 语句的上一行,另外一些则必须和 go 语句隔开至少一行空白。
全局tag
必须在目标包的doc.go
文件中声明,一般路径为pkg/apis/<apigroup>/<version>/doc.go
,如下所示:
// 为包中任何类型生成深拷贝方法,可以在局部 tag 覆盖此默认行为
// +k8s:deepcopy-gen=package
// groupName 指定 API 组的全限定名
// 此 API 组的 v1 版本,放在同一个包中
// +groupName=crd.example.com
package v1
注意:空行不能省略
局部tag
局部tag要么直接声明在类型之前,要么位于类型之前的第二个注释块中。下面的 types.go 中声明了 CR 对应的类型:
// 为当前类型生成客户端,如果不加此注解则无法生成 lister、informer 等包
// +genclient
// 提示此类型不基于 /status 子资源来实现 spec-status 分离,产生的客户端不具有 UpdateStatus 方法
// 否则,只要类型具有 Status 字段,就会生成 UpdateStatus 方法
// +genclient:noStatus
// 为每个顶级 API 类型添加,自动生成 DeepCopy 相关代码
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// K8S 资源,数据库
type Database struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DatabaseSpec `json:"spec"`
}
// 不为此类型生成深拷贝方法
// +k8s:deepcopy-gen=false
// 数据库的规范
type DatabaseSpec struct {
User string `json:"user"`
Password string `json:"password"`
Encoding string `json:"encoding,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// 数据库列表,因为 list 获取的是列表,所以需要定义该结构
type DatabaseList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Database `json:"items"`
}
在上面 CR 的定义上面就通过 tag 来添加了自动生成相关代码的一些注释。此外对于集群级别的资源,我们还需要提供如下所示的注释:
// +genclient:nonNamespaced
// 下面的 Tag 不能少
// +genclient
另外我们还可以控制客户端提供哪些 HTTP 方法:
// +genclient:noVerbs
// +genclient:onlyVerbs=create,delete
// +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch
// 仅仅返回 Status 而非整个资源
// +genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/meta/v1.Status
// 下面的 Tag 不能少
// +genclient
使用 tag 定义完需要生成的代码规则后,执行上面提供的代码生成脚本即可自动生成对应的代码了。
补充
除了上面介绍的代码生成方式,我们还可以直接使用sample-controller
项目提供的hack/update-condegen.sh脚本。
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
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)}
# generate-groups.sh <generators> <output-package> <apis-package> <groups-versions>
# 使用哪些生成器,可选值 deepcopy,defaulter,client,lister,informer,逗号分隔,all表示全部使用
# 输出包的导入路径
# CR 定义所在路径
# API 组和版本
bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \
k8s.io/sample-controller/pkg/generated k8s.io/sample-controller/pkg/apis \
samplecontroller:v1alpha1 \
--output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \
--go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt
# 自动生成的源码头部附加的内容:
# --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:
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# 先调用 update-codegen.sh 生成一份新代码
# 然后对比新老代码是否一样
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
DIFFROOT="${SCRIPT_ROOT}/pkg"
TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg"
_tmp="${SCRIPT_ROOT}/_tmp"
cleanup() {
rm -rf "${_tmp}"
}
trap "cleanup" EXIT SIGINT
cleanup
mkdir -p "${TMP_DIFFROOT}"
cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}"
"${SCRIPT_ROOT}/hack/update-codegen.sh"
echo "diffing ${DIFFROOT} against freshly generated codegen"
ret=0
diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$?
cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}"
if [[ $ret -eq 0 ]]
then
echo "${DIFFROOT} up to date."
else
echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh"
exit 1
fi
code-generator的简单介绍的更多相关文章
- [转]OData的初步认识 OData v4 Client Code Generator
本文转自:http://www.cnblogs.com/1zhk/p/5356053.html What – OData是什么? OData - Open Data Protocol,是一个设计和使用 ...
- ABP配套代码生成器(ABP Code Generator)帮助文档,实现快速开发
ABP代码生成器介绍 针对abp这个框架做了一个代码生成器,功能强大.分为两大功能点,一个是数据层,一个是视图层. 数据服务层:通过它,可以实现表设计.领域层初始化.多语言.automapper自动注 ...
- RUF MVC5 Repositories Framework Generator代码生成工具介绍和使用
RUF MVC5 Repositories Framework Generator代码生成工具介绍和使用 功能介绍 这个项目经过了大半年的持续更新到目前的阶段基本稳定 所有源代码都是开源的,在gith ...
- Linux curl使用简单介绍
在两台新搬迁的微信服务器上执行命令: curl -H "Content-Type: application/json" -d '{"partner_no":&q ...
- SQLite数据库和JPA简单介绍
SQLite数据库和JPA简单介绍 一.SQLite简单使用 SQLite是遵循ACID的关系数据库管理系统,它的处理速度很快,它的设计目标是嵌入式的,只需要几百K的内存就可以了. 1.下载SQLit ...
- NSDate简单介绍
NSDate简单介绍 一:NSDate是一个日期\时间方面的类,主要用来创建\获取时间 1.NSDate对象的创建: date 创建一个当前系统日期和时间的对象 dateWithTimeInterva ...
- hive中简单介绍分区表
所介绍内容基本上是翻译官方文档,比较肤浅,如有错误,请指正! hive中创建分区表没有什么复杂的分区类型(范围分区.列表分区.hash分区.混合分区等).分区列也不是表中的一个实际的字段,而是一个或者 ...
- Epplus 使用的简单介绍
操作Excel的主要有以下类库: MyXls(http://sourceforge.net/projects/myxls/) Koogra(http://sourceforge.net/project ...
- MongoDB入门简单介绍
有关于MongoDB的资料如今较少,且大多为英文站点,以上内容大多由笔者翻译自官网,请翻译或理解错误之处请指证.之后笔者会继续关注MongoDB,并翻译“Developer Zone”和“Admin ...
- Linux 内核开发—内核简单介绍
内核简单介绍 Linux 构成 Linux 为什么被划分为系统空间和内核空间 隔离核心程序和应用程序,实现对核心程序和数据的保护. 什么内核空间,用户空间 内核空间和用户空间是程序执行的两种不同的状态 ...
随机推荐
- STM32 学习方法
前言 学习知识要掌握有效的学习方法,学习技术也是一样,本篇分享关于我学习 STM32 后总结的学习方法. 推荐的学习方法 系统学习 在网上购买一款开发板,使用开发板+开发板配套视频教程+开发板配套源码 ...
- 冒泡排序(LOW)
博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ import random def bubble_sort(li): for i ...
- oracle服务 linux启动命令
一.Linux下启动Oracle Linux下启动Oracle分为两步: 1)启动监听: 2)启动数据库实例: 1.登录服务器,切换到oracle用户,或者以oracle用户登录 [admin@dat ...
- DevOps - DevOps基础概念梳理
目录 DevOps DevOps的好处与价值 可能的几个关注点 DevOps能力环 DevOps与持续集成.持续交付 一个完整的过程 典型的CICD流水线过程 DevOps黄金思维圈 初见 一些图示 ...
- ORA-01779: 无法修改与非键值保存表对应的列”中涉及的概念和解决方法
什么是键值保存表(Key-Preserved Table)? 在理解什么是键值保存表之前,首先要知道 可更新的联接视图 这个概念,键值保存表只是保存了允许更新的字段信息的一张表.为什么会出现这么一张表 ...
- study Rust-1【Rust的特点和应用场景】
Rust语言的特点 高性能 - Rust 速度惊人且内存利用率极高.由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成. 可靠性 - Rust ...
- AndrodStudio构建时报错Could not find com.android.tools.build:gradle:xxx
检查配置的版本号在maven仓库里有没有. maven仓库地址: https://repo1.maven.org/maven2/com/android/tools/build/gradle/ 选择需要 ...
- Launchpool名词解释
# 一.什么是Launchpool Launchpool是一种加密货币领域的创新机制,通常由交易所或DeFi平台提供,允许用户通过质押(staking)或锁定特定代币来获得新项目的代币奖励. ## L ...
- Web前端入门第 34 问:CSS 常见布局
Web 网页中,所有元素都是盒模型构成的,一个大盒子套一个或者多个小盒子,再用更大的盒子把大盒子给圈起来,这就构成了基本的 HTML 结构,再利用 CSS 把盒子装修得好看一些,最后把它放在正确位置, ...
- 重生之我是操作系统(十)----I/O管理
简介 操作系统的I/O管理(input/output mannagment)是协调,控制计算机与外部设备(如磁盘,键盘,网络接口)等之间数据交换的核心功能.实现可靠高效且统一(隐藏设备差异,如磁盘.串 ...