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

CoreDump丶 · 收录于 2023-06-03 04:09:09 · source URL


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

执行下面的命令来注册我们的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 使用自定义资源:

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

apiVersion: "example.com/v1"
kind: Demo
metadata:
  name: crd-demo
spec:
  name: test

创建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

虽然我们注册了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

自定义 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

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

在这里插入图片描述

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

# 启动另一个终端

# 编辑我们的CR
kubectl edit demo demo-finalizer

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

在这里插入图片描述

保存退出后,可以看到另一个终端已经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

将我们的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))
}

对于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{}
}

运行并使用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))

}

运行并使用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))
}

        在使用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中,这样就可以在任意地方使用了

        如果一个个使用这些组件也是很麻烦的,我们可以使用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

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

目录结构:pkg/apis/<Group>/<Version>

在这里插入图片描述

创建出如下的目录:

在这里插入图片描述

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

(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"`
}

我们声明的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
}

上面三个步骤完成后,在下面会报红,是因为我们还没有为其生成相应的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 ../../
  • 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

注册CRD:

# 将上面的内容黏贴到crd.yaml中

# 注册crd
[root@master manifests]# kubectl create -f crd.yaml
customresourcedefinition.apiextensions.k8s.io/foos.crd.example.com created

创建一个CR

example-foo.yaml

apiVersion: crd.example.com/v1
kind: Foo
metadata:
  name: example-foo
spec:
  deploymentName: example-foo
  replicas: 1
# 创建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

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
}

运行结果如下:

在这里插入图片描述

 

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}

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

 

2.4.1 controller-tools使用

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

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

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

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

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

生成deepcopy方法:

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

生成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)
)

在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{})
}
# 生成CRD
controller-gen  crd paths=./... output:crd:dir=config/crd

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