总结
在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了
containers:
- name: pi
image: perl
command: [“perl”, “-Mbignum=bpi”, “-wle”, “print bpi(2000)”]
restartPolicy: Never
backoffLimit: 4
这个Job会创建一个容器,然后执行命令进行π的计算,
然后我们创建这个pod:
$ kubectl create -f job.yaml
$ kubectl describe jobs/pi
Name: pi
Namespace: default
Selector: controller-uid=cf78ebe4-07f9-4234-b8f9-2fe92df352ea
Labels: controller-uid=cf78ebe4-07f9-4234-b8f9-2fe92df352ea
job-name=pi
Annotations: Parallelism: 1
Completions: 1
…
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=cf78ebe4-07f9-4234-b8f9-2fe92df352ea
job-name=pi
Containers:
pi:
Image: resouer/ubuntu-bc
…
Events:
Type Reason Age From Message
Normal SuccessfulCreate 29m job-controller Created pod: pi-g9fs4
Normal Completed 27m job-controller Job completed
可以看到创建对象后,Pod模板中,被自动加上了一个controller-uid=< 一个随机字符串 > 这样的 Label。而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,从而 保证了 Job 与它所管理的 Pod 之间的匹配关系。这个uid避免了不同Job对象的Pod不会重合。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pi-g9fs4 0/1 Completed 0 33m
$ kubectl describe pod pi-g9fs4
…
Events:
Type Reason Age From Message
Normal Scheduled 35m default-scheduler Successfully assigned default/pi-g9fs4 to 192.168.13.130
Normal Pulling 35m kubelet, 192.168.13.130 Pulling image “resouer/ubuntu-bc”
Normal Pulled 35m kubelet, 192.168.13.130 Successfully pulled image “resouer/ubuntu-bc”
Normal Created 35m kubelet, 192.168.13.130 Created container pi
Normal Started 35m kubelet, 192.168.13.130 Started container pi
我们可以看到Pod在创建好运行完毕之后会进入到Completed状态。上面的yaml定义中restartPolicy=Never也保证了这个Pod只会运行一次。
如果创建的Pod运行失败了,那么Job Controller会不断创建一个新的Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
pi-55h89 0/1 ContainerCreating 0 2s
pi-tqbcz 0/1 Error 0 5s
参数说明
spec.backoffLimit
我们在上面的字段中定义了为4,表示重试次数为4。
restartPolicy
在运行过程中,可能发生各种系统问题导致的Pod运行失败,如果设置restartPolicy为OnFailure,那么在运行中发生的失败后Job Controller会重启Pod里面的容器,而不是创建新的Pod。
还可以设置为Never,表示容器运行失败之后不会重启。
spec.activeDeadlineSeconds
表示最长运行时间,单位是秒。如:
spec:
backoffLimit: 5
activeDeadlineSeconds: 100
这样设置之后会进入pastActiveDeadline进行校验job.Spec.ActiveDeadlineSeconds
是不是为空,不是空的话,会比较Pod的运行时间duration是否大于job.Spec.ActiveDeadlineSeconds
设置的值,如果大于,那么会标记Pod终止的原因是DeadlineExceeded。
在job Controller的源码中,我们可以看到这部分的逻辑:
job Controller首先会去校验任务是不是处理次数是不是超过了BackoffLimit设置,如果没有超过的话就校验有没有设置ActiveDeadlineSeconds,如果设置了的话,就校验当前job运行时间是否超过了ActiveDeadlineSeconds设置的的时间,超过了那么会打上标记,表示这个job运行失败。
…
jobHaveNewFailure := failed > job.Status.Failed
exceedsBackoffLimit := jobHaveNewFailure && (active != *job.Spec.Parallelism) &&
(int32(previousRetry)+1 > *job.Spec.BackoffLimit)
if exceedsBackoffLimit || pastBackoffLimitOnFailure(&job, pods) {
// check if the number of pod restart exceeds backoff (for restart OnFailure only)
// OR if the number of failed jobs increased since the last syncJob
jobFailed = true
failureReason = “BackoffLimitExceeded”
failureMessage = “Job has reached the specified backoff limit”
} else if pastActiveDeadline(&job) {
jobFailed = true
failureReason = “DeadlineExceeded”
failureMessage = “Job was active longer than specified deadline”
}
…
func pastActiveDeadline(job *batch.Job) bool {
if job.Spec.ActiveDeadlineSeconds == nil || job.Status.StartTime == nil {
return false
}
now := metav1.Now()
start := job.Status.StartTime.Time
duration := now.Time.Sub(start)
allowedDuration := time.Duration(*job.Spec.ActiveDeadlineSeconds) * time.Second
return duration >= allowedDuration
}
Job的并行任务
在 Job 对象中,负责并行控制的参数有两个:
-
spec.parallelism
表示一个 Job 在任意时间最多可以启动多少个 Pod 同时运行; -
spec.completions
表示Job 的最小完成数。
举例:
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
parallelism: 2
completions: 4
template:
spec:
containers:
- name: pi
image: perl
command: [“perl”, “-Mbignum=bpi”, “-wle”, “print bpi(2000)”]
restartPolicy: Never
backoffLimit: 4
在创建任务之后,我们可以看到最多只会有两个Pod同时运行:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pi-8fsrn 0/1 ContainerCreating 0 30s
pi-job-67kwg 0/1 Completed 0 14h
pi-wlbm5 0/1 ContainerCreating 0 30s
每当有一个 Pod 完成计算进入 Completed 状态时,就会有一个新的 Pod 被自动创建出来,并且快速地从 Pending 状态进入到 ContainerCreating 状态。
最终我们可以看到job的COMPLETIONS会标记全部完成:
$ kubectl get job
NAME COMPLETIONS DURATION AGE
pi 4/4 2m52s 2m52s
Job Controller中会会根据配置的并发数来确认当前处于 active 的 pods 数量是否合理,如果不合理的话则进行调整。
如果处于 active 状态的 pods 数大于 job 设置的并发数 job.Spec.Parallelism
,则并发删除多余的 active pods。
Job源码分析
通过上面的使用例子,我们可以看到job的使用时非常的简单的,下面我们通过源码来理解一下这job的运行逻辑。
核心源码位置在job_controller.go中Controller类的syncJob方法中:
syncJob方法很长,我还是想要将这个方法拆开来进行说明。
Controller#syncJob
func (jm *Controller) syncJob(key string) (bool, error) {
…
job := *sharedJob
// if job was finished previously, we don’t want to redo the termination
// 如果job已经跑完了,那么直接返回,避免重跑
if IsJobFinished(&job) {
return true, nil
}
// retrieve the previous number of retry
// 获取job的重试次数
previousRetry := jm.queue.NumRequeues(key)
jobNeedsSync := jm.expectations.SatisfiedExpectations(key)
//获取这个job的pod列表
pods, err := jm.getPodsForJob(&job)
if err != nil {
return false, err
}
//找到这个job中仍然活跃的pod
activePods := controller.FilterActivePods(pods)
active := int32(len(activePods))
//获取job中运行成功的pod数和运行失败的pod数
succeeded, failed := getStatus(pods)
conditions := len(job.Status.Conditions)
// job first start
//设置job 的启动时间
if job.Status.StartTime == nil {
now := metav1.Now()
job.Status.StartTime = &now
// enqueue a sync to check if job past ActiveDeadlineSeconds
if job.Spec.ActiveDeadlineSeconds != nil {
klog.V(4).Infof(“Job %s has ActiveDeadlineSeconds will sync after %d seconds”,
key, *job.Spec.ActiveDeadlineSeconds)
jm.queue.AddAfter(key, time.Duration(*job.Spec.ActiveDeadlineSeconds)*time.Second)
}
}
…
}
这部分的代码会校验job是否已经跑完了,如果跑完了直接返回;
然后获取job的重试次数,以及与job关联的pod列表,并计算出活跃的pod数量、运行成功的pod数量、以及失败的pod数量;
接下来如果job是首次启动,那么需要设置job的启动时间。
继续:
func (jm *Controller) syncJob(key string) (bool, error) {
…
var manageJobErr error
jobFailed := false
var failureReason string
var failureMessage string
//failed次数超过了job.Status.Failed说明有新的pod运行失败了
jobHaveNewFailure := failed > job.Status.Failed
// new failures happen when status does not reflect the failures and active
// is different than parallelism, otherwise the previous controller loop
// failed updating status so even if we pick up failure it is not a new one
//如果有新的pod运行失败,并且活跃的pod不等于并行Parallelism数
//并且重试次数超过了BackoffLimit
exceedsBackoffLimit := jobHaveNewFailure && (active != *job.Spec.Parallelism) &&
(int32(previousRetry)+1 > *job.Spec.BackoffLimit)
//重试次数是否超标
if exceedsBackoffLimit || pastBackoffLimitOnFailure(&job, pods) {
// check if the number of pod restart exceeds backoff (for restart OnFailure only)
// OR if the number of failed jobs increased since the last syncJob
jobFailed = true
failureReason = “BackoffLimitExceeded”
failureMessage = “Job has reached the specified backoff limit”
// job运行时间是否超过了ActiveDeadlineSeconds
} else if pastActiveDeadline(&job) {
jobFailed = true
failureReason = “DeadlineExceeded”
failureMessage = “Job was active longer than specified deadline”
}
…
}
这段代码是用来判断job是否运行失败,判断依据是job重试次数是否超过了BackoffLimit,以及job的运行时间是否超过了设置的ActiveDeadlineSeconds。
上面这里会获取上一次运行的Failed次数和这次的job的failed次数进行比较,如果failed多了表示又产生了新的运行失败的pod。如果运行失败会标识出失败原因,以及设置jobFailed为true。
在上面的代码中调用了pastBackoffLimitOnFailure方法和pastActiveDeadline方法,我们分别看一下:
pastBackoffLimitOnFailure
func pastBackoffLimitOnFailure(job *batch.Job, pods []*v1.Pod) bool {
//如果RestartPolicy为OnFailure,那么直接返回
if job.Spec.Template.Spec.RestartPolicy != v1.RestartPolicyOnFailure {
return false
}
result := int32(0)
for i := range pods {
po := pods[i]
//如果pod状态为Running或Pending
//获取到pod对应的重启次数以及Container状态,包含pod中的InitContainer
if po.Status.Phase == v1.PodRunning || po.Status.Phase == v1.PodPending {
for j := range po.Status.InitContainerStatuses {
stat := po.Status.InitContainerStatuses[j]
result += stat.RestartCount
}
for j := range po.Status.ContainerStatuses {
stat := po.Status.ContainerStatuses[j]
result += stat.RestartCount
}
}
}
//如果BackoffLimit等于,那么只要重启了一次,则返回true
if *job.Spec.BackoffLimit == 0 {
return result > 0
}
//比较重启次数是否超过了BackoffLimit
return result >= *job.Spec.BackoffLimit
}
这个方法会校验job的RestartPolicy策略,不是OnFailure才继续往下执行。然后会遍历pod列表,将pod列表中的重启次数累加并与BackoffLimit进行比较,超过了则返回true。
pastActiveDeadline
func pastActiveDeadline(job *batch.Job) bool {
if job.Spec.ActiveDeadlineSeconds == nil || job.Status.StartTime == nil {
return false
}
now := metav1.Now()
start := job.Status.StartTime.Time
duration := now.Time.Sub(start)
allowedDuration := time.Duration(*job.Spec.ActiveDeadlineSeconds) * time.Second
return duration >= allowedDuration
}
这个方法会算出job的运行时间duration,然后和ActiveDeadlineSeconds进行比较,如果超过了则返回true。
我们回到syncJob中继续往下:
func (jm *Controller) syncJob(key string) (bool, error) {
…
//job运行失败
if jobFailed {
errCh := make(chan error, active)
//将job里面的active的pod删除
jm.deleteJobPods(&job, activePods, errCh)
select {
case manageJobErr = <-errCh:
if manageJobErr != nil {
break
}
default:
}
// update status values accordingly
//清空active数
failed += active
active = 0
job.Status.Conditions = append(job.Status.Conditions, newCondition(batch.JobFailed, failureReason, failureMessage))
jm.recorder.Event(&job, v1.EventTypeWarning, failureReason, failureMessage)
} else {
//如果job需要同步,并且job没有被删除,则调用manageJob进行同步工作
if jobNeedsSync && job.DeletionTimestamp == nil {
active, manageJobErr = jm.manageJob(activePods, succeeded, &job)
}
//完成数等于pod 运行成功的数量
completions := succeeded
complete := false
//如果没有设置Completions,那么只要有pod完成,那么job就算完成
if job.Spec.Completions == nil {
if succeeded > 0 && active == 0 {
complete = true
}
} else {
//如果实际完成数大于或等于Completions
if completions >= *job.Spec.Completions {
complete = true
//如果还有pod处于active状态,发送EventTypeWarning事件
if active > 0 {
jm.recorder.Event(&job, v1.EventTypeWarning, “TooManyActivePods”, “Too many active pods running after completion count reached”)
}
//如果实际完成数大于Completions,发送EventTypeWarning事件
if completions > *job.Spec.Completions {
jm.recorder.Event(&job, v1.EventTypeWarning, “TooManySucceededPods”, “Too many succeeded pods running after completion count reached”)
}
}
}
//job完成了则更新 job.Status.Conditions 和 job.Status.CompletionTime 字段
if complete {
job.Status.Conditions = append(job.Status.Conditions, newCondition(batch.JobComplete, “”, “”))
now := metav1.Now()
job.Status.CompletionTime = &now
jm.recorder.Event(&job, v1.EventTypeNormal, “Completed”, “Job completed”)
}
}
…
}
这一段中会根据jobFailed的状态进行判断。
如果jobFailed为true则表示这个job运行失败,需要删除这个job关联的所有pod,并且清空active数。
如果jobFailed为false则表示这个job处于非false状态。如果job需要同步,并且job没有被删除,则调用manageJob进行同步工作;
接下来会对设置的Completions进行处理,如果Completions没有设置,那么只要有一个pod运行完毕,那么这个pod就算完成;
如果实际完成的pod数量大于completions或仍然有pod处于active中,则发送相应的事件信息。最后更新job的状态为完成。
我们接下来一口气看看manageJob中这个同步方法里面做了什么,这个方法是job管理pod运行数量的核心方法:
Controller#manageJob
func (jm *Controller) manageJob(activePods []*v1.Pod, succeeded int32, job *batch.Job) (int32, error) {
…
//如果处于 active 状态的 pods 数大于 job 设置的并发数 job.Spec.Parallelism
if active > parallelism {
//多出的个数
diff := active - parallelism
errCh = make(chan error, diff)
jm.expectations.ExpectDeletions(jobKey, int(diff))
klog.V(4).Infof(“Too many pods running job %q, need %d, deleting %d”, jobKey, parallelism, diff)
//pods 排序,以便可以优先删除一些pod:
// 判断 pod 状态:Not ready < ready
// 是否已经被调度:unscheduled< scheduled
//判断 pod phase :pending < running
sort.Sort(controller.ActivePods(activePods))
active -= diff
wait := sync.WaitGroup{}
wait.Add(int(diff))
for i := int32(0); i < diff; i++ {
//并发删除多余的 active pods
go func(ix int32) {
defer wait.Done()
if err := jm.podControl.DeletePod(job.Namespace, activePods[ix].Name, job); err != nil {
// Decrement the expected number of deletes because the informer won’t observe this deletion
jm.expectations.DeletionObserved(jobKey)
if !apierrors.IsNotFound(err) {
klog.V(2).Infof(“Failed to delete %v, decremented expectations for job %q/%q”, activePods[ix].Name, job.Namespace, job.Name)
activeLock.Lock()
active++
activeLock.Unlock()
errCh <- err
utilruntime.HandleError(err)
}
}
}(i)
}
wait.Wait()
//若处于 active 状态的 pods 数小于 job 设置的并发数,则需要创建出新的 pod
} else if active < parallelism {
wantActive := int32(0)
//如果没有声明Completions,那么active的pod应该等于parallelism,如果有pod已经完成了,那么不再创建新的。
if job.Spec.Completions == nil {
if succeeded > 0 {
wantActive = active
} else {
wantActive = parallelism
}
// 如果声明了Completions,那么需要比较Completions和succeeded
// 如果wantActive大于parallelism,那么需要创建的Pod数等于parallelism
} else {
// Job specifies a specific number of completions. Therefore, number
// active should not ever exceed number of remaining completions.
wantActive = *job.Spec.Completions - succeeded
if wantActive > parallelism {
wantActive = parallelism
}
}
//计算出 diff 数
diff := wantActive - active
if diff < 0 {
utilruntime.HandleError(fmt.Errorf(“More active than wanted: job %q, want %d, have %d”, jobKey, wantActive, active))
diff = 0
}
//表示已经有足够的pod,不需要再创建了
if diff == 0 {
return active, nil
}
jm.expectations.ExpectCreations(jobKey, int(diff))
errCh = make(chan error, diff)
klog.V(4).Infof(“Too few pods running job %q, need %d, creating %d”, jobKey, wantActive, diff)
active += diff
wait := sync.WaitGroup{}
面试准备+复习分享:
为了应付面试也刷了很多的面试题与资料,现在就分享给有需要的读者朋友,资料我只截取出来一部分哦
should not ever exceed number of remaining completions.
wantActive = *job.Spec.Completions - succeeded
if wantActive > parallelism {
wantActive = parallelism
}
}
//计算出 diff 数
diff := wantActive - active
if diff < 0 {
utilruntime.HandleError(fmt.Errorf(“More active than wanted: job %q, want %d, have %d”, jobKey, wantActive, active))
diff = 0
}
//表示已经有足够的pod,不需要再创建了
if diff == 0 {
return active, nil
}
jm.expectations.ExpectCreations(jobKey, int(diff))
errCh = make(chan error, diff)
klog.V(4).Infof(“Too few pods running job %q, need %d, creating %d”, jobKey, wantActive, diff)
active += diff
wait := sync.WaitGroup{}
面试准备+复习分享:
为了应付面试也刷了很多的面试题与资料,现在就分享给有需要的读者朋友,资料我只截取出来一部分哦
[外链图片转存中…(img-iXFlXxyM-1715407025070)]