美文网首页
[k8s源码分析][code-generator] crd代码生

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

作者: nicktming | 来源:发表于2019-10-29 23:32 被阅读0次

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.

相关文章

网友评论

      本文标题:[k8s源码分析][code-generator] crd代码生

      本文链接:https://www.haomeiwen.com/subject/axcivctx.html