这里需要说明一下,新API和controller之间的关系,本质上说他们没有直接关系。
在kubernetes中添加新API的方法叫CRD(另外一种叫做聚合层,不是本文关注的重点,感兴趣的同学可以自行参考官网),是一种在不改变原有API Server接口的情况动态扩展API方法。在本文档中如无特殊说明扩展API等同于CRD。每个CRD对应着一种资源类型,用户创建了新的CRD之后,就可以通过新的API创建该CRD描述的资源,比如名称为ZnbaseCluster的CRD描述的是运行在kubernetes上的znbase集群。kubernetes的API server自动添加了对该资源的请求的处理程序,当然处理程序实现的仅为对该资源属性的查询修改等。本质是将其存储在ETCD上,然后将REST操作转换为对ETCD存储对象的操作。
而controller是本质上是一个事件队列和一个无限的循环,在启动的时候向API server订阅kubernetes的若干事件,当事件发生时API Server将事件放入事件队列中。controller不间断地处理事件队列中的事件,向kubernetes或目标系统输出控制指令,推动目标系统进入预想状态。
可以看出controller实际上是一种软件框架,具有声明式接口、幂等操作等特性,其应用场景不限于kubernetes,完全可以用于其他后台应该程序。这也是有些人认为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
,主要由ZnbaseClusterSpec
和ZnbaseClusterStatus
两个子结构组成。
// 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对象的示例,使用时需要手动修改;新增的文件如下:
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
}
...
我们通过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 {
修改主要有两处:
schema
注册到manager
;controller
添加到manager
;如需增加其他CRD和controller,仅需重复上述步骤即可。