导读

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框架,官方提供两个示例。这里只讲解第一个(两者的步骤基本一致)

  1. buildin:现有ReplicaSet资源实现了自定义controller和webhooks。
  2. crd:这个示例实现了一个新的Kubernetes资源类型ChaosPod,并创建了一个监视它的自定义controller,并实现Webhooks。

步骤

  1. 创建manager,参数config
  2. 向manager添加scheme
  3. 向manager添加controller,该controller包含一个reconciler结构体,我们需要在reconciler结构体实现逻辑处理
  4. 向manager添加webhook,同样需要实现逻辑处理
  5. 启动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>