目录
基本结构
注入CRD
基本结构
首先下载相应的go pkg
go get -u sigs.k8s.io/controller-runtime
接下来需要创建控制器和Manager
Operator的本质是一个可重入的队列编程模式,而Manager可以用来管理Controller、Admission Webhook,包括访问资源对象的client、cache、scheme、提供了一个简单的依赖注入机制、优雅关闭的信号处理机制等。
参见官方文档中的示例代码
opsController.go
package kube
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type OpsController struct {
client.Client
}
func NewOpsController() *OpsController {
return &OpsController{}
}
func (a *OpsController) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
return reconcile.Result{}, nil
}
func (a *OpsController) InjectClient(c client.Client) error {
a.Client = c
return nil
}
先监听官方资源,比如Ingress(后续需要在Manager中指定),因此将调谐函数补全为
func (a *OpsController) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
resource := &v1.Ingress{}
a.Client.Get(ctx, req.NamespacedName, resource)
fmt.Println(resource)
return reconcile.Result{}, nil
}
当有一个新的Ingress对象被提交到Apiserver,都会将整个结构打印在控制台上。
package kube
import (
v1 "k8s.io/api/networking/v1"
"os"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client/config"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
)
func InitManager() {
logf.SetLogger(zap.New())
var log = logf.Log.WithName("builder-examples")
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{})
if err != nil {
log.Error(err, "could not create manager")
os.Exit(1)
}
err = builder.
ControllerManagedBy(mgr). // Create the ControllerManagedBy
For(&v1.Ingress{}). // ReplicaSet is the Application API
Complete(NewOpsController())
if err != nil {
log.Error(err, "could not create controller")
os.Exit(1)
}
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
log.Error(err, "could not start manager")
os.Exit(1)
}
}
在这段代码中,将Ingress的指挥权分配给了刚才创建的Controller(可以同时被多个Controller管理),并且启动Manager,这里其实也是operator的入口函数。
随便提交一个Ingress对象,控制台输出打印,结束。
至此,我们完成了一个Operator最基本的调用过程。
注入CRD
Operator=CRD+Controller+Webhook
在实际的环境中,往往需要高度定制的资源,来完成复杂的流程控制和预期导向。
所以,这一小节就来生成CRD以及完成对CR的监听和控制。
同样是在源码中,可以发现一段示例代码,就是上述实现的一个demo。
其中,完成了对CRD的申明,深拷贝,和对外暴露的注册函数。
Kubernetes官方发布的代码生成脚手架code-generator中也有对此的一段实现代码。
根据这段demo,需要填入CRD的一些预设字段,用来创建Manager监听对象以及Controller在调用client-go时的反序列化操作。
在启动Manager之前,需要将CRD注册到Scheme中去,并且加入对CRD的控制与分配控制器。
err = v1.AddToScheme(mgr.GetScheme())
if err != nil {
log.Error(err, "could not register scheme")
}
err = builder.
ControllerManagedBy(mgr). // Create the ControllerManagedBy
For(&v1.Operation{}). // ReplicaSet is the Application API
Complete(NewOpsController())
if err != nil {
log.Error(err, "could not create controller")
os.Exit(1)
}
修改调谐函数,对触发的对象打印到控制台。
func (a *OpsController) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
resource := &v1.Operation{}
err := a.Client.Get(ctx, req.NamespacedName, resource)
if err != nil {
return reconcile.Result{}, err
}
fmt.Println(resource)
return reconcile.Result{}, nil
}
参考官方文档 ,需要编辑一个yaml并应用,这里同样是进行字段的申明,主要是将对象提交到Apiserver中去。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
name: operations.extensions.octoboy.com
spec:
# 组名称,用于 REST API: /apis/<组>/<版本>
group: extensions.octoboy.com
# 列举此 CustomResourceDefinition 所支持的版本
versions:
- name: v1
# 每个版本都可以通过 served 标志来独立启用或禁止
served: true
# 其中一个且只有一个版本必需被标记为存储版本
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
action:
type: string
# 可以是 Namespaced 或 Cluster
scope: Namespaced
names:
# 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
plural: operations
# 名称的单数形式,作为命令行使用时和显示时的别名
singular: operation
# kind 通常是单数形式的驼峰命名(CamelCased)形式。你的资源清单会使用这一形式。
kind: Operation
# shortNames 允许你在命令行使用较短的字符串来匹配资源
shortNames:
- ops
然后启动我们的Operator,并简单编写一个对象yaml。
apiVersion: extensions.octoboy.com/v1
kind: Operation
metadata:
name: myops
namespace: default
spec:
action: "restart"
同时应用,观察到控制台输出
&{{Operation extensions.octoboy.com/v1} {myops default 1344e481-4db4-461d-8d8d-b07713a76236 3927679 1 2023-03-07 14:21:09 +0800 CST <nil> <nil> map[] map[kubectl.kubernetes.io/last-applied-configuration:{"apiVersion":"extensions.octoboy.com/v1","kind":"Operation","metadata":{"annotations":{},"name":"myops","namespace":"default"},"spec":{"action":"restart"}}
] [] [] [{kubectl-client-side-apply Update extensions.octoboy.com/v1 2023-03-07 14:21:09 +0800 CST FieldsV1 {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{".":{},"f:action":{}}} }]} {} {0001-01-01 00:00:00 +0000 UTC}}
至此,通过controller-runtime搭建起了一个简易的Operator控制器。