在我们线上环境,因为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列表中的每一个对象。
//待更