惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
J
Java Code Geeks
博客园_首页
Google Online Security Blog
Google Online Security Blog
Hugging Face - Blog
Hugging Face - Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
Intezer
P
Palo Alto Networks Blog
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
O
OpenAI News
SecWiki News
SecWiki News
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
N
News | PayPal Newsroom
Project Zero
Project Zero
Forbes - Security
Forbes - Security
IT之家
IT之家
A
Arctic Wolf
WordPress大学
WordPress大学
Jina AI
Jina AI
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
博客园 - 聂微东
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy International News Feed
Cloudbric
Cloudbric
G
GRAHAM CLULEY
博客园 - 叶小钗
H
Hacker News: Front Page
腾讯CDC
量子位
Help Net Security
Help Net Security
人人都是产品经理
人人都是产品经理
C
Cyber Attacks, Cyber Crime and Cyber Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
爱范儿
爱范儿
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
C
CERT Recently Published Vulnerability Notes

祈雨的笔记

安全多方计算MPC spark原理解析 spark on k8s执行源码分析 spark-operator源码解析 系统压测遇到的缓存击穿问题 我的世界PC与安卓联机 蚂蚁金服流量投放平台的AIG改造 G1大对象致Old区占用率高 日志打印导致接口响应率下跌分析 Groovy加载类导致OOM分析 ERROR日志打印导致CPU满载 记OceanBase死锁超时 应用发版期间服务响应超时 Ark Serverless初探 系统优化复盘一二三 The user specified as a definer does not exist Kong网关初探 API网关选型调研 CPU火焰图常用工具 配置中心选型调研 root操作Nginx导致用户组错误 基于Proxifier使用代理 FastJSON字段智能匹配踩坑 Nacos初探 记一次Nginx服务器CPU满荷载故障 基于券系统分库分表的思考 limit不参与SQL成本计算致索引失效 Linux常用性能监控命令 golang低版本http2偶现400 hostname in certificate didn't match 常见对称加密原理以及应用 tcp_tw_recycle引起的TCP握手失败 记一次mysql执行DDL导致锁表 mysql磁盘占用查看 mysql对text字段update致磁盘增长 elasticsearch报错index read-only TIME_WAIT与Http的Keep-Alive 记一次TIME_WAIT导致连接数报警 记一次生产事故OOM问题排查 redis分布式锁RedissonLock的实现细节 webservice复杂加密签名(2)java调用 webservice复杂加密签名(1)SoapUI mysql延时关联 利用中间人拦截实现APP内H5窜改 MySQL表字符集不同导致关联查询索引失效 通过SSH隧道远程办公 数据落盘方案 BeanDefinitionRegistryPostProcessor扩展 mysql空间索引 HTTPS攻击 spring循环依赖过程解析 elasticsearch性能优化 mysql IS NULL 使用索引 mysql字符集utf8mb4失效踩坑 常用加密算法 xml与javaBean转换 初探InnoDB MVCC源码实现 mysql索引原理 redis之list源码分析 redis之key过期源码分析 redis之string源码分析 redis之hash源码分析 线程池之ThreadPoolExecutor mysql数据页结构 Using temporary与Using filesort mysql回表致索引失效 springboot(28)HTTP连接池 定时任务之ScheduledThreadPoolExecutor elasticsearch常用script聚合 elasticsearch实现like查询 elasticsearch实现乐观锁 elasticsearch准实时原理 springboot(27)自定义缓存读写机制CachingConfigurerSupport optimizer tracing arthas常用命令 HTTP和HTTPS详解 redis集群选举机制 kafka消息重试 一点压力测试的经验 kafka架构概念 explain分析sql语句字段的解释 JVM问题分析处理手册 logstash过滤器(2)date logstash过滤器(3)dissect logstash编码器(1)json logstash编码器(2)multiline logstash表达式 logstash输入(1)通用选项 logstash输入(3)file logstash过滤器(1)通用选项 logstash输入(2)stdin logstash安装 记一次前端vConsole导致JSON序列化错误排查 解决多个异步操作嵌套问题 fastjson反序列化失败autoType is not support RTMP串流服务 POI自动调整列宽错误 Nginx+Lua实现动态黑名单 使用curl命令模拟POST和GET请求 ResponseEntity下载文件
kueue执行源码分析
祈雨的笔记 · 2024-09-06 · via 祈雨的笔记

1. kueue

1.1 概念

img

1.2 样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: Pod
metadata:
name: kueue-sleep-2
labels:
kueue-job: "true"
kueue.x-k8s.io/queue-name: team-a-queue
spec:
containers:
- name: sleep
image: busybox
command:
- sleep
args:
- 60s
resources:
requests:
cpu: 3
sgx.intel.com/epc: 41061273600
sgx.intel.com/enclave: 1
sgx.intel.com/provision: 1
limits:
cpu: 3
sgx.intel.com/epc: 41061273600
sgx.intel.com/enclave: 1
sgx.intel.com/provision: 1
restartPolicy: OnFailure

将多个Pod提交到kueue的队列中,如下图所示,队列资源不足时,Pod的状态阻塞并标记为SchedulingGated

查看被kueue阻塞的Pod信息,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: '2024-08-22T02:16:48Z'
finalizers:
- kueue.x-k8s.io/managed
labels:
kueue-job: 'true'
kueue.x-k8s.io/managed: 'true'
kueue.x-k8s.io/queue-name: team-a-queue
name: kueue-sleep-2
namespace: default
resourceVersion: '35416494'
uid: d1913c90-cfc7-4293-90bd-c4a31b4f65e0
spec:
containers:
- args:
- 60s
command:
- sleep
image: busybox
imagePullPolicy: Always
name: sleep
resources:
limits:
cpu: '3'
sgx.intel.com/enclave: '1'
sgx.intel.com/epc: '41061273600'
sgx.intel.com/provision: '1'
requests:
cpu: '3'
sgx.intel.com/enclave: '1'
sgx.intel.com/epc: '41061273600'
sgx.intel.com/provision: '1'
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-mmhfs
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: OnFailure
schedulerName: default-scheduler
schedulingGates:
- name: kueue.x-k8s.io/admission
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: kube-api-access-mmhfs
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
status:
conditions:
- message: Scheduling is blocked due to non-empty scheduling gates
reason: SchedulingGated
status: 'False'
type: PodScheduled
phase: Pending
qosClass: Burstable

2. schedulingGates

2.1. k8s特性

Kueue是如何实现阻塞Pod的调度创建?K8s在1.27版本添加特性支持schedulingGates,如果Pod中spec的schedulingGates不为空时,则K8s不会调度该Pod的执行,直到该Pod中spec的schedulingGates被移除后,K8s才会继续调度创建该Pod。

该特性文档见以下文档:

  1. https://github.com/orgs/kubernetes/projects/117/views/1?filterQuery=3521&pane=issue&itemId=17721636
  2. https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/3521-pod-scheduling-readiness/README.md

2.2. 实践

如下yaml文件添加的spec.schedulingGates值,提交后如下图,在资源足够的情况下,Pod的状态依然阻塞并标记为SchedulingGated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
name: kueue-sleep-0
spec:
containers:
- name: sleep
image: busybox
command:
- sleep
args:
- 60s
resources:
requests:
cpu: 3
sgx.intel.com/epc: 41061273600
sgx.intel.com/enclave: 1
sgx.intel.com/provision: 1
limits:
cpu: 3
sgx.intel.com/epc: 41061273600
sgx.intel.com/enclave: 1
sgx.intel.com/provision: 1
schedulingGates:
- name: qiyu
restartPolicy: OnFailure

img

移除spec.schedulingGates的值,重新提交后如下图,该Pod被K8s正常调度创建,状态推进到Running

3. 整体流程

梳理kueue的源代码后,核心主链路流程如下:

  1. 用户提交Pod,K8s创建Pod,状态为Pending
  2. kueue的pod_webhook监听到Pod的创建事件,添加Pod中spec的schedulingGates值,通过K8s的特性阻塞该Pod的创建调度
  3. kueue的reconciler监听到事件,发现该Pod需要被kueue接管,但是没有对应的workload,自动创建关联一个workload
  4. kueue的workload_controller监听到workload的创建事件,将该workload实例push到所属的ClusterQueue中
  5. kueue的scheduler轮询ClusterQueue,按优先级和资源额度从队列Pop出待执行的workload,修改状态为已提交
  6. kueue的reconciler监听事件,发现Pod关联的workload状态已提交,则移除Pod中spec的schedulingGates值,不再阻塞该Pod的创建调度,Pod正常创建执行
  7. Pod执行完成/失败
  8. kueue的reconciler监听事件,发现Pod已经完成/失败,修改关联的workload状态为终态
  9. kueued的workload_controller监听workload的状态更新,并同时更新关联的ClusterQueue的资源使用状态

4. 源码分析

4.1. 阻塞Pod创建调度

pkg\controller\jobs\pod\pod_webhook.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

func (w *PodWebhook) Default(ctx context.Context, obj runtime.Object) error {
pod := FromObject(obj)
log := ctrl.LoggerFrom(ctx).WithName("pod-webhook").WithValues("pod", klog.KObj(&pod.pod))
log.V(5).Info("Applying defaults")

if IsPodOwnerManagedByKueue(pod) {
log.V(5).Info("Pod owner is managed by kueue, skipping")
return nil
}


podSelector, err := metav1.LabelSelectorAsSelector(w.podSelector)
if err != nil {
return fmt.Errorf("failed to parse pod selector: %w", err)
}
if !podSelector.Matches(labels.Set(pod.pod.GetLabels())) {
return nil
}


ns := corev1.Namespace{}
err = w.client.Get(ctx, client.ObjectKey{Name: pod.pod.GetNamespace()}, &ns)
if err != nil {
return fmt.Errorf("failed to run mutating webhook on pod %s, error while getting namespace: %w",
pod.pod.GetName(),
err,
)
}
log.V(5).Info("Found pod namespace", "Namespace.Name", ns.GetName())
nsSelector, err := metav1.LabelSelectorAsSelector(w.namespaceSelector)
if err != nil {
return fmt.Errorf("failed to parse namespace selector: %w", err)
}
if !nsSelector.Matches(labels.Set(ns.GetLabels())) {
return nil
}

if jobframework.QueueName(pod) != "" || w.manageJobsWithoutQueueName {
controllerutil.AddFinalizer(pod.Object(), PodFinalizer)

if pod.pod.Labels == nil {
pod.pod.Labels = make(map[string]string)
}
pod.pod.Labels[ManagedLabelKey] = ManagedLabelValue


if gateIndex(&pod.pod) == gateNotFound {
log.V(5).Info("Adding gate")
pod.pod.Spec.SchedulingGates = append(pod.pod.Spec.SchedulingGates, corev1.PodSchedulingGate{Name: SchedulingGateName})
}

if podGroupName(pod.pod) != "" {
if err := pod.addRoleHash(); err != nil {
return err
}
}
}


pod.pod.DeepCopyInto(obj.(*corev1.Pod))
return nil
}

4.2. Pod自动关联创建workload

pkg\controller\jobframework\reconciler.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

func (r *JobReconciler) ReconcileGenericJob(ctx context.Context, req ctrl.Request, job GenericJob) (result ctrl.Result, err error) {
object := job.Object()
log := ctrl.LoggerFrom(ctx).WithValues("job", req.String(), "gvk", job.GVK())
ctx = ctrl.LoggerInto(ctx, log)

defer func() {
err = r.ignoreUnretryableError(log, err)
}()

...

log.V(2).Info("Reconciling Job")


wl, err := r.ensureOneWorkload(ctx, job, object)
if err != nil {
return ctrl.Result{}, err
}

...


if wl == nil {
log.V(3).Info("The workload is nil, handle job with no workload")
err := r.handleJobWithNoWorkload(ctx, job, object)
if err != nil {
if IsUnretryableError(err) {
log.V(3).Info("Handling job with no workload", "unretryableError", err)
} else {
log.Error(err, "Handling job with no workload")
}
}
return ctrl.Result{}, err
}

...
}

4.3. workload关联到所属队列

pkg\controller\core\workload_controller.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

func (r *WorkloadReconciler) Create(e event.CreateEvent) bool {
wl, isWorkload := e.Object.(*kueue.Workload)
if !isWorkload {

return true
}
defer r.notifyWatchers(nil, wl)
status := workload.Status(wl)
log := r.log.WithValues("workload", klog.KObj(wl), "queue", wl.Spec.QueueName, "status", status)
log.V(2).Info("Workload create event")

if status == workload.StatusFinished {
return true
}

ctx := ctrl.LoggerInto(context.Background(), log)
wlCopy := wl.DeepCopy()
workload.AdjustResources(ctx, r.client, wlCopy)


if !workload.HasQuotaReservation(wl) {
if !r.queues.AddOrUpdateWorkload(wlCopy) {
log.V(2).Info("LocalQueue for workload didn't exist or not active; ignored for now")
}
return true
}
if !r.cache.AddOrUpdateWorkload(wlCopy) {
log.V(2).Info("ClusterQueue for workload didn't exist; ignored for now")
}

return true
}

pkg\queue\manager.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


func (m *Manager) AddOrUpdateWorkload(w *kueue.Workload) bool {
m.Lock()
defer m.Unlock()
return m.AddOrUpdateWorkloadWithoutLock(w)
}

func (m *Manager) AddOrUpdateWorkloadWithoutLock(w *kueue.Workload) bool {

qKey := workload.QueueKey(w)
q := m.localQueues[qKey]
if q == nil {
return false
}
wInfo := workload.NewInfo(w, m.workloadInfoOptions...)
q.AddOrUpdate(wInfo)
cq := m.clusterQueues[q.ClusterQueue]
if cq == nil {
return false
}

cq.PushOrUpdate(wInfo)
m.reportPendingWorkloads(q.ClusterQueue, cq)
m.Broadcast()
return true
}

pkg\queue\cluster_queue.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31


func (c *ClusterQueue) PushOrUpdate(wInfo *workload.Info) {
c.rwm.Lock()
defer c.rwm.Unlock()
key := workload.Key(wInfo.Obj)
c.forgetInflightByKey(key)
oldInfo := c.inadmissibleWorkloads[key]
if oldInfo != nil {



if equality.Semantic.DeepEqual(oldInfo.Obj.Spec, wInfo.Obj.Spec) &&
equality.Semantic.DeepEqual(oldInfo.Obj.Status.ReclaimablePods, wInfo.Obj.Status.ReclaimablePods) &&
equality.Semantic.DeepEqual(apimeta.FindStatusCondition(oldInfo.Obj.Status.Conditions, kueue.WorkloadEvicted),
apimeta.FindStatusCondition(wInfo.Obj.Status.Conditions, kueue.WorkloadEvicted)) &&
equality.Semantic.DeepEqual(apimeta.FindStatusCondition(oldInfo.Obj.Status.Conditions, kueue.WorkloadRequeued),
apimeta.FindStatusCondition(wInfo.Obj.Status.Conditions, kueue.WorkloadRequeued)) {
c.inadmissibleWorkloads[key] = wInfo
return
}

delete(c.inadmissibleWorkloads, key)
}
if c.heap.GetByKey(key) == nil && !c.backoffWaitingTimeExpired(wInfo) {
c.inadmissibleWorkloads[key] = wInfo
return
}

c.heap.PushOrUpdate(wInfo)
}

4.4. 队列执行workload

pkg\scheduler\scheduler.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
s.attemptCount++
log := ctrl.LoggerFrom(ctx).WithValues("attemptCount", s.attemptCount)
ctx = ctrl.LoggerInto(ctx, log)





headWorkloads := s.queues.Heads(ctx)
if len(headWorkloads) == 0 {
return wait.KeepGoing
}
startTime := time.Now()


snapshot := s.cache.Snapshot()
logSnapshotIfVerbose(log, &snapshot)


entries := s.nominate(ctx, headWorkloads, snapshot)


sort.Sort(entryOrdering{
enableFairSharing: s.fairSharing.Enable,
entries: entries,
workloadOrdering: s.workloadOrdering,
})






cycleCohortsUsage := cohortsUsage{}
cycleCohortsSkipPreemption := sets.New[string]()
preemptedWorkloads := sets.New[string]()
for i := range entries {
e := &entries[i]

...

e.status = nominated

if err := s.admit(ctx, e, cq); err != nil {
e.inadmissibleMsg = fmt.Sprintf("Failed to admit workload: %v", err)
}
if cq.Cohort != nil {
cycleCohortsSkipPreemption.Insert(cq.Cohort.Name)
}
}


result := metrics.AdmissionResultInadmissible
for _, e := range entries {
logAdmissionAttemptIfVerbose(log, &e)
if e.status != assumed {
s.requeueAndUpdate(ctx, e)
} else {
result = metrics.AdmissionResultSuccess
}
}
metrics.AdmissionAttempt(result, time.Since(startTime))
if result != metrics.AdmissionResultSuccess {
return wait.SlowDown
}
return wait.KeepGoing
}

4.5. Pod取消阻塞

pkg\controller\jobframework\reconciler.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

func (r *JobReconciler) ReconcileGenericJob(ctx context.Context, req ctrl.Request, job GenericJob) (result ctrl.Result, err error) {
object := job.Object()
log := ctrl.LoggerFrom(ctx).WithValues("job", req.String(), "gvk", job.GVK())
ctx = ctrl.LoggerInto(ctx, log)

defer func() {
err = r.ignoreUnretryableError(log, err)
}()

...

log.V(2).Info("Reconciling Job")


wl, err := r.ensureOneWorkload(ctx, job, object)
if err != nil {
return ctrl.Result{}, err
}

...



if job.IsSuspended() {

if workload.IsAdmitted(wl) {
log.V(2).Info("Job admitted, unsuspending")
err := r.startJob(ctx, job, object, wl)
if err != nil {
log.Error(err, "Unsuspending job")
if podset.IsPermanent(err) {

errUpdateStatus := workload.UpdateStatus(ctx, r.client, wl, kueue.WorkloadFinished, metav1.ConditionTrue, FailedToStartFinishedReason, err.Error(), constants.JobControllerName)
if errUpdateStatus != nil {
log.Error(errUpdateStatus, "Updating workload status, on start failure", "err", err)
}
return ctrl.Result{}, errUpdateStatus
}
}
return ctrl.Result{}, err
}

if workload.HasQuotaReservation(wl) {
r.recordAdmissionCheckUpdate(wl, job)
}

q := QueueName(job)
if wl.Spec.QueueName != q {
log.V(2).Info("Job changed queues, updating workload")
wl.Spec.QueueName = q
err := r.client.Update(ctx, wl)
if err != nil {
log.Error(err, "Updating workload queue")
}
return ctrl.Result{}, err
}
log.V(3).Info("Job is suspended and workload not yet admitted by a clusterQueue, nothing to do")
return ctrl.Result{}, nil
}

...


log.V(3).Info("Job running with admitted workload, nothing to do")
return ctrl.Result{}, nil
}

pkg\controller\jobframework\reconciler.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (r *JobReconciler) startJob(ctx context.Context, job GenericJob, object client.Object, wl *kueue.Workload) error {
info, err := getPodSetsInfoFromStatus(ctx, r.client, wl)
if err != nil {
return err
}
msg := fmt.Sprintf("Admitted by clusterQueue %v", wl.Status.Admission.ClusterQueue)

if cj, implements := job.(ComposableJob); implements {

if err := cj.Run(ctx, r.client, info, r.record, msg); err != nil {
return err
}
} else {
if err := clientutil.Patch(ctx, r.client, object, true, func() (bool, error) {
return true, job.RunWithPodSetsInfo(info)
}); err != nil {
return err
}
r.record.Event(object, corev1.EventTypeNormal, ReasonStarted, msg)
}

return nil
}

pkg\controller\jobs\pod\pod_controller.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32



func (p *Pod) Run(ctx context.Context, c client.Client, podSetsInfo []podset.PodSetInfo, recorder record.EventRecorder, msg string) error {
log := ctrl.LoggerFrom(ctx)

if !p.isGroup {
if len(podSetsInfo) != 1 {
return fmt.Errorf("%w: expecting 1 pod set got %d", podset.ErrInvalidPodsetInfo, len(podSetsInfo))
}

if gateIndex(&p.pod) == gateNotFound {
return nil
}

if err := clientutil.Patch(ctx, c, &p.pod, true, func() (bool, error) {

ungatePod(&p.pod)
return true, podset.Merge(&p.pod.ObjectMeta, &p.pod.Spec, podSetsInfo[0])
}); err != nil {
return err
}

if recorder != nil {
recorder.Event(&p.pod, corev1.EventTypeNormal, jobframework.ReasonStarted, msg)
}

return nil
}

...
}

4.6. workload状态推进到终态

pkg\controller\jobframework\reconciler.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

func (r *JobReconciler) ReconcileGenericJob(ctx context.Context, req ctrl.Request, job GenericJob) (result ctrl.Result, err error) {
object := job.Object()
log := ctrl.LoggerFrom(ctx).WithValues("job", req.String(), "gvk", job.GVK())
ctx = ctrl.LoggerInto(ctx, log)

defer func() {
err = r.ignoreUnretryableError(log, err)
}()

...

log.V(2).Info("Reconciling Job")


wl, err := r.ensureOneWorkload(ctx, job, object)
if err != nil {
return ctrl.Result{}, err
}

...


if message, success, finished := job.Finished(); finished {
log.V(3).Info("The workload is already finished")
if wl != nil && !apimeta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadFinished) {
reason := kueue.WorkloadFinishedReasonSucceeded
if !success {
reason = kueue.WorkloadFinishedReasonFailed
}
err := workload.UpdateStatus(ctx, r.client, wl, kueue.WorkloadFinished, metav1.ConditionTrue, reason, message, constants.JobControllerName)
if err != nil && !apierrors.IsNotFound(err) {
return ctrl.Result{}, err
}
r.record.Eventf(object, corev1.EventTypeNormal, ReasonFinishedWorkload,
"Workload '%s' is declared finished", workload.Key(wl))
}


if err := r.finalizeJob(ctx, job); err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

...


log.V(3).Info("Job running with admitted workload, nothing to do")
return ctrl.Result{}, nil
}

4.7. 队列资源状态自动更新

pkg\controller\core\workload_controller.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

func (r *WorkloadReconciler) Update(e event.UpdateEvent) bool {
oldWl, isWorkload := e.ObjectOld.(*kueue.Workload)
if !isWorkload {

return true
}
wl := e.ObjectNew.(*kueue.Workload)
defer r.notifyWatchers(oldWl, wl)

...

default:


if err := r.cache.UpdateWorkload(oldWl, wlCopy); err != nil {
log.Error(err, "Updating workload in cache")
}
}

return true
}

pkg\cache\cache.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func (c *Cache) UpdateWorkload(oldWl, newWl *kueue.Workload) error {
c.Lock()
defer c.Unlock()
if workload.HasQuotaReservation(oldWl) {
cq, ok := c.clusterQueues[string(oldWl.Status.Admission.ClusterQueue)]
if !ok {
return errors.New("old ClusterQueue doesn't exist")
}
cq.deleteWorkload(oldWl)
}
c.cleanupAssumedState(oldWl)

if !workload.HasQuotaReservation(newWl) {
return nil
}
cq, ok := c.clusterQueues[string(newWl.Status.Admission.ClusterQueue)]
if !ok {
return errors.New("new ClusterQueue doesn't exist")
}
if c.podsReadyTracking {
c.podsReadyCond.Broadcast()
}

return cq.addWorkload(newWl)
}

pkg\cache\clusterqueue.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (c *clusterQueue) addWorkload(w *kueue.Workload) error {
k := workload.Key(w)
if _, exist := c.Workloads[k]; exist {
return errors.New("workload already exists in ClusterQueue")
}
wi := workload.NewInfo(w, c.workloadInfoOptions...)
c.Workloads[k] = wi

c.updateWorkloadUsage(wi, 1)
if c.podsReadyTracking && !apimeta.IsStatusConditionTrue(w.Status.Conditions, kueue.WorkloadPodsReady) {
c.WorkloadsNotReady.Insert(k)
}
c.reportActiveWorkloads()
return nil
}