友情提示:全文6000多文字,预计阅读时间11分钟
一、前言
Kubernetes(K8s) 作为一个可移植的、可扩展的开源平台,已经被广泛应用于管理容器化的工作负载和服务。虽然K8s本身提供了丰富的资源类型,但是在使用中,仍然存在扩展资源的需求。
本文将以项目中遇到的一个实际问题作为切入点,详细分析问题原因,同时介绍下使用第三方框架扩展K8s资源时的工作原理。
二、背景
在描述具体问题前,先对涉及的K8s概念做一些简单介绍。
图(1) CRD关系图
1)CRD
CRD全称是CustomResourceDefinition,即自定义资源。CRD也是K8s的一种资源,创建一个CRD即在K8s中定义了一种新的资源类型,这个资源类型可以像K8s中的原生资源一样,既可以通过kubectl命令行,也可以通过访问apiserver来进行操作。
2)Resource Event
这里的Event是当资源本身发生变化时触发的事件,并不是K8s中的Event资源。共有四种类型,CreateEvent,UpdateEvent,DeleteEvent,GenericEvent。其中GenericEvent用来处理未知类型的Event,比如非集群内资源事件,一般不会使用。如果控制器"订阅"了这个资源,那么资源发生变化时,比如被更新或者被删除时,控制器会获取到这个事件。Event是联系控制器和资源的数据通道。
3)controller-runtime
controller-runtime被用来创建K8s资源控制器,如果引入了CRD的话,单纯定义这个资源只能起到存数据的作用,并没有业务处理逻辑。通过controller-runtime可以监听资源的变化,捕获Resource Event,触发相应的处理流程,让这个自定义资源表现出和原生资源相同的行为。
4)kubebuilder
kubebuilder是一个根据模板生成代码的工具,使用kubebuilder可以快速渲染出一个依赖controller-runtime的控制器。在分析controller-runtime之前,需要先用它来生成一个controller。
为了使CRD像原生资源那样工作,需要创建对应的控制器(controller),这个控制器需要捕获资源发生变化时的事件,完成指定的操作。理解了CRD的使用方法和运行原理,这样遇在到问题时,才能够方便定位和解决。
三、问题
在项目中使用controller-runtime监听CRD资源过程中,发现在资源变化触发事件,如果请求事件没有被正确处理而返回错误时,事件会被重复处理,但是每次处理的时间间隔并不规则。表现为日志中显示事件被再处理的间隔时间并不等长,从几秒到十几分钟的分布。精简后的日志显示如下。
controller.go:36] failed at: 2020-06-30 08:14:38.492813441 +0000 UTC m=+0.733523768controller.go:36] failed at: 2020-06-30 08:14:39.493302606 +0000 UTC m=+1.734012936controller.go:36] failed at: 2020-06-30 08:14:40.49372105 +0000 UTC m=+2.734431343controller.go:36] failed at: 2020-06-30 08:14:41.49402338 +0000 UTC m=+3.734733690controller.go:36] failed at: 2020-06-30 08:14:42.494593356 +0000 UTC m=+4.735303683controller.go:36] failed at: 2020-06-30 08:14:43.495217453 +0000 UTC m=+5.735927765controller.go:36] failed at: 2020-06-30 08:14:44.49564783 +0000 UTC m=+6.736358135controller.go:36] failed at: 2020-06-30 08:14:45.496101659 +0000 UTC m=+7.736811950controller.go:36] failed at: 2020-06-30 08:14:46.496564312 +0000 UTC m=+8.737274709controller.go:36] failed at: 2020-06-30 08:14:47.776977606 +0000 UTC m=+10.017687933controller.go:36] failed at: 2020-06-30 08:14:50.339145951 +0000 UTC m=+12.579856239controller.go:36] failed at: 2020-06-30 08:14:55.459778396 +0000 UTC m=+17.700488722controller.go:36] failed at: 2020-06-30 08:15:05.700252055 +0000 UTC m=+27.940962359controller.go:36] failed at: 2020-06-30 08:15:26.180615289 +0000 UTC m=+48.421325604controller.go:36] failed at: 2020-06-30 08:16:07.140934099 +0000 UTC m=+89.381644386controller.go:36] failed at: 2020-06-30 08:17:29.061481373 +0000 UTC m=+171.302191703controller.go:36] failed at: 2020-06-30 08:20:12.901929639 +0000 UTC m=+335.142639963
大体观察到,前面日志的时间差值在1s左右,后面的差值变成了5s,10s,20s左右。所以产生了下面的疑问:
1) 这些事件的处理时间间隔是不是会持续增加?
2) 如果持续增加,最大会有多长?
3) 这样持续的事件处理会不会影响到controller性能?
4) 当集群中事件数量规模扩大时会不会冲刷掉正常的请求?
四、分析
为了解Event是如何被处理的,将从上到下分析下controller-runtime的启动流程,Reconcile函数在何时被调用、调用出错时如何再处理等步骤。为了聚焦在事件再处理的步骤上,对前面的几个步骤先做下简单的描述。也借此了解下controller-runtime的整体架构。
图(2) Kubernetes controller架构图(图片来自网络)
上面提到的问题在步骤7和步骤8中产生,也是本文分析的重点。
在分析controller-runtime之前,需要先使用kubebuilder构建一个简单的controller,因为这不是本文的重点,所以下面略过生成步骤,直接进入到分析步骤。kubebuilder的使用参考链接(https://book.kubebuilder.io/quick-start.html)。
其中reconcile.Reconcile函数被简化为
go func (r *ClusterReconciler) Reconcile(req ctrl.Request) (ctrl.Result,error) {
klog.Infof("failed at: %s",time.Now()) return ctrl.Result{},errors.New("err") }
1)controller-runtime启动
*controller-runtime版本: 0.5.5
图(3) controller-runtime启动
在controller-runtime中,Event的处理逻辑是Reconciler对象,Reconciler被controller引用,这里的controller便是控制器。在controller之上,还有一个更高层的管理者manager。manager中可以设置多个controller,但是一个controller中只有一个Reconciler。
1.1)生成manager
goimport ("sigs.k8s.io/controller-runtim