【Kubernetes】CronJob源码探寻

在我们线上环境,因为CronJob的数量太多(20074个CronJob),导致线上部分CronJob出现了延迟,甚至不再执行。

从定性分析上,可以确认是CronJob数量太多导致,因为出现问题是在某一天增加了800 * 6 * 3 = 14400个CronJob之后,我发现原本应该当场执行的CronJob过了两天依然没有执行。

现在需要定量确认导致这样现象原因是什么,才能通过某一种方案去优化他,可以通过调参解决?还是需要业务向技术妥协?

找到具体做CronJob时间检查的操作 \kubernetes\pkg\controller\cronjob\cronjob_controller.go
这份代码是和k8s集群中 kube-controller-manager组件在一起的。所以他的日志是打印在这个组件里的。

// Run starts the main goroutine responsible for watching and syncing jobs.
func (jm *Controller) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	klog.Infof("Starting CronJob Manager")
	// Check things every 10 second.
	go wait.Until(jm.syncAll, 10*time.Second, stopCh)
	<-stopCh
	klog.Infof("Shutting down CronJob Manager")
}

从注释上来看,确实是由这个jm.syncAll() 函数来负责CronJob实行逻辑。

go wait.Until(jm.syncAll, 10*time.Second, stopCh)

这个函数会每十秒钟启动一个协程来执行具体逻辑。
大概是
等待十秒 -> 开始执行逻辑 -> 执行逻辑结束 -> 等待十秒 -> 开始执行逻辑 -> … …

// syncAll lists all the CronJobs and Jobs and reconciles them.
func (jm *Controller) syncAll() {
	// List children (Jobs) before parents (CronJob).
	// This guarantees that if we see any Job that got orphaned by the GC orphan finalizer,
	// we must also see that the parent CronJob has non-nil DeletionTimestamp (see #42639).
	// Note that this only works because we are NOT using any caches here.
	jobListFunc := func(opts metav1.ListOptions) (runtime.Object, error) {
		return jm.kubeClient.BatchV1().Jobs(metav1.NamespaceAll).List(context.TODO(), opts)
	}

	js := make([]batchv1.Job, 0)
	err := pager.New(pager.SimplePageFunc(jobListFunc)).EachListItem(context.Background(), metav1.ListOptions{}, func(object runtime.Object) error {
		jobTmp, ok := object.(*batchv1.Job)
		if !ok {
			return fmt.Errorf("expected type *batchv1.Job, got type %T", jobTmp)
		}
		js = append(js, *jobTmp)
		return nil
	})

	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Failed to extract job list: %v", err))
		return
	}

同样从注释上看 syncAll函数不单单取了所有CronJob资源,也获取了所有的Job资源。

	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Failed to extract job list: %v", err))
		return
	}

	klog.V(4).Infof("Found %d jobs", len(js))
	cronJobListFunc := func(opts metav1.ListOptions) (runtime.Object, error) {
		return jm.kubeClient.BatchV1beta1().CronJobs(metav1.NamespaceAll).List(context.TODO(), opts)
	}

往下看,也是为CronJob构造了一个获取所有namespace下面的CronJob函数。

	klog.V(4).Infof("Found %d jobs", len(js))
	cronJobListFunc := func(opts metav1.ListOptions) (runtime.Object, error) {
		return jm.kubeClient.BatchV1beta1().CronJobs(metav1.NamespaceAll).List(context.TODO(), opts)
	}

	jobsByCj := groupJobsByParent(js)
	klog.V(4).Infof("Found %d groups", len(jobsByCj))

=======================分割线=======================
// groupJobsByParent groups jobs into a map keyed by the job parent UID (e.g. cronJob).
// It has no receiver, to facilitate testing.
func groupJobsByParent(js []batchv1.Job) map[types.UID][]batchv1.Job {
	jobsByCj := make(map[types.UID][]batchv1.Job)
	for _, job := range js {
		parentUID, found := getParentUIDFromJob(job)
		if !found {
			klog.V(4).Infof("Unable to get parent uid from job %s in namespace %s", job.Name, job.Namespace)
			continue
		}
		jobsByCj[parentUID] = append(jobsByCj[parentUID], job)
	}
	return jobsByCj
}

在groupJobsByParent 这一步中会将取到的Jobs按照 UID或者说parent id 整合为到一个列表。为下一步检查做准备。

klog.V(4).Infof("Found %d groups", len(jobsByCj))
err = pager.New(pager.SimplePageFunc(cronJobListFunc)).EachListItem(context.Background(), metav1.ListOptions{}, func(object runtime.Object) error {
	cj, ok := object.(*batchv1beta1.CronJob)
	if !ok {
		return fmt.Errorf("expected type *batchv1beta1.CronJob, got type %T", cj)
	}
	syncOne(cj, jobsByCj[cj.UID], time.Now(), jm.jobControl, jm.cjControl, jm.recorder)
	cleanupFinishedJobs(cj, jobsByCj[cj.UID], jm.jobControl, jm.cjControl, jm.recorder)
	return nil
})

可以看到Kubernetes会遍历获取到的CronJob列表中的每一个对象。

我们线上环境kube-controller-manager 报的是这个

E0810 11:01:07.124377       1 cronjob_controller.go:146] Failed to extract cronJobs list: The provided continue parameter is too old to display a consistent list result. You can start a new list without the continue parameter, or use the continue token in this response to retrieve the remainder of the results. Continuing with the provided token results in an inconsistent list - objects that were created, modified, or deleted between the time the first chunk was returned and now may show up in the list.

这里面的关键词我感觉是 continue parameter , 所以查询了这个词的相关文档
在大集群中进行资源操作,如果一次性操作的集群资源过多,会对集群造成很重的负担。所以在1.9版本的kubernetes里引入了chunk操作。它允许使用者将一个大量资源的操作分割为多个小批量资源的操作。continue parammeter 在这里面扮演一个承上启下的操作枢纽。他是一串用作检查鉴权的token,除了第一次以外,每一次的值都是上一次请求返回json串中会带上。

Like a watch operation, a continue token will expire after a short
amount of time (by default 5 minutes) and return a 410 Gone if more
results cannot be returned. In this case, the client will need to
start from the beginning or omit the limit parameter.

根据文章中的意思,这个值默认是五分钟,所以当小批量获取CronJob资源信息之后,处理这块函数的内容,如果时间特别长。那么下次再查询的时候,上一次的token很有可能过期了。
在这里插入图片描述
所以可以进行的优化操作是,把函数内所花费的时间尽量缩短,是一个可能的优化方向。

优化之后,发现依然还是和原来报的一样的错,说明优化力度不够,依然需要继续优化。

在Stackoverflow发帖询问之后,得到了一个回复。说是这个token的过期时间和etcd里面的设置相关。在回复中的文档也提到了当集群扩大时,可以适当增加一些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值