[k8s源码分析][code-generator] crd代码生成

1. 前言

转载请说明原文出处, 尊重他人劳动成果!

源码位置: https://github.com/nicktming/kubernetes/tree/tming-v1.13/staging/src/k8s.io/code-generator
分支: tming-v1.13 (基于v1.13版本)

https://github.com/nicktming/k8s-crd-controller

由于crd是需要生成代码的, 当然如果自己把那段代码自己写上也不会有任何问题, 只是这些代码重复性很高, 所以规律性就很强, 只需要把某些字段改成自己的字段即可. 所以本文将分析如何使用生成代码并分析其原理.

2. 例子

2.1 准备crd

参考 Kubernetes Deep Dive: Code Generation for CustomResources 以及 [极客时间深入剖析Kubernetes] (推荐学习)

文件内容以及结构如下:

crd.png
doc.go 和 types.go
===> pkg/apis/example.com/v1/doc.go
// +k8s:deepcopy-gen=package,register
// +groupName=nicktming.example.com
package v1

===> pkg/apis/example.com/v1/types.go
package v1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Database describes a database.
type Database struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec DatabaseSpec `json:"spec"`
}
// DatabaseSpec is the spec for a Foo resource
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
// DatabaseList is a list of Database resources
type DatabaseList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata"`
    Items []Database `json:"items"`
}
Global Tags

1. // +k8s:deepcopy-gen=package,register 表示为该package下面所有的Type创建DeepCopy方法. (关于什么是Type后面源码分析部分会解释)

It tells deepcopy-gen to create deepcopy methods by default for every 
type in that package. 

If you have types where deepcopy is not necessary or not desired, you 
can opt-out for such a type with a local tag // +k8s:deepcopy-gen=false. 

If you do not enable package-wide deepcopy, you have to opt-in 
to deepcopy for each desired type via // +k8s:deepcopy-gen=true

2. // +groupName=nicktming.example.com 表示group的名字是nicktming.example.com, 后面在注册schema的时候会用到.

Local Tags

3. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 表示该Type需要实现k8s.io/apimachinery/pkg/runtime.Object这个借口的两个方法GetObjectKindDeepCopyObject. (因为在调用clientset的时候会有decode, 这个时候会用到)

type Object interface {
    GetObjectKind() schema.ObjectKind
    DeepCopyObject() Object
}
Client-gen Tags

4.
// +genclient
表示为该Type创建client. (clientset会为Type创建client)
// +genclient:noStatus

this type is not using spec-status separation via the /status subresource. 

The resulting client will not have the UpdateStatus method 
(client-gen would generate that blindly otherwise as soon as
 it finds a Status field in your struct)
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: "nicktming.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,
        &Database{},
        &DatabaseList{},
    )
    metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    return nil
}

该文件主要为了向schema注册apiGroupVersion用的, 在生成客户端中decode阶段会用到.
说白了就是在解析的时候GroupVersion=nicktming.example.com/v1并且Kind=Database的时候可以知道将结果解析成Database结构, 所以需要提前注册信息.

2.2 运行

执行参数如下:
./generate-groups.sh all github.com/nicktming/k8s-crd-controller/pkg/client github.com/nicktming/k8s-crd-controller/pkg/apis example.com:v1
all: 代表deepcopy-gen, client-gen, 和 lister-geninformer-gen都要执行. 如果只想执行deepcopy-gen, 把all换成deepcopy-gen即可.

[root@master k8s-crd-controller]# pwd
/root/go/src/github.com/nicktming/k8s-crd-controller
[root@master k8s-crd-controller]# tree
.
├── pkg
│   └── apis
│       └── example.com
│           └── v1
│               ├── doc.go
│               └── types.go
└── README.md
[root@master code-generator]# pwd
/root/go/src/k8s.io/kubernetes/staging/src/k8s.io/code-generator
[root@master code-generator]# ./generate-groups.sh all github.com/nicktming/k8s-crd-controller/pkg/client github.com/nicktming/k8s-crd-controller/pkg/apis example.com:v1
Generating deepcopy funcs
Generating clientset for example.com:v1 at github.com/nicktming/k8s-crd-controller/pkg/client/clientset
Generating listers for example.com:v1 at github.com/nicktming/k8s-crd-controller/pkg/client/listers
Generating informers for example.com:v1 at github.com/nicktming/k8s-crd-controller/pkg/client/informers

[root@master k8s-crd-controller]# pwd
/root/go/src/github.com/nicktming/k8s-crd-controller
 [root@master k8s-crd-controller]# tree
.
├── pkg
│   ├── apis
│   │   └── 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
│       │           └── example.com
│       │               └── v1
│       │                   ├── database.go
│       │                   ├── doc.go
│       │                   ├── example.com_client.go
│       │                   ├── fake
│       │                   │   ├── doc.go
│       │                   │   ├── fake_database.go
│       │                   │   └── fake_example.com_client.go
│       │                   └── generated_expansion.go
│       ├── informers
│       │   └── externalversions
│       │       ├── example.com
│       │       │   ├── interface.go
│       │       │   └── v1
│       │       │       ├── database.go
│       │       │       └── interface.go
│       │       ├── factory.go
│       │       ├── generic.go
│       │       └── internalinterfaces
│       │           └── factory_interfaces.go
│       └── listers
│           └── example.com
│               └── v1
│                   ├── database.go
│                   └── expansion_generated.go
└── README.md

21 directories, 27 files

deepcopy-gen: 生成了github.com/nicktming/k8s-crd-controller/pkg/apis/example.com/v1/zz_generated.deepcopy.go文件.
client-gen: 生成了nicktming/k8s-crd-controller/pkg/client/clientset文件夹及其下属所有文件. (主要与api-server打交道代码)
lister-gen: 生成了nicktming/k8s-crd-controller/pkg/client/listers文件夹及其下属所有文件. (informer体系相关代码)
informers: 生成了nicktming/k8s-crd-controller/pkg/client/informers文件夹及其下属所有文件.(从本地缓存中list该元素)

client文件下生成的这些文件在 [k8s源码分析][client-go] informer之SharedInformerFactory[k8s源码分析][client-go] client之clientset 中已经分析过了.

关于zz_generated.deepcopy.go文件中的内容主要是生成对象拷贝方法.

3. 创建crd

crd.yaml

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: databases.nicktming.example.com
spec:
  group: nicktming.example.com
  version: v1
  names:
    kind: Database
    plural: databases
  scope: Namespaced

my-database.yaml

apiVersion: nicktming.example.com/v1
kind: Database
metadata:
  name: my-database
spec:
  username: "nicktming"
  password: "123456"
  encoding: "json"

运行结果如下:

[root@master kubectl]# ./kubectl apply -f crd/crd.yaml 
customresourcedefinition.apiextensions.k8s.io/databases.nicktming.example.com created
[root@master kubectl]# ./kubectl get crd 
NAME                              CREATED AT
databases.nicktming.example.com   2019-10-29T13:30:04Z
[root@master kubectl]# ./kubectl apply -f crd/my-database.yaml 
database.nicktming.example.com/my-database created
[root@master kubectl]# ./kubectl get database
NAME          AGE
my-database   58s
[root@master kubectl]# ./kubectl describe database my-database
Name:         my-database
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"nicktming.example.com/v1","kind":"Database","metadata":{"annotations":{},"name":"my-database","namespace":"default"},"spec"...
API Version:  nicktming.example.com/v1
Kind:         Database
Metadata:
  Creation Timestamp:  2019-10-29T13:30:51Z
  Generation:          1
  Resource Version:    100743
  Self Link:           /apis/nicktming.example.com/v1/namespaces/default/databases/my-database
  UID:                 53787ca0-fa50-11e9-8739-525400d54f7e
Spec:
  Encoding:  json
  Password:  123456
  Username:  nicktming
Events:      <none>

通过selflink访问 curl http://localhost:8080/apis/nicktming.example.com/v1/namespaces/default/databases/my-database

browser.png

可以看到看到可以通过api-server访问所创建的资源. 那么接下来看一下如何使用代码进行访问.

3.1 代码访问

前面生成的代码就是为了用代码可以访问, 之所有可以用代码访问pod, service这些kubernetes的资源, 这是因为client-go已经实现了这部分代码, 然而对于自定义的资源, kubernetes无法提前写好, 但是由于基本结构差不多, 所以用代码直接生成了.

package main

import (
    "fmt"
    nicktmingv1 "github.com/nicktming/k8s-crd-controller/pkg/apis/example.com/v1"
    "k8s.io/apimachinery/pkg/labels"
    "k8s.io/client-go/rest"
    nicktmingclientset "github.com/nicktming/k8s-crd-controller/pkg/client/clientset/versioned"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    nicktminginformers "github.com/nicktming/k8s-crd-controller/pkg/client/informers/externalversions"
    "k8s.io/client-go/tools/cache"
)

func main()  {
    config := &rest.Config{
        Host: "http://172.21.0.16:8080",
    }
    // 生成client
    databaseClient, _ := nicktmingclientset.NewForConfig(config)

    // 从api-server中获取
    myDatabase, _ := databaseClient.NicktmingV1().Databases("default").Get("my-database", metav1.GetOptions{})
    fmt.Printf("===>Database Name:%v(%v,%v,%v)\n", myDatabase.Name, myDatabase.Spec.User, myDatabase.Spec.Password, myDatabase.Spec.Encoding)


    factory := nicktminginformers.NewSharedInformerFactory(databaseClient, 10)

    // 添加event handler
    databaseInformer := factory.Nicktming().V1().Databases()
    databaseInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    func(obj interface{}) {fmt.Printf("add: %v\n", obj.(*nicktmingv1.Database).Name)},
        UpdateFunc: func(oldObj, newObj interface{}) {fmt.Printf("update: %v\n", newObj.(*nicktmingv1.Database).Name)},
        DeleteFunc: func(obj interface{}){fmt.Printf("delete: %v\n", obj.(*nicktmingv1.Database).Name)},
    })

    // 启动
    stopCh := make(chan struct{})
    factory.Start(stopCh)
    factory.WaitForCacheSync(stopCh)


    // 从本地缓存中获取元素
    databaseLister := databaseInformer.Lister()

    allDatabases, _ := databaseLister.List(labels.Everything())
    for _, p := range allDatabases {
        fmt.Printf("list database: %v\n", p.Name)
    }
    <- stopCh

}

如果报错的话把config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} 改成 config.NegotiatedSerializer = scheme.Codecs, 因为版本问题, apimachinery中的版本不存在DirectCodecFactory.

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

推荐阅读更多精彩内容