chinazjK零SK壹S
导读
controller-runtime库封装了之前client-go的使用(我认为的)。controller-runtime使用更加简单。但是controller导入了很多概念。
controller-runtime框架
controller-runtime 地址)
提供了用于构建Controller的库。Controller实现Kubernetes API访问,在这基础之上构建Operators,Workload APIs,Configuration APIs,Autoscalers等。
名词概念
Client
提供用于读写Kubernetes对象的Read/Write客户端。
Cache
提供读取客户端,用于从本地缓存读取对象。缓存可以注册处理程序以响应更新缓存的事件。
Manager
Manager是创建Controller所必需的,并提供Controller共享的依赖项,例如客户端,缓存,方案等。应通过调用Manager.Start通过Manager启动Controller。
Controller
控制器实现Kubernetes API来响应事件(Create/Update/Delete objetc)并确保资源实例指定的状态与系统状态匹配(使用)。这称为reconcile。如果它们不匹配,则控制器将根据需要create/update/delete objects以使其匹配。
控制器实现工作队列处理reconcile的请求。
与http处理程序不同,Controller不会直接处理事件,而是将请求加入队列以最终协调该对象。这意味着可以将多个事件的处理分批处理,并且每次协调时都必须读取系统的完整状态。
*Controllers需要Reconciler来执行从工作队列中拉出的工作。
*Controllers需要配置Watchs为监控reconcile.Requests的请求。
Scheme
每一组 Controllers 都需要一个 Scheme,提供了 Kinds 与对应 Go types 的映射,也就是说给定 Go type 就知道他的 GVK,给定 GVK 就知道他的 Go type,比如说我们给定一个 Scheme: “tutotial.kubebuilder.io/api/v1”.CronJob{} 这个 Go type 映射到 batch.tutotial.kubebuilder.io/v1 的 CronJob GVK,那么从 Api Server 获取到下面的 JSON:
{
"kind": "CronJob",
"apiVersion": "batch.tutorial.kubebuilder.io/v1",
...
}
就能构造出对应的 Go type了,通过这个 Go type 也能正确地获取 GVR 的一些信息,控制器可以通过该 Go type 获取到期望状态以及其他辅助信息进行调谐逻辑。
OwnerReference
K8s GC 在删除一个对象时,任何 ownerReference 是该对象的对象都会被清除,与此同时,Kubebuidler 支持所有对象的变更都会触发 Owner 对象 controller 的 Reconcile 方法。
Webhook
Admission Webhooks是一种扩展kubernetes API的机制。可以使用目标事件类型(对象创建,更新,删除)来配置Webhooks,当某些事件发生时,API服务器将向他们发送Admission Requests。Webhook 可能会转变和(或)验证请求的对象,并将响应发送回API服务器。
admission Webhook有两种类型:mutating 和validating。mutating webhook用于在API服务器允许之前转变核心API对象或CRD实例。和validating 用于验证对象是否满足某些要求。
- Admission Webhooks要求提供Handle来处理接收到的AdmissionReview请求。
Reconciler
Reconciler提供给Controller的方法接口(具体方法为Reconcile):可以随时通过对象的Name和Namespace调用。当Reconciler被调用时,Reconciler将确保系统状态与调用Reconciler时对象中指定的状态相匹配。
示例:为ReplicaSet对象调用Reconciler函数。ReplicaSet指定5个副本,但系统中仅存在3个Pod。Reconciler将再创建2个Pod,并设置OwnerReference指向ReplicaSet和controller=true。
- Reconciler包含Controller的所有业务逻辑。
- 一个Reconciler通常在一个对象(资源实例)上工作。对于单独的对象(资源实例),请使用单独的控制器。如果您希望从其他对象触发该Reconciler,则可以提供一个map(例如所有者引用),该map将触发对帐的对象映射到要该对象。
- 提供Reconciler的协调器对象的Name/Namespace。
- Reconciler不关心负责触发的事件内容或事件类型。例如,创建或更新ReplicaSet无关紧要,Reconciler将始终将系统中Pod的数量与调用对象时指定的数量进行比较。
Source
resource.Source是Controller.Watch的参数。Source提供事件流(streaming event)类型。事件流(streaming event)通常来自watch Kubernetes API event(例如Pod创建,更新,删除)。
示例:source.Kind将Kubernetes API 监控(Watch) GroupVersionKind的Create,Update,Delete事件。
Source 提供 事件流 (例如object的Create, Update, Delete) 通过Watch API为Kubernetes对象
用户应该只使用提供的Source接口,而不是在实现自己的Source。
EventHandler
handler.EventHandler是Controller.Watch的参数,它响应reconcile.Requests事件(排队方式)。
示例:Pod Create事件(来源于上面Source)提供给eventhandler.EnqueueHandler。该Pod Create事件将在一个reconcile.Request排队。
EventHandlers通过使reconcile.enqueques处理一个或多个对象的事件。
EventHandlers可以将一个对象的事件映射到一个reconcile.Request来请求相同类型的对象。
EventHandlers可以将对象的事件映射到reconcile.Request不同类型的对象。例如,将Pod事件映射到拥有的ReplicaSet的reconcile.Request。
EventHandlers可以将一个对象的事件映射到多个协调对象。对相同或不同类型的对象的请求。例如,将Node事件映射到响应集群调整大小事件的对象。
用户应该只使用提供的EventHandler实现,而不是在都实现自己的实现。
Predicate
predicate.Predicate是Controller.Watch的可选参数,用于过滤事件。这使普通的过滤器可以重复使用和组合。
- Predicate接受一个事件并返回布尔值(如果为真,入队)
- Predicate是可选参数
- 用户应该使用Predicate接口,但是可以实现additional Predicate,例如更改generation,更改标签选择器等。
使用方法
下面展现了如何使用controller-runtime框架,官方提供两个示例。这里只讲解第一个(两者的步骤基本一致)
- buildin:现有ReplicaSet资源实现了自定义controller和webhooks。
- crd:这个示例实现了一个新的Kubernetes资源类型ChaosPod,并创建了一个监视它的自定义controller,并实现Webhooks。
步骤
- 创建manager,参数config
- 向manager添加scheme
- 向manager添加controller,该controller包含一个reconciler结构体,我们需要在reconciler结构体实现逻辑处理
- 向manager添加webhook,同样需要实现逻辑处理
- 启动manager.start()
Controller-runtime自定义资源示例
代码结构
├── main.go ## 主函数,事件的逻辑处理
└── pkg
├── groupversion_info.go ## 将GroupVersion加入到scheme
├── resource.go ## api type定义
└── zz_generated.deepcopy.go
main方法:
// 代码来源https://github.com/kubernetes-sigs/controller-runtime/tree/master/examples/crd
// 原代码在alias.go使用了别名。我这里还原了原来的方法和变量名
// 根据config创建manager,config.GetConfigOrDie()使用默认的配置~/.kube/config
manager.New(config.GetConfigOrDie(), manager.Options{})
// 将api注册到Scheme,Scheme提供了GVK到go type的映射。
// 如果多个crd,需要多次调用AddToScheme。
api.AddToScheme(mgr.GetScheme())
// 注册controller到manager。
// For:监控的资源。相当于调用Watches(&source.Kind{Type: apiType},&handler.EnqueueRequestForObject{})
// Owns:拥有的下属资源,如果corev1.Pod{}资源属于api.ChaosPod{},也将会被监控,相当于调用Watches(&source.Kind{Type: <ForType-apiType>}, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true})
// reconciler结构体:继承Reconciler,需要实现该结构体和Reconcile方法。mgr.GetClient()、mgr.GetScheme()是客户端和Scheme,这在前面已经创建好了
err = builder.ControllerManagedBy(mgr).
For(&api.ChaosPod{}).
Owns(&corev1.Pod{}).
Complete(&reconciler{
Client: mgr.GetClient(),
scheme: mgr.GetScheme(),
})
// 构建webhook
err = builder.WebhookManagedBy(mgr).For(&api.ChaosPod{}).Complete()
//启动manager,实际上是启动controller
mgr.Start(ctrl.SetupSignalHandler())
reconciler结构体和Reconcile方法:资源的逻辑处理
//reconciler结构体和Reconcile方法的实现。
type reconciler struct {
client.Client
scheme *runtime.Scheme
}
func (r *reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
log := recLog.WithValues("chaospod", req.NamespacedName)
log.V(1).Info("reconciling chaos pod")
ctx := context.Background()
//获得chaospod资源实例
var chaospod api.ChaosPod
if err := r.Get(ctx, req.NamespacedName, &chaospod); err != nil {
log.Error(err, "unable to get chaosctl")
return ctrl.Result{}, err
}
//获得pod资源实例
var pod corev1.Pod
podFound := true
if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
if !apierrors.IsNotFound(err) {
log.Error(err, "unable to get pod")
return ctrl.Result{}, err
}
podFound = false
}
// 如果有pod,查看NextStop属性(time类型,类似于过期时间)。如果过期,停止,不过期更新NextStop属性
if podFound {
shouldStop := chaospod.Spec.NextStop.Time.Before(time.Now())
if !shouldStop {
return ctrl.Result{RequeueAfter: chaospod.Spec.NextStop.Sub(time.Now()) + 1*time.Second}, nil
}
if err := r.Delete(ctx, &pod); err != nil {
log.Error(err, "unable to delete pod")
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}
// 深拷贝chaospod数据结构
templ := chaospod.Spec.Template.DeepCopy()
pod.ObjectMeta = templ.ObjectMeta
pod.Name = req.Name
pod.Namespace = req.Namespace
pod.Spec = templ.Spec
// 设置chaospod和pod的所属关系
if err := ctrl.SetControllerReference(&chaospod, &pod, r.scheme); err != nil {
log.Error(err, "unable to set pod's owner reference")
return ctrl.Result{}, err
}
// 创建pod
if err := r.Create(ctx, &pod); err != nil {
log.Error(err, "unable to create pod")
return ctrl.Result{}, err
}
// 跟新pod的NextStop
chaospod.Spec.NextStop.Time = time.Now().Add(time.Duration(10*(rand.Int63n(2)+1)) * time.Second)
chaospod.Status.LastRun = pod.CreationTimestamp
if err := r.Update(ctx, &chaospod); err != nil {
log.Error(err, "unable to update chaosctl status")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
kubebuilder
kubebuilder是一个代码生成命令。生成的代码使用controller-runtime库。使用kubebuilder后,只需要构建api type和处理逻辑。所以这里不讲解。
相关用法:<https://cloud.tencent.com/developer/article/1532941>,<https://book.kubebuilder.io/cronjob-tutorial/controller-overview.html>