【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列表中的每一个对象。

//待更

你可能感兴趣的:(Kubernetes)