<aside> 🤡 本文主要介绍如何为 CRD 资源编写自定义的控制器

</aside>

介绍

上节课我们已经学习了如何使用 code-generator 来进行代码自动生成,通过代码自动生成可以帮我们自动生成 CRD 资源对象客户端访问的 ClientSet、Informer、Lister 等工具包,接下来我们就来了解下如何编写一个自定义的控制器。

CRD 定义

这里我们来针对前面课程中介绍的 CronTab 自定义资源对象编写一个自定义的控制器,对应的资源清单文件如下所示:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
  - name: v1beta1  
    served: true 
    storage: true 
    schema:  
      openAPIV3Schema:
        description: Define CronTab YAML Spec
        type: object
        properties:
          spec:
            type: object
            properties:
              cronSpec:
                type: string
              image:
                type: string
              replicas:
                type: integer
  scope: Namespaced
  names:
    kind: CronTab
    plural: crontabs
    singular: crontab    
    shortNames:
    - ct

我们需要针对上面的规范来定义资源结构,首先初始化项目:

$ mkdir -p github.com/cnych/controller-demo && cd github.com/cnych/controller-demo
# 初始化项目
$ go mod init github.com/cnych/controller-demo
go: creating new go.mod: module github.com/cnych/controller-demo
# 获取依赖
$ go get k8s.io/apimachinery@v0.17.9
$ go get -d k8s.io/code-generator@v0.17.9
$ go get k8s.io/client-go@v0.17.9

然后初始化 CRD 资源类型,建立好自己的 CRD 结构体,然后使用code-generator 生成客户端代码:

$ mkdir -p pkg/apis/stable/v1beta1

在该文件夹中新建 doc.go 文件,内容如下所示:

// +k8s:deepcopy-gen=package
// +groupName=stable.example.com

package v1beta1

根据 CRD 的规范定义,这里我们定义的 group 为 stable.example.com,版本为 v1beta1 ,在顶部添加了一个代码自动生成的 deepcopy-gen 的 tag,为整个包中的类型生成深拷贝方法。

然后就是非常重要的资源对象的结构体定义,新建 type.go 文件,内容如下所示:

package v1beta1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// 根据 CRD 定义 CronTab 结构体
type CronTab struct {
	metav1.TypeMeta `json:",inline"`
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              CronTabSpec `json:"spec"`
}

// +k8s:deepcopy-gen=false

type CronTabSpec struct {
	CronSpec string `json:"cronSpec"`
	Image    string `json:"image"`
	Replicas int    `json:"replicas"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// CronTab 资源列表
type CronTabList struct {
	metav1.TypeMeta `json:",inline"`

	// 标准的 list metadata
	// +optional
	metav1.ListMeta `json:"metadata,omitempty"`

	Items []CronTab `json:"items"`
}

然后可以参考系统内置的资源对象,还需要提供 AddToScheme 与 Resource 两个变量供 client 注册,新建 register.go 文件,内容如下所示:

package v1beta1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// GroupName is the group name use in this package
const GroupName = "stable.example.com"
// 注册自己的自定义资源
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}

// 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 {
  // 添加 CronTab 与 CronTabList 这两个资源到 scheme
	scheme.AddKnownTypes(SchemeGroupVersion,
		&CronTab{},
		&CronTabList{},
	)
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}

最终的项目结构如下所示:

$ tree .
.
├── go.mod
├── go.sum
└── pkg
    └── apis
        └── stable
            └── v1beta1
                ├── doc.go
                ├── register.go
                └── types.go

4 directories, 5 files