从零开始构建operator(二)添加API和controller

司寇阳朔
2023-12-01

添加新API和controller

说明

这里需要说明一下,新API和controller之间的关系,本质上说他们没有直接关系。

CRD

在kubernetes中添加新API的方法叫CRD(另外一种叫做聚合层,不是本文关注的重点,感兴趣的同学可以自行参考官网),是一种在不改变原有API Server接口的情况动态扩展API方法。在本文档中如无特殊说明扩展API等同于CRD。每个CRD对应着一种资源类型,用户创建了新的CRD之后,就可以通过新的API创建该CRD描述的资源,比如名称为ZnbaseCluster的CRD描述的是运行在kubernetes上的znbase集群。kubernetes的API server自动添加了对该资源的请求的处理程序,当然处理程序实现的仅为对该资源属性的查询修改等。本质是将其存储在ETCD上,然后将REST操作转换为对ETCD存储对象的操作。

controller

而controller是本质上是一个事件队列和一个无限的循环,在启动的时候向API server订阅kubernetes的若干事件,当事件发生时API Server将事件放入事件队列中。controller不间断地处理事件队列中的事件,向kubernetes或目标系统输出控制指令,推动目标系统进入预想状态。
可以看出controller实际上是一种软件框架,具有声明式接口、幂等操作等特性,其应用场景不限于kubernetes,完全可以用于其他后台应该程序。这也是有些人认为controller将带来一次后台软件框架变革的原因所在。

CRD和controller的关系

回到operator,当用户在kubernetes中添加了新的CRD后,大部分情况下是希望kubernetes能根据新的资源对象(CR),自动执行对应的业务逻辑,即领域知识代码化。这时候新创建了一个controller,该controller订阅新CRD相关的事件,这样这个controller将能感知资源对象的所有状态变化,再根据这些状态变化,做出相应的动作,推动目标系统趋于理想化状态,从而实现了领域知识代码化的目标。而operator SDK等脚手架工具的价值在于将这个过程简单化,自动生成与kubernetes交互的代码。

另外从解耦的角度考虑,每个CRD对应唯一的一个controller是较好的选择。一个operator里只使用一个manager对象,该manager对象同时管理多个controller实例。

执行创建命令

下面我们看一下创建CRD和controller的命令:

operator-sdk create api --group znbase --version v1alpha1 --kind ZnbaseCluster --resource --controller

这里需要说明的是--resource表示是否创建crd对应的yaml文件和go类型文件,--controller表示是否创建对应的controller文件,一般情况下不需要更改。

如看到如下信息,说明创建成功

operator-sdk create api --group znbase --version v1alpha1 --kind ZnbaseCluster --resource --controller

Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
api/v1alpha1/znbasecluster_types.go
controllers/znbasecluster_controller.go
Update dependencies:
$ go mod tidy
Running make:
$ make generate
/home/chensj/code/znbase-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."

新增的资源文件

api/vialpha1/目录下新增了3个go文件,其中最主要文件是znbasecluster_types.go,是对新增CRD的定义。

api
└── v1alpha1
    ├── groupversion_info.go
    ├── znbasecluster_types.go
    └── zz_generated.deepcopy.go

打开znbasecluster_types.go文件,可以看到描述结构体ZnbaseCluster,主要由ZnbaseClusterSpecZnbaseClusterStatus两个子结构组成。

// ZnbaseCluster is the Schema for the znbaseclusters API
type ZnbaseCluster struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ZnbaseClusterSpec   `json:"spec,omitempty"`
	Status ZnbaseClusterStatus `json:"status,omitempty"`
}

其中ZnbaseClusterSpec的定义如下,当前只有一个string类型的Foo成员,后面我们将修改该结构,添加新的成员信息。

// ZnbaseClusterSpec defines the desired state of ZnbaseCluster
type ZnbaseClusterSpec struct {
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	// Foo is an example field of ZnbaseCluster. Edit znbasecluster_types.go to remove/update
	Foo string `json:"foo,omitempty"`
}

另外一个结构体ZnbaseClusterStatus定义如下,描述了新增CRD的状态信息,当前是没有任何信息。

// ZnbaseClusterStatus defines the observed state of ZnbaseCluster
type ZnbaseClusterStatus struct {
	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
	// Important: Run "make" to regenerate code after modifying this file
}

新增的配置文件

新增了如下yaml文件:

config/
├── crd
│   ├── kustomization.yaml
│   ├── kustomizeconfig.yaml
│   └── patches
│       ├── cainjection_in_znbaseclusters.yaml
│       └── webhook_in_znbaseclusters.yaml
├── rbac
│   ...
│   ├── znbasecluster_editor_role.yaml
│   └── znbasecluster_viewer_role.yaml
└── samples
    ├── kustomization.yaml
    └── znbase_v1alpha1_znbasecluster.yaml

其中:

  • config/crd/目录下的文件是CRD的定义;
  • config/rbac/目录下的文件是新增的role定义;
  • config/samples/目录下的文件是创建CR对象的示例,使用时需要手动修改;

新增的controller文件

新增的文件如下:

controllers/
├── suite_test.go
└── znbasecluster_controller.go

文件znbasecluster_controller.go定义了controller的业务逻辑,suite_test.go是它的测试。下面我们重点分析一下文件znbasecluster_controller.go

该文件中最重要的函数是Reconcile,同时Reconcile也是整个operator最重要的函数,因为我们要将实现业务逻辑的代码添加到该函数中。其初始定义如下:

...
func (r *ZnbaseClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)

	// your logic here

	return ctrl.Result{}, nil
}
...

main.go内容的变化

我们通过git diff可以看到上述操作对main.go修改如下:

@@ -41,6 +44,7 @@ var (
 func init() {
        utilruntime.Must(clientgoscheme.AddToScheme(scheme))
 
+       utilruntime.Must(znbasev1alpha1.AddToScheme(scheme))
        //+kubebuilder:scaffold:scheme
 }

@@ -74,6 +78,13 @@ func main() {
                os.Exit(1)
        }
 
+       if err = (&controllers.ZnbaseClusterReconciler{
+               Client: mgr.GetClient(),
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "ZnbaseCluster")
+               os.Exit(1)
+       }
        //+kubebuilder:scaffold:builder

       if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

修改主要有两处:

  • 将新增CRD的schema注册到manager
  • 将新增CRD的controller添加到manager;

如需增加其他CRD和controller,仅需重复上述步骤即可。

 类似资料: