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

1. 前言

轉(zhuǎn)載請說明原文出處, 尊重他人勞動成果!

源碼位置: 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是需要生成代碼的, 當(dāng)然如果自己把那段代碼自己寫上也不會有任何問題, 只是這些代碼重復(fù)性很高, 所以規(guī)律性就很強(qiáng), 只需要把某些字段改成自己的字段即可. 所以本文將分析如何使用生成代碼并分析其原理.

2. 例子

2.1 準(zhǔn)備crd

參考 Kubernetes Deep Dive: Code Generation for CustomResources 以及 [極客時間深入剖析Kubernetes] (推薦學(xué)習(xí))

文件內(nèi)容以及結(jié)構(gòu)如下:

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創(chuàng)建DeepCopy方法. (關(guān)于什么是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需要實(shí)現(xiàn)k8s.io/apimachinery/pkg/runtime.Object這個借口的兩個方法GetObjectKindDeepCopyObject. (因?yàn)樵谡{(diào)用clientset的時候會有decode, 這個時候會用到)

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

4.
// +genclient
表示為該Type創(chuàng)建client. (clientset會為Type創(chuàng)建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的時候可以知道將結(jié)果解析成Database結(jié)構(gòu), 所以需要提前注冊信息.

2.2 運(yùn)行

執(zhí)行參數(shù)如下:
./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都要執(zhí)行. 如果只想執(zhí)行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體系相關(guān)代碼)
informers: 生成了nicktming/k8s-crd-controller/pkg/client/informers文件夾及其下屬所有文件.(從本地緩存中list該元素)

client文件下生成的這些文件在 [k8s源碼分析][client-go] informer之SharedInformerFactory[k8s源碼分析][client-go] client之clientset 中已經(jīng)分析過了.

關(guān)于zz_generated.deepcopy.go文件中的內(nèi)容主要是生成對象拷貝方法.

3. 創(chuàng)建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"

運(yùn)行結(jié)果如下:

[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訪問所創(chuàng)建的資源. 那么接下來看一下如何使用代碼進(jìn)行訪問.

3.1 代碼訪問

前面生成的代碼就是為了用代碼可以訪問, 之所有可以用代碼訪問pod, service這些kubernetes的資源, 這是因?yàn)?code>client-go已經(jīng)實(shí)現(xiàn)了這部分代碼, 然而對于自定義的資源, kubernetes無法提前寫好, 但是由于基本結(jié)構(gòu)差不多, 所以用代碼直接生成了.

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, 因?yàn)榘姹締栴}, apimachinery中的版本不存在DirectCodecFactory.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末韧涨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子如孝,更是在濱河造成了極大的恐慌娩贷,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茁瘦,死亡現(xiàn)場離奇詭異涧至,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纺非,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門赘方,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炕淮,你說我怎么就攤上這事跳夭。” “怎么了币叹?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵颈抚,是天一觀的道長。 經(jīng)常有香客問我,道長锚赤,這世上最難降的妖魔是什么褐鸥? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任叫榕,我火速辦了婚禮,結(jié)果婚禮上翠霍,老公的妹妹穿的比我還像新娘。我一直安慰自己零如,他們只是感情好锄弱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肖卧,像睡著了一般掸鹅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葵姥,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天句携,我揣著相機(jī)與錄音,去河邊找鬼削咆。 笑死蠢笋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挺尿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼熟史,長吁一口氣:“原來是場噩夢啊……” “哼窄俏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起限寞,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤仰坦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玫霎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妈橄,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年鼻种,在試婚紗的時候發(fā)現(xiàn)自己被綠了叉钥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篙贸。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛾洛,靈堂內(nèi)的尸體忽然破棺而出雁芙,到底是詐尸還是另有隱情,我是刑警寧澤兔甘,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布洞焙,位于F島的核電站拯啦,受9級特大地震影響熔任,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疑苔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一惦费、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薪贫,春花似錦、人聲如沸季惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藕筋。三九已至梳码,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掰茶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工盐碱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沪伙,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓暖混,卻偏偏與公主長得像翁授,于是被迫代替她去往敵國和親晾咪。 傳聞我的和親對象是個殘疾皇子贮配,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容