使用 code-generator 为 CustomResources 生成代码

kubernetes 项目中有相当一部分代码是自动生成的,主要是 API 的定义和调用方法,kubernetes 项目下 k8s.io/kubernetes/hack/ 目录中以 update 开头的大部分脚本都是用来生成代码的。code-generator 是官方提供的代码生成工具,在实现自定义 controller 的时候需要用到 CRD,也需要使用该工具生成对 CRD 操作的代码。

要生成哪些代码

在自定义 controller 时需要用到 typed clientsets,informers,listers 和 deep-copy 等函数,这些函数都可以使用 code-generator 来生成,具体的作用可以参考:kubernetes 中 informer 的使用

code-generator 里面包含多个生成代码的工具,下面是需要用到的几个:

  • deepcopy-gen:为每种类型T生成方法: func (t* T) DeepCopy() *T,CustomResources 必须实现runtime.Object 接口且要有 DeepCopy 方法

  • client-gen:为 CustomResource APIGroups 生成 typed clientsets

  • informer-gen:为 CustomResources 创建 informers,用来 watch 对应 CRD 所触发的事件,以便对 CustomResources 的变化进行对应的处理

  • lister-gen:为 CustomResources 创建 listers,用来对 GET/List 请求提供只读的缓存层

除了上面几个工具外,code-generator 中还提供了 conversion-gen、defaulter-gen、register-gen、set-gen,这些生成器可以应用在其他场景,比如构建聚合 API 服务时会用到一些内部的类型,conversion-gen 会为这些内部和外部类型之间创建转换函数,defaulter-gen 会处理某些字段的默认值。

代码生成步骤

使用 code-generator 生成代码还需要以下几步:

  • 创建指定的目录格式
  • 在代码中使用 tag 标注要生成哪些代码

首先要创建指定的目录格式,目录的格式可以参考官方提供的示例项目:sample-controller,下文也会讲到,目录中需要包含对应 CustomResources 的定义以及 group 和 version 信息。

其次要在在代码中使用 tag 标注要生成哪些代码,tag 有两钟类型,全局的和局部的,所有类型的 deepcopy tag 会默认启用,更多关于 tag 的使用方法可以参考:Kubernetes Deep Dive: Code Generation for CustomResources,也可以参考官方的示例 code-generator/_example

开始生成代码

本文以该 CRD 为例子进行演示,group 为ecs.yun.com ,version 为 v1

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: kubernetesclusters.ecs.yun.com
spec:
  group: ecs.yun.com
  names:
    kind: KubernetesCluster
    listKind: KubernetesClusterList
    plural: kubernetesclusters
    singular: kubernetescluster
    shortNames:
    - ecs
  scope: Namespaced
  subresources:
    status: {}
  version: v1
  versions:
  - name: v1
    served: true
    storage: true

创建指定目录结构 pkg/apis/{group}/{version},group 可以定义一个 shortNames,也就是 CRD 中的 shortNames

$ mkdir -pv pkg/apis/ecs/v1

创建 doc.go:

$ cat << EOF > pkg/apis/ecs/v1/doc.go
// Package v1 contains API Schema definitions for the ecs v1 API group
// +k8s:deepcopy-gen=package,register
// +groupName=ecs.yun.com
package v1
EOF

创建 register.go:

$ cat << EOF > pkg/apis/ecs/v1/register.go
package v1

import (
    "github.com/gosoon/kubernetes-operator/pkg/apis/ecs"

    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: ecs.GroupName, 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 = runtime.NewSchemeBuilder(addKnownTypes)
    AddToScheme   = SchemeBuilder.AddToScheme
)

// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
    scheme.AddKnownTypes(SchemeGroupVersion,
        &KubernetesCluster{},
        &KubernetesClusterList{},
    )
    metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    return nil
}
EOF

创建 types.go,该文件中会定义多个 tag

$ cat << EOF > pkg/apis/ecs/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

// KubernetesCluster is the Schema for the kubernetesclusters API
type KubernetesCluster struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   KubernetesClusterSpec   `json:"spec,omitempty"`
    Status KubernetesClusterStatus `json:"status,omitempty"`
}

// KubernetesClusterSpec defines the desired state of KubernetesCluster
type KubernetesClusterSpec struct {
    // Add custom validation using kubebuilder tags:    
    // https://book.kubebuilder.io/beyond_basics/generating_crd.html
    TimeoutMins   string     `json:"timeout_mins,omitempty"`
    ClusterType   string     `json:"clusterType,omitempty"`
    ContainerCIDR string     `json:"containerCIDR,omitempty"`
    ServiceCIDR   string     `json:"serviceCIDR,omitempty"`
    MasterList    []Node     `json:"masterList" tag:"required"`
    MasterVIP     string     `json:"masterVIP,omitempty"`
    NodeList      []Node     `json:"nodeList" tag:"required"`
    EtcdList      []Node     `json:"etcdList,omitempty"`
    Region        string     `json:"region,omitempty"`
    AuthConfig    AuthConfig `json:"authConfig,omitempty"`
}

// AuthConfig defines the nodes peer authentication
type AuthConfig struct {
    Username      string `json:"username,omitempty"`
    Password      string `json:"password,omitempty"`
    PrivateSSHKey string `json:"privateSSHKey,omitempty"`
}

// KubernetesClusterStatus defines the observed state of KubernetesCluster
type KubernetesClusterStatus struct {
    // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html
    Phase KubernetesOperatorPhase `json:"phase,omitempty"`

    // when job failed callback or job timeout used
    Reason string `json:"reason,omitempty"`

    // JobName is store each job name
    JobName string `json:"jobName,omitempty"`

    // Last time the condition transitioned from one status to another.
    LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}

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

// KubernetesClusterList contains a list of KubernetesCluster
type KubernetesClusterList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []KubernetesCluster `json:"items"`
}

// users
// "None,Creating,Running,Failed,Scaling"
type KubernetesOperatorPhase string

type Node struct {
    IP string `json:"ip,omitempty"`
}
EOF

执行命令生成代码:

$ $GOPATH/src/k8s.io/code-generator/generate-groups.sh all github.com/gosoon/kubernetes-operator/pkg/client github.com/gosoon/kubernetes-operator/pkg/apis ecs:v1

generate-groups.sh 需要四个参数:

  • 第一个 参数:all,也就是要生成所有的模块,clientset,informers,listers
  • 第二个参数:github.com/gosoon/kubernetes-operator/pkg/client 这个是你要生成代码的目录,目录的名称一般定义为 client
  • 第三个参数:github.com/gosoon/kubernetes-operator/pkg/apis 这个目录是已经创建好的源目录
  • 第四个参数:"ecs:v1" 是 group 和 version 信息,ecs 是 apis 下的目录,v1 是 ecs 下面的目录

生成的代码如下所示:

.
└── pkg
    ├── apis
    │   └── ecs
    │       └── 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
        │           └── ecs
        │               └── v1
        │                   ├── doc.go
        │                   ├── ecs_client.go
        │                   ├── fake
        │                   │   ├── doc.go
        │                   │   ├── fake_ecs_client.go
        │                   │   └── fake_kubernetescluster.go
        │                   ├── generated_expansion.go
        │                   └── kubernetescluster.go
        ├── informers
        │   └── externalversions
        │       ├── ecs
        │       │   ├── interface.go
        │       │   └── v1
        │       │       ├── interface.go
        │       │       └── kubernetescluster.go
        │       ├── factory.go
        │       ├── generic.go
        │       └── internalinterfaces
        │           └── factory_interfaces.go
        └── listers
            └── ecs
                └── v1
                    ├── expansion_generated.go
                    └── kubernetescluster.go

21 directories, 26 files

CRD 以及生成的代码见:kubernetes-operator

总结

本问讲述了如何使用 code-generator 生成代码,要使用自定义 controller 代码生成是最开始的一步,下文会继续讲述自定义 controller 的详细步骤,感兴趣的可以关注笔者 github 的项目 kubernetes-operator

参考:

https://github.com/kubernetes/sample-controller

https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/

https://hexo.do1618.com/2018/04/04/Kubernetes-Deep-Dive-Code-Generation-for-CustomResources/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343