• k8s编程operator——(3) 自定义资源CRD.md



    k8s编程operator系列:
    k8s编程operator——(1) client-go基础部分
    k8s编程operator——(2) client-go中的informer
    k8s编程operator——(3) 自定义资源CRD
    k8s编程operator——(4) kubebuilder & controller-runtime
    k8s编程operator实战之云编码平台——①架构设计
    k8s编程operator实战之云编码平台——②controller初步实现
    k8s编程operator实战之云编码平台——③Code-Server Pod访问实现
    k8s编程operator实战之云编码平台——④web后端实现
    k8s编程operator实战之云编码平台——⑤项目完成、部署
     

    在K8S系统扩展中,开发者可以通过CRD(CustomResourceDefinition)来扩展K8S API,其功能主要由APIExtensionServer负责。使用CRD扩展资源分为三步:

    • 注册自定义资源:开发者需要通过K8S提供的方式注册自定义资源,即通过CRD进行注册,注册之后,K8S就知道我们自定义资源的存在了,然后我们就可以像使用K8S内置资源一样使用自定义资源(CR)
    • 使用自定义资源:像内置资源比如Pod一样声明资源,使用CR声明我们的资源
    • 删除自定义资源:当我们不需要时,可以删除自定义资源

    1、自定义资源的使用

    1.1 注册自定义资源

    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    metadata:
      # 名字必须与下面的spec字段匹配,并且格式为: <名称的复数形式>.<组名>
      name: demos.example.com
    spec:
      # 组名,用于 REST API: /apis/<组>/<版本>
      group: example.com
      names:
        # 名称的复数形式,用于URL: /apis/<组>/<版本>/<名称的复数形式>
        plural: demos
        # 名称的单数形式,作为命令行使用时和显示时的别名
        singular: demo
        # kind通常是单数形式的帕斯卡编码形式。你的资源清单会使用这一形式
        kind: Demo
        # shortNames 允许你在命令行使用较短的字符串来匹配资源
        shortNames:
        - dm
      # 可以是Namespaced 或 Cluster  
      scope: Namespaced
      # 列举此CRD所支持的版本
      versions:
      - name: v1
        # 每个版本都可以通过served标准来独立启用或禁止
        served: true
        # 其中一个且只有一个版本必须被标记为存储版本
        storage: true
        schema:
          openAPIV3Schema:
            type: object
            properties:
              spec:
                type: object
                properties:
                  name:
                    type: string
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    执行下面的命令来注册我们的CRD:

    # 将上面内容复制到一个crd-demo.yaml文件中
    
    # 注册我们的CRD
    [root@master demo-test]# kubectl create -f crd-demo.yaml
    customresourcedefinition.apiextensions.k8s.io/demos.example.com created
    
    # 查看我们注册的CRD
    [root@master demo-test]# kubectl get crd
    NAME                                        CREATED AT
    demos.example.com                           2022-11-24T07:16:38Z
    
    # 查看自定义资源CR,目前还没有,因为我们还没有创建
    [root@master demo-test]# kubectl get demos
    No resources found in default namespace.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

     

    1.2 使用自定义资源:

    待CRD创建完成后,我们就可以使用它来创建我们的自定义资源了,其创建方式跟内置的资源如Pod这些是一样的,只需要将kind、apiVersion指定为我们CRD中声明的值,比如使用上面的例子中的CRD定义资源:

    apiVersion: "example.com/v1"
    kind: Demo
    metadata:
      name: crd-demo
    spec:
      name: test
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建Demo:

    # 将上面yaml内容复制到demo.yaml中
    
    # 创建demo
    [root@master demo-test]# kubectl create -f demo.yaml
    demo.example.com/crd-demo created
    
    # 查看demos
    [root@master demo-test]# kubectl get dm
    NAME       AGE
    crd-demo   5s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    虽然我们注册了CRD并且创建了一个CR,但是此时是没有任何效果的。要实现效果的话,就需要我们来实现一个controller来监听我们的CR。

     

    1.3 Finalizers

    Finalizers能够让控制器实现异步的删除前(Pre-delete)回调。与内置对象类似,定制对象也支持Finalizer

    给我们的CR添加Finalizer:

    apiVersion: "example.com/v1"
    kind: Demo
    metadata:
      name: demo-finalizer
      finalizers:
      - example.com/finalizer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    自定义 Finalizer 的标识符包含一个域名、一个正向斜线和 finalizer 的名称。 任何控制器都可以在任何对象的 finalizer 列表中添加新的 finalizer。

    对带有 Finalizer 的对象的第一个删除请求会为其 metadata.deletionTimestamp 设置一个值,但不会真的删除对象。一旦此值被设置,finalizers 列表中的表项只能被移除。 在列表中仍然包含 finalizer 时,无法强制删除对应的对象。

    metadata.deletionTimestamp 字段被设置时,监视该对象的各个控制器会执行它们所能处理的 finalizer,并在完成处理之后将其从列表中移除。 每个控制器负责将其 finalizer 从列表中删除。

    metadata.deletionGracePeriodSeconds 的取值控制对更新的轮询周期。

    一旦 finalizers 列表为空时,就意味着所有 finalizer 都被执行过, Kubernetes 会最终删除该资源

     

    下面进行一个测试:

    创建CR:

    # 将上面yaml内容复制到cr-finalizer.yaml
    
    # 创建cr
    [root@master demo-test]# kubectl create -f cr-finalizer.yaml
    demo.example.com/demo-finalizer created
    
    # 查看cr
    [root@master demo-test]# kubectl get demos
    NAME             AGE
    crd-demo         9m48s
    demo-finalizer   19s
    
    # 删除cr
    kubectl delete demo demo-finalizer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    当我们删除时,可以看到会在命令执行后一直卡住,等待我们的controller来完成资源清理:

    在这里插入图片描述

    下面我们来模拟一下清理资源:

    # 启动另一个终端
    
    # 编辑我们的CR
    kubectl edit demo demo-finalizer
    
    • 1
    • 2
    • 3
    • 4

    将下面图片中红框中的内容删除

    在这里插入图片描述

    保存退出后,可以看到另一个终端已经OK了

    在这里插入图片描述

    1.4 合法性验证

    在CRD中定义了我们的CR的一些字段,我们可以对字段进行合法性校验,比如我们使用正则表达式来限制name必须为test开头:

    在这里插入图片描述

    # 1.在我们的crd-demo.yaml中添加上图的pattern
    
    # 2.将之前的cr删除
    kubectl delete dm crd-demo
    
    # 3. 更新我们的crd
    [root@master demo-test]# kubectl apply -f crd-demo.yaml
    customresourcedefinition.apiextensions.k8s.io/demos.example.com configured
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将我们的demo.yaml中的spec.name修改

    在这里插入图片描述

    创建cr,可以看到name不合法,创建失败了。如果将name改为test开头的字符串就可以创建成功了。

    在这里插入图片描述

     

    2、如何操作自定义资源

    client-go为每种K8S内置资源提供对应的clientsetinformer。那么如果我们要监听和操作自定义资源对象,应该如何操作呢?这里有两种方式:

    • 方式一:使用client-go提供的dynamicClient来操作自定义资源对象,当然由于dynamicClient是基于RESTClient实现的,所以我们也可以使用RESTClient来达到同样目的。
    • 方式二:使用code-generator来帮助我们生成我们需要的代码,这样我们就可以像使用client-go为K8S内置资源提供的方式监听和操作自定义资源了。

    我们主要使用code-generator来编写我们的控制器。

     

    2.1 使用RestClient和DynamicClient来操作自定义资源对象

    下面将使用RestClient和DynamicClient来操作我们的自定义资源demo

    使用RestClient操作自定义资源对象:

    使用restClient时,需要我们在config中指定GV以及解码器同时还要配置APIPath

    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    	"k8s.io/apimachinery/pkg/runtime/schema"
    	"k8s.io/client-go/kubernetes/scheme"
    	"k8s.io/client-go/rest"
    	"k8s.io/client-go/tools/clientcmd"
    	"k8s.io/klog/v2"
    )
    
    func main() {
        // 获取配置 将/root/.kube/config拷贝到项目的conf目录下
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    
        // 指定GV
    	config.GroupVersion = &schema.GroupVersion{
    		Group:   "example.com",
    		Version: "v1",
    	}
        // 指定解码器
    	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
        // 指定APIPath APIPath为通过http访问的路径前缀
    	config.APIPath = "/apis/"
    	
        // 创建restClient
    	restClient, err := rest.RESTClientFor(config)
    	if err != nil {
    		panic(err)
    	}
    	
        // 将获取的数据保存到Unstructured类型的对象中
    	obj := unstructured.Unstructured{}
        // 获取资源为demos,deafult命名空间下,名称为crd-demo的资源对象
    	err = restClient.Get().Resource("demos").Name("crd-demo").
    		Namespace(v1.NamespaceDefault).Do(context.Background()).Into(&obj)
    	if err != nil {
    		klog.Errorf("get demo error:%v", err)
    		return
    	}
    	
        // 序列化为json后打印,看的更清晰
    	bytes, err := json.Marshal(obj.Object)
    	if err != nil {
    		klog.Errorf("json marshal error:%v", err)
    		return
    	}
    
    	fmt.Println(string(bytes))
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    对于K8S内建的资源对象例如Pod、Deployment来说,有对应的golang 结构体类型,我们可以直接使用。但是我们自定义的资源是没有的,所以数据的接收需要使用unstructured.Unstructured{}类型:

    这个类型中就是一个map

    type Unstructured struct {
    	// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
    	// map[string]interface{}
    	// children.
    	Object map[string]interface{}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行并使用json_pp格式化后的结果如下:

    在这里插入图片描述

     

    使用DynamicClient操作自定义资源对象:

    在使用dynamicClient操作自定义资源对象时,需要传入自定义资源的GVR,然后就可以像使用内置资源对象一样来操作了。

    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/apimachinery/pkg/runtime/schema"
    	"k8s.io/client-go/dynamic"
    	"k8s.io/client-go/tools/clientcmd"
    	"k8s.io/klog/v2"
    )
    
    func main() {
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    
    	client, err := dynamic.NewForConfig(config)
    	gvr := schema.GroupVersionResource{
    		Group:    "example.com",
    		Version:  "v1",
    		Resource: "demos",
    	}
    
    	resourceInterface := client.Resource(gvr)
    	
    	obj, err := resourceInterface.Namespace(v1.NamespaceDefault).Get(context.Background(), "crd-demo", v1.GetOptions{})
    	if err != nil {
    		klog.Errorf("get error:%v", err)
    		return
    	}
    
    	bytes, err := json.Marshal(obj.Object)
    	if err != nil {
    		klog.Errorf("json marshal error:%v", err)
    		return
    	}
    	fmt.Println(string(bytes))
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行并使用json_pp格式化后的运行结果如下:

    在这里插入图片描述

     

    2.2 使用sharedIndexInformer

            K8S的内建资源都有对应的informer的实现,比如PodInformerDeploymentInformer。对于我们的自定义资源来说,并没有这样的informer,但是我们可以使用shredIndexInformer。在下一节的代码生成中,可以使用代码生成器来生成特定的informer,比如DemoInfomer和DemoLister等工具。

    这节主要将sharedIndexInformer的使用,代码如下:

    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    	"k8s.io/apimachinery/pkg/runtime"
    	"k8s.io/apimachinery/pkg/runtime/schema"
    	"k8s.io/apimachinery/pkg/watch"
    	"k8s.io/client-go/dynamic"
    	"k8s.io/client-go/tools/cache"
    	"k8s.io/client-go/tools/clientcmd"
    	"k8s.io/klog/v2"
    	"time"
    )
    
    func main() {
        // 1、构建config
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    	// 2、创建dynamicClient,也可以使用restClient
    	client, err := dynamic.NewForConfig(config)
    	gvr := schema.GroupVersionResource{
    		Group:    "example.com",
    		Version:  "v1",
    		Resource: "demos",
    	}
    
    	resourceInterface := client.Resource(gvr)
    	
        // 使用sharedIndexInformer需要一个ListWatch对象,该对象可以从apiServer获取数据
    	listwatch := cache.ListWatch{
    		ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
    			return resourceInterface.List(context.Background(), options)
    		},
    		WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
    			return resourceInterface.Watch(context.Background(), options)
    		},
    		DisableChunking: false,
    	}
        // 示例对象,unstructured.Unstructured实现了runtime.Object接口
    	obj := unstructured.Unstructured{}
        // 3、创建sharedIndexInformer,使用Namespace索引器
    	informer := cache.NewSharedIndexInformer(&listwatch, &obj, time.Minute, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
    	
        // 4、添加资源事件处理方法
    	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    		AddFunc: PrintObj,
    		UpdateFunc: func(oldObj, newObj interface{}) {
    			PrintObj(newObj)
    		},
    		DeleteFunc: PrintObj,
    	})
    
    	stopCh := make(chan struct{})
        // 5、启动informer
    	informer.Run(stopCh)
    
    	<-stopCh
    }
    
    func PrintObj(obj interface{}) {
    	demo := obj.(*unstructured.Unstructured)
    	bytes, err := json.Marshal(demo.Object)
    	if err != nil {
    		klog.Errorf("json marshal error:%v", err)
    		return
    	}
    	fmt.Println(string(bytes))
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

            在使用sharedIndexInformer时需要我们传入ListWatch示例对象索引器。ListWatch用于用apiServer获取数据;由于我们没有自定义资源的go类型,因此只能使用unstructured.Unstructured类型。

     

    2.3 code-generator

            code-generator是K8S官网提供的一组代码生成工具。当我们为CRD编写自定义controller时,可以使用它来生成我们需要的versioned clientinformerlister以及其它工具方法。

    2.3.1 下载安装

    github地址:https://github.com/kubernetes/code-generator

    # 将code-generator克隆到$GOPATH/pkg中
    cd $GOPATH/pkg
    
    git clone https://github.com/kubernetes/code-generator
    
    # 安装需要的组件
    # 进入code-generator目录中
    cd code-generator
    
    $ go install ./cmd/{client-gen,deepcopy-gen,informer-gen,lister-gen}
    
    # 这些组件被安装到了$GOPATH/bin目录下, 我们可以将它们添加到PATH中,这样就可以在任意地方使用了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

            如果一个个使用这些组件也是很麻烦的,我们可以使用code-generator目录下的generate-groups.sh脚本文件来生成我们的代码。

    2.3.2 code-generator使用

            接下来我们自定义一个CRD,然后使用code-generator来生成代码来实现对自定义资源的操作。在https://github.com/kubernetes/sample-controller中有一个样例,我们就根据这个样例来。

    1、创建一个工程文件,然后使用我们的ide打开,我用的是Goland:

    mkdir -p github.com/operator-crd
    cd github.com/operator-crd
    touch main.go
    go mod init github.com/operator-crd
    
    • 1
    • 2
    • 3
    • 4

    2、根据样例中的目录结构来创建出我们的目录结构

    目录结构:pkg/apis//

    在这里插入图片描述

    创建出如下的目录:

    在这里插入图片描述

    3、在样例的v1alpha1中有四个文件,其中doc.go types.go 以及 register.go都是需要我们自己写的,然后其余的代码根据这三个文件来生成。

    在这里插入图片描述

    创建出这些文件

    • types.go:在这个文件中需要定义我们的自定义资源的go结构体类型
    • register.go:用来注册我们的类型
    • doc.go:在其中添加全局的标记

    我们需要在这些文件中添加标记,然后代码生成器就可以根据这些标记来生成代码,比如在doc.go中添加下面两个标记,// +k8s:deepcopy-gen=package用来告诉生成器来生成我们自定义资源类型的deepcopy方法+groupName=samplecontroller.k8s.io是指定我们的group名称

    在这里插入图片描述

    (1)我们需要在我们的doc.go中添加标记,内容如下:

    doc.go

    // +k8s:deepcopy-gen=package
    // +groupName=crd.example.com
    package v1
    
    • 1
    • 2
    • 3

    (2)然后在types.go中声明类型:

    types.go

    package v1
    
    import (
    	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )
    
    // +genclient
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // Foo is a specification for a Foo resource
    type Foo struct {
    	metav1.TypeMeta   `json:",inline"`
    	metav1.ObjectMeta `json:"metadata,omitempty"`
    
    	Spec   FooSpec   `json:"spec"`
    	Status FooStatus `json:"status"`
    }
    
    // FooSpec is the spec for a Foo resource
    type FooSpec struct {
    	DeploymentName string `json:"deploymentName"`
    	Replicas       *int32 `json:"replicas"`
    }
    
    // FooStatus is the status for a Foo resource
    type FooStatus struct {
    	AvailableReplicas int32 `json:"availableReplicas"`
    }
    
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // FooList is a list of Foo resources
    type FooList struct {
    	metav1.TypeMeta `json:",inline"`
    	metav1.ListMeta `json:"metadata"`
    
    	Items []Foo `json:"items"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    我们声明的Foo类型跟K8S内建的资源类型是差不多的,都包含了TypeMetaObjectMeta以及Spec等。

    下面两个标记分别用来告诉代码生成器生成自定义资源的clientset和Foo类型要实现的deepcopy的interface

    在这里插入图片描述

    (3)在register.go中注册我们的类型

    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: "crd.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,
    		&Foo{},
    		&FooList{},
    	)
    	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    	return nil
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上面三个步骤完成后,在下面会报红,是因为我们还没有为其生成相应的GetObjectKind和DeepCopyObject方法

    在这里插入图片描述

    4、生成其余代码

    我们可以使用code-generator中的generate-groups.sh来生成代码

    我们可以直接执行来查看它的使用方法,Examples中的第一个用来使用所有的组件,第二个我们可以单独使用组件:

    在这里插入图片描述

    生成的命令如下:

    $GOPATH/pkg/code-generator/generate-groups.sh all github.com/operator-crd/pkg/generated github.com/operator-crd/pkg/apis crd.example.com:v1 --go-header-file=$GOPATH/pkg/code-generator/hack/boilerplate.go.txt --output-base ../../
    
    • 1
    • github.com/operator-crd/pkg/generated:是我们生成的代码的位置
    • github.com/operator-crd/pkg/apis:我们的代码的位置,要根据我们的三个代码文件来生成其它代码
    • crd.example.com:v1:组名和版本

    注意:在windows的gitbash上使用generate-groups.sh不成功,还没有找到解决办法,建议在linux中生成,其实这个code-generator不会也无所谓,后面有更好用的工具,主要看生成的步骤即可。可以根据上面的步骤在linux中安装code-generator来生成代码

    最终在linux上生成了对应的代码:

    在这里插入图片描述

    最终生成了我们的deepcopy、clientset、informer以及listers

    在这里插入图片描述

    然后我们就可以像使用K8S的内建资源一样来操作我们的自定义资源了。

    5、创建自定义资源

    crd.yaml

    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    metadata:
      name: foos.crd.example.com
    spec:
      group: crd.example.com
      versions:
        - name: v1
          served: true
          storage: true
          schema:
            # schema used for validation
            openAPIV3Schema:
              type: object
              properties:
                spec:
                  type: object
                  properties:
                    deploymentName:
                      type: string
                    replicas:
                      type: integer
                      minimum: 1
                      maximum: 10
                status:
                  type: object
                  properties:
                    availableReplicas:
                      type: integer
      names:
        kind: Foo
        plural: foos
      scope: Namespaced
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    注册CRD:

    # 将上面的内容黏贴到crd.yaml中
    
    # 注册crd
    [root@master manifests]# kubectl create -f crd.yaml
    customresourcedefinition.apiextensions.k8s.io/foos.crd.example.com created
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建一个CR

    example-foo.yaml

    apiVersion: crd.example.com/v1
    kind: Foo
    metadata:
      name: example-foo
    spec:
      deploymentName: example-foo
      replicas: 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    # 创建cr
    [root@master manifests]# kubectl create -f example-foo.yaml
    foo.crd.example.com/example-foo created
    
    # 查看cr
    [root@master manifests]# kubectl get foos
    NAME          AGE
    example-foo   27s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6、在代码中操作我们的自定义资源

    main.go

    package main
    
    import (
    	"fmt"
    	v1 "github.com/operator-crd/pkg/apis/crd.example.com/v1"
    	clientset "github.com/operator-crd/pkg/generated/clientset/versioned"
    	"github.com/operator-crd/pkg/generated/informers/externalversions"
    	"k8s.io/client-go/tools/cache"
    	"k8s.io/client-go/tools/clientcmd"
    	"k8s.io/klog/v2"
    )
    
    func main() {
    	// 1.创建配置
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(config)
    	}
    
    	// 2.创建clientset
    	clientset, err := clientset.NewForConfig(config)
    	if err != nil {
    		panic(err)
    	}
    
    	// 3.创建informerFactory
    	factory := externalversions.NewSharedInformerFactory(clientset, 0)
    	// 4.获取FooInformer
    	fooInformer := factory.Crd().V1().Foos()
    	// 获取SharedIndexInformer
    	informer := fooInformer.Informer()
    	// 获取lister
    	lister := fooInformer.Lister()
    
    	// 注册资源事件处理其
    	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    		AddFunc: func(obj interface{}) {
    			foo := obj.(*v1.Foo)
    			fmt.Printf("[Add Event] %s\n", foo.Name)
    		},
    		UpdateFunc: func(oldObj, newObj interface{}) {
    			foo := newObj.(*v1.Foo)
    			fmt.Printf("[Add Event] %s\n", foo.Name)
    		},
    		DeleteFunc: func(obj interface{}) {
    			foo := obj.(*v1.Foo)
    			fmt.Printf("[Add Event] %s\n", foo.Name)
    		},
    	})
    
    	stopCh := make(chan struct{})
    	factory.Start(stopCh)
    	factory.WaitForCacheSync(stopCh)
    
    	// 使用lister查询foo
    	foo, err := lister.Foos("default").Get("example-foo")
    	if err != nil {
    		klog.Errorf("get foo error:%v", err)
    	} else {
    		fmt.Println("foo name:", foo.Name)
    	}
    
    	<-stopCh
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    运行结果如下:

    在这里插入图片描述

     

    2.4 controller-tools

    在上节中,使用code-generate可以帮助我们生成types文件以及informer、lister等工具方法。但是它不能为我们生成types文件以及CRD。但是使用controller-tools中的工具就可以来生成types文件以及CRD、RBAC等文件。

    2.4.1 下载安装

    github地址:https://github.com/kubernetes-sigs/controller-tools

    # 将代码克隆到下来
    git clone https://github.com/kubernetes-sigs/controller-tools
    
    # 安装
    cd controller-tools
    go install ./cmd/{controller-gen,type-scaffold}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    安装后,两个程序就被安装到了$GOPATH/bin目录下。

     

    2.4.1 controller-tools使用

    type-scaffold可以为我们生成自定义资源的go类型。使用时需要指定Kind以及resource(也可以不指定resource,会根据kind来生成),使用如下:

    type-scaffold.exe --kind Foo --resource Foos
    
    • 1

    但是直接这样使用并不会为我们生成文件,而是直接打印出了生成的代码,因此我们可以使用重定向来生成文件:

    type-scaffold.exe --kind Foo --resource Foos > pkg/apis/crd.example.com/v1/types.go
    
    • 1

    然后我们可以在FooSpec中添加需要的字段:

    在这里插入图片描述

    controller-gen可以生成deepcopy方法实现、CRD、RBAC等:

    在这里插入图片描述

    生成这些文件也要依赖于注释标记,比如在生成CRD时候,我们可以在types文件中添加标记来设置数据的校验:

    在这里插入图片描述

    生成deepcopy方法:

    controller-gen object paths=pkg/apis/crd.example.com/v1/types.go
    
    • 1

    生成CRD

    资源定义好了,我们需要将其注册到client-go中。在v1目录下创建register.go,在package上面添加groupName的标记,然后定义GV:`

    // register.go
    // +groupName=example.com
    package v1
    
    import (
    	"k8s.io/apimachinery/pkg/runtime"
    	"k8s.io/apimachinery/pkg/runtime/schema"
    	"k8s.io/apimachinery/pkg/runtime/serializer"
    )
    
    var (
    	Scheme       = runtime.NewScheme()
    	GroupVersion = schema.GroupVersion{
    		Group:   "example.com",
    		Version: "v1",
    	}
    
    	Codec = serializer.NewCodecFactory(Scheme)
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在types.go文件中调用Scheme.AddKnownTypes的方法来注册我们的类型:

    // types.go
    package v1
    
    import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    
    // FooSpec defines the desired state of Foo
    type FooSpec struct {
    	// INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
    }
    
    // FooStatus defines the observed state of Foo.
    // It should always be reconstructable from the state of the cluster and/or outside world.
    type FooStatus struct {
    	// INSERT ADDITIONAL STATUS FIELDS -- observed state of cluster
    }
    
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // Foo is the Schema for the foos API
    // +k8s:openapi-gen=true
    type Foo struct {
    	metav1.TypeMeta   `json:",inline"`
    	metav1.ObjectMeta `json:"metadata,omitempty"`
    
    	Spec   FooSpec   `json:"spec,omitempty"`
    	Status FooStatus `json:"status,omitempty"`
    }
    
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // FooList contains a list of Foo
    type FooList struct {
    	metav1.TypeMeta `json:",inline"`
    	metav1.ListMeta `json:"metadata,omitempty"`
    	Items           []Foo `json:"items"`
    }
    
    func init() {
    	Scheme.AddKnownTypes(GroupVersion, &Foo{}, &FooList{})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    # 生成CRD
    controller-gen  crd paths=./... output:crd:dir=config/crd
    
    • 1
    • 2

    但是不知为何,在这生成之后,没有任何文件,半天找不到原因。先不管了,这些东西知道一个流程就行了,反正后面也不会用。后面有kubebuilder脚手架,使用起来非常简单方便。

  • 相关阅读:
    【尘缘赠书活动:01期】Python数据挖掘——入门进阶与实用案例分析
    【图像分割】基于matlab和声搜索算法图像多级阈值分割【含Matlab源码 2044期】
    VIT(Vision Transformer)学习-模型理解(一)
    解决pod健康检查问题
    潘多拉 IOT 开发板学习(RT-Thread)—— 实验2 RGB LED 实验(学习笔记)
    Excel函数2
    无碳越障S型小车总体设计(lunwen+工序卡+过程卡+cad图纸+proe三维图纸+运动仿真视频)
    c++11 智能指针 (std::shared_ptr)(三)
    [最短路径问题]Dijkstra算法(含还原具体路径)
    【全志T113-S3_100ask】2-编写第一个驱动
  • 原文地址:https://blog.csdn.net/Peerless__/article/details/128056647