资源预留

资源预留是koord-scheduler的一种为某些特定Pod或负载预留节点资源的能力。

介绍

Pod是kubernetes节点资源分配的基础载体,他根据业务逻辑绑定对应的资源需求。但是我们可能分为一些还没创建的特定Pod和负载分配资源,例如:

  1. 抢占:已经存在的抢占规则不能保证只有正在抢占中的Pod才能分配抢占的资源,我们期望调度器能锁定资源,防止这些资源被有相同或更高优先级的其他Pod抢占。
  2. 重调度:在重调度场景下,最好能保证在Pod被重调度之前保留足够的资源。否则,被重调度的Pod可能再也没法运行,然后对应的应用可能就会崩溃。
  3. 水平扩容:为了能更精准地进行水平扩容,我们希望能为扩容的Pod副本分配节点资源。
  4. 资源预分配:即使当前的资源还不可用,我们可能想为将来的资源需求提前预留节点资源。

为了增强kubernetes的资源调度能力,koord-scheduler提供了一个名字叫Reservation的调度API,允许我们为一些当前还未创建的特定的Pod和负载,提前预留节点资源。

image

更多信息,请看 设计文档:资源预留。

设置

前提

  • Kubernetes >= 1.18
  • Koordinator >= 0.6

安装步骤

请确保Koordinator的组件已经在你的集群中正确安装,如果还未正确安装,请参考安装说明。

配置

资源预留功能默认启用,你无需对koord-scheduler配置做任何修改,即可使用。

使用指南

快速上手

  1. 使用如下yaml文件预留资源:reservation-demo。
1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: Reservation
3. metadata:
4. name: reservation-demo
5. spec:
6. template: # set resource requirements
7. metadata:
8. namespace: default
9. spec:
10. containers:
11. - args:
12. - '-c'
13. - '1'
14. command:
15. - stress
16. image: polinux/stress
17. imagePullPolicy: Always
18. name: stress
19. resources: # reserve 500m cpu and 800Mi memory
20. requests:
21. cpu: 500m
22. memory: 800Mi
23. schedulerName: koord-scheduler # use koord-scheduler
24. owners: # set the owner specifications
25. - object: # owner pods whose name is `default/pod-demo-0`
26. name: pod-demo-0
27. namespace: default
28. ttl: 1h # set the TTL, the reservation will get expired 1 hour later
1. $ kubectl create -f reservation-demo.yaml
2. reservation.scheduling.koordinator.sh/reservation-demo created
  1. 跟踪reservation-demo的状态,直到它变成可用状态。
1. $ kubectl get reservation reservation-demo -o wide
2. NAME               PHASE       AGE   NODE     TTL  EXPIRES
3. reservation-demo   Available   88s   node-0   1h
  1. 使用如下YAML文件部署一个Pod:Pod-demo-0。
1. apiVersion: v1
2. kind: Pod
3. metadata:
4. name: pod-demo-0 # match the owner spec of `reservation-demo`
5. spec:
6. containers:
7. - args:
8. - '-c'
9. - '1'
10. command:
11. - stress
12. image: polinux/stress
13. imagePullPolicy: Always
14. name: stress
15. resources:
16. limits:
17. cpu: '1'
18. memory: 1Gi
19. requests:
20. cpu: 200m
21. memory: 400Mi
22. restartPolicy: Always
23. schedulerName: koord-scheduler # use koord-scheduler
1. $ kubectl create -f pod-demo-0.yaml
2. pod/pod-demo-0 created
  1. 检查Pod-demo-0的调度状态。
1. $ kubectl get pod pod-demo-0 -o wide
2. NAME         READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
3. pod-demo-0   1/1     Running   0          32s   10.17.0.123   node-0   <none>           <none>

Pod-demo-0将会和reservation-demo被调度到同一个节点。

  1. 检查reservation-demo的状态。
1. $ kubectl get reservation reservation-demo -oyaml
2. apiVersion: scheduling.koordinator.sh/v1alpha1
3. kind: Reservation
4. metadata:
5. name: reservation-demo
6. creationTimestamp: "YYYY-MM-DDT05:24:58Z"
7. uid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
8. ...
9. spec:
10. owners:
11. - object:
12. name: pod-demo-0
13. namespace: default
14. template:
15. spec:
16. containers:
17. - args:
18. - -c
19. - "1"
20. command:
21. - stress
22. image: polinux/stress
23. imagePullPolicy: Always
24. name: stress
25. resources:
26. requests:
27. cpu: 500m
28. memory: 800Mi
29. schedulerName: koord-scheduler
30. ttl: 1h
31. status:
32. allocatable: # total reserved
33. cpu: 500m
34. memory: 800Mi
35. allocated: # current allocated
36. cpu: 200m
37. memory: 400Mi
38. conditions:
39. - lastProbeTime: "YYYY-MM-DDT05:24:58Z"
40. lastTransitionTime: "YYYY-MM-DDT05:24:58Z"
41. reason: Scheduled
42. status: "True"
43. type: Scheduled
44. - lastProbeTime: "YYYY-MM-DDT05:24:58Z"
45. lastTransitionTime: "YYYY-MM-DDT05:24:58Z"
46. reason: Available
47. status: "True"
48. type: Ready
49. currentOwners:
50. - name: pod-demo-0
51. namespace: default
52. uid: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
53. nodeName: node-0
54. phase: Available

现在我们可以看到reservation-demo预留了500m cpu和 800Mi内存, Pod-demo-0从预留的资源中分配了200m cpu and 400Mi内存。

  1. 清理reservation-demo的预留资源。
1. $ kubectl delete reservation reservation-demo
2. reservation.scheduling.koordinator.sh "reservation-demo" deleted
3. $ kubectl get pod pod-demo-0
4. NAME         READY   STATUS    RESTARTS   AGE
5. pod-demo-0   1/1     Running   0          110s

在预留资源被删除后,Pod-demo-0依然正常运行。

高级特性

最新的API可以在这里查看: reservation_types。

1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: Reservation
3. metadata:
4. name: reservation-demo
5. spec:
6. # pod template (required): Reserve resources and play pod/node affinities according to the template.
7. # The resource requirements of the pod indicates the resource requirements of the reservation
8. template:
9. metadata:
10. namespace: default
11. spec:
12. containers:
13. - args:
14. - '-c'
15. - '1'
16. command:
17. - stress
18. image: polinux/stress
19. imagePullPolicy: Always
20. name: stress
21. resources:
22. requests:
23. cpu: 500m
24. memory: 800Mi
25. # scheduler name (required): use koord-scheduler to schedule the reservation
26. schedulerName: koord-scheduler
27. # owner spec (required): Specify what kinds of pods can allocate resources of this reservation.
28. # Currently support three kinds of owner specifications:
29. # - object: specify the name, namespace, uid of the owner pods
30. # - controller: specify the owner reference of the owner pods, e.g. name, namespace(extended by koordinator), uid, kind
31. # - labelSelector: specify the matching labels are matching expressions of the owner pods
32. owners:
33. - object:
34. name: pod-demo-0
35. namespace: default
36. - labelSelector:
37. matchLabels:
38. app: app-demo
39. # TTL (optional): Time-To-Live duration of the reservation. The reservation will get expired after the TTL period.
40. # If not set, use `24h` as default.
41. ttl: 1h
42. # Expires (optional): Expired timestamp when the reservation is expected to expire.
43. # If both `expires` and `ttl` are set, `expires` is checked first.
44. expires: "YYYY-MM-DDTHH:MM:SSZ"

字段: allocateOnce

  • 类型: *bool
  • 默认值: true
  • 描述: 当设置为 true 时,预留资源仅对第一个成功分配的属主可用,之后不再对其他属主可分配。当设置为 false 时,只要有足够的资源,预留资源可以被多个属主分配。

字段: allocatePolicy

  • 类型: ReservationAllocatePolicy
  • 可选值: Aligned, Restricted
  • 描述: 指定预留资源的分配策略。
  • Aligned: Pod 优先从 Reservation 分配资源。如果 Reservation 的剩余资源不足,可以从节点分配,但需要严格遵循 Pod 的资源规范。这避免了 Pod 同时使用多个 Reservation 的问题。
  • Restricted: Pod 请求的资源与 Reservation 预留的资源重叠部分只能从 Reservation 分配。Pod 中声明但 Reservation 中未预留的资源可以从节点分配。Restricted 包含 Aligned 的语义。

字段: preAllocation

  • 类型: bool
  • 默认值: false
  • 描述: 当 preAllocation 设置为 true 时,预留资源可以绑定到节点上已调度的 Pod。预留将预占这些运行中 Pod 的资源。当被绑定的 Pod 退出后,预留自动从绑定状态转变为预留已释放资源的普通预留。

这对于资源迁移和优雅的 Pod 重新调度等场景非常有用,可以在 Pod 退出之前确保资源连续性。

字段: preAllocationPolicy

  • 类型: PreAllocationPolicy
  • 描述: 当 preAllocation 设置为 true 时,定义预分配的策略。该字段允许精细控制如何选择可预分配的 Pod 以及是否可以预分配多个 Pod。

PreAllocationPolicy 结构体包含以下字段:

  • mode (类型: PreAllocationMode, 默认值: Default):

  • Default: 使用 Reservation Spec 中的 Owner 匹配器来选择可预分配的 Pod。这是默认行为。

  • Cluster: 使用集群级别的标签/注解选择器来识别可预分配的 Pod。该模式适用于多租户集群,可预分配的 Pod 可能属于不同的属主,需要集中管理。
  • enableMultiple (类型: bool, 默认值: false):

  • 当为 false 时,只能为单个 Reservation 预分配一个 Pod。

  • 当为 true 时,可以预分配多个 Pod 来满足 Reservation 的资源需求。当没有单个 Pod 能提供所有所需资源时(由于资源碎片化),这非常有用。

Cluster 模式的标签和注解:

使用 Cluster 模式时,调度器使用以下标签和注解来识别可预分配的 Pod:

标签/注解 描述
--- ---
pod.koordinator.sh/is-pre-allocatable 用于标识可预分配 Pod 的标签。设置为 “true” 表示 Pod 可被预分配。
pod.koordinator.sh/pre-allocatable-priority 用于设置预分配优先级的注解。数值越高表示优先级越高。值应为数字字符串。

字段: unschedulable

  • 类型: bool
  • 默认值: false
  • 描述: 控制新 Pod 对预留资源的可调度性。默认情况下,预留是可调度的。当设置为 true 时,没有新的 Pod 可以分配此预留资源。

字段: taints

  • 类型: []corev1.Taint
  • 描述: 指定预留资源的污点。Pod 必须容忍这些污点才能分配预留资源。

案例:多个属主在同一个节点预留资源

  1. 检查每个节点的可分配资源。
1. $ kubectl get node -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEMORY:.status.allocatable.memory
2. NAME     CPU     MEMORY
3. node-0   7800m   28625036Ki
4. node-1   7800m   28629692Ki
5. ...
6. $ kubectl describe node node-1 | grep -A 8 "Allocated resources"
7. Allocated resources:
8. (Total limits may be over 100 percent, i.e., overcommitted.)
9. Resource                     Requests     Limits
10. --------                     --------     ------
11. cpu                          780m (10%)   7722m (99%)
12. memory                       1216Mi (4%)  14044Mi (50%)
13. ephemeral-storage            0 (0%)       0 (0%)
14. hugepages-1Gi                0 (0%)       0 (0%)
15. hugepages-2Mi                0 (0%)       0 (0%)

如上图,node-1节点还保留7.0 cpu and 26Gi memory未分配。

  1. 用如下YAML文件预留资源:reservation-demo-big。
1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: Reservation
3. metadata:
4. name: reservation-demo-big
5. spec:
6. allocateOnce: false # 允许资源预留被多个owners分配
7. template:
8. metadata:
9. namespace: default
10. spec:
11. containers:
12. - args:
13. - '-c'
14. - '1'
15. command:
16. - stress
17. image: polinux/stress
18. imagePullPolicy: Always
19. name: stress
20. resources: # reserve 6 cpu and 20Gi memory
21. requests:
22. cpu: 6
23. memory: 20Gi
24. nodeName: node-1 # set the expected node name to schedule at
25. schedulerName: koord-scheduler
26. owners: # set multiple owners
27. - object: # owner pods whose name is `default/pod-demo-0`
28. name: pod-demo-1
29. namespace: default
30. - labelSelector: # owner pods who have label `app=app-demo` can allocate the reserved resources
31. matchLabels:
32. app: app-demo
33. ttl: 1h
1. $ kubectl create -f reservation-demo-big.yaml
2. reservation.scheduling.koordinator.sh/reservation-demo-big created
  1. 跟踪reservation-demo-big的状态,直到他变成可用状态。
1. $ kubectl get reservation reservation-demo-big -o wide
2. NAME                   PHASE       AGE   NODE     TTL  EXPIRES
3. reservation-demo-big   Available   37s   node-1   1h

reservation-demo-big将被调度到Pod模板中设置的nodeName属性节点:node-1。

  1. 用如下YAML文件创建一次部署:app-demo。
1. apiVersion: apps/v1
2. kind: Deployment
3. metadata:
4. name: app-demo
5. spec:
6. replicas: 2
7. selector:
8. matchLabels:
9. app: app-demo
10. template:
11. metadata:
12. name: stress
13. labels:
14. app: app-demo # match the owner spec of `reservation-demo-big`
15. spec:
16. schedulerName: koord-scheduler # use koord-scheduler
17. containers:
18. - name: stress
19. image: polinux/stress
20. args:
21. - '-c'
22. - '1'
23. command:
24. - stress
25. resources:
26. requests:
27. cpu: 2
28. memory: 10Gi
29. limits:
30. cpu: 4
31. memory: 20Gi
1. $ kubectl create -f app-demo.yaml
2. deployment.apps/app-demo created
  1. 检查app-demo的Pod调度结果.
1. k get pod -l app=app-demo -o wide
2. NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
3. app-demo-798c66db46-ctnbr   1/1     Running   0          2m    10.17.0.124   node-1   <none>           <none>
4. app-demo-798c66db46-pzphc   1/1     Running   0          2m    10.17.0.125   node-1   <none>           <none>

app-demo的Pod将会被调度到reservation-demo-big所在的节点。

  1. 检查reservation-demo-big的状态。
1. $ kubectl get reservation reservation-demo-big -oyaml
2. apiVersion: scheduling.koordinator.sh/v1alpha1
3. kind: Reservation
4. metadata:
5. name: reservation-demo-big
6. creationTimestamp: "YYYY-MM-DDT06:28:16Z"
7. uid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
8. ...
9. spec:
10. allocateOnce: false # 允许资源预留被多个owners分配
11. owners:
12. - object:
13. name: pod-demo-0
14. namespace: default
15. template:
16. spec:
17. containers:
18. - args:
19. - -c
20. - "1"
21. command:
22. - stress
23. image: polinux/stress
24. imagePullPolicy: Always
25. name: stress
26. resources:
27. requests:
28. cpu: 500m
29. memory: 800Mi
30. schedulerName: koord-scheduler
31. ttl: 1h
32. status:
33. allocatable:
34. cpu: 6
35. memory: 20Gi
36. allocated:
37. cpu: 4
38. memory: 20Gi
39. conditions:
40. - lastProbeTime: "YYYY-MM-DDT06:28:17Z"
41. lastTransitionTime: "YYYY-MM-DDT06:28:17Z"
42. reason: Scheduled
43. status: "True"
44. type: Scheduled
45. - lastProbeTime: "YYYY-MM-DDT06:28:17Z"
46. lastTransitionTime: "YYYY-MM-DDT06:28:17Z"
47. reason: Available
48. status: "True"
49. type: Ready
50. currentOwners:
51. - name: app-demo-798c66db46-ctnbr
52. namespace: default
53. uid: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
54. - name: app-demo-798c66db46-pzphc
55. namespace: default
56. uid: zzzzzzzz-zzzz-zzzz-zzzzzzzzzzzz
57. nodeName: node-1
58. phase: Available

现在我们能看到reservation-demo-big预留了6 cpu和20Gi内存,app-demo从预留的资源中分配了4 cpu and 20Gi内存,预留资源的分配不会增加节点资源的请求容量,否则node-1的请求资源总容量将会超过可分配的资源容量。而且当有足够的未分配的预留资源时,这些预留资源可以被同时分配给多个属主。

案例:使用 Default 模式的 PreAllocation

本案例演示如何使用 Default 模式的 PreAllocation,Reservation 绑定到匹配属主规范的已调度 Pod。

  1. 部署一个启用 preAllocation 的 Reservation:
1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: Reservation
3. metadata:
4. name: reservation-prealloc
5. spec:
6. preAllocation: true
7. template:
8. metadata:
9. namespace: default
10. spec:
11. containers:
12. - name: placeholder
13. resources:
14. requests:
15. cpu: 500m
16. memory: 800Mi
17. schedulerName: koord-scheduler
18. owners:
19. - labelSelector:
20. matchLabels:
21. app: my-app
22. ttl: 2h
  1. 调度器会找到匹配属主规范的运行中 Pod 并将 Reservation 绑定到它。当 Pod 终止时,Reservation 将转换为预留已释放资源供后续 Pod 使用。

案例:使用 Cluster 模式的 PreAllocation

本案例演示如何使用 Cluster 模式的 PreAllocation,该模式使用集群级别的选择器来识别可预分配的 Pod。

  1. 首先,为可预分配的 Pod 添加标签:
1. apiVersion: v1
2. kind: Pod
3. metadata:
4. name: batch-job-1
5. labels:
6. pod.koordinator.sh/is-pre-allocatable: "true"
7. annotations:
8. pod.koordinator.sh/pre-allocatable-priority: "100"
9. spec:
10. containers:
11. - name: batch-job
12. image: busybox
13. resources:
14. requests:
15. cpu: 2
16. memory: 4Gi
  1. 创建一个 Cluster 模式的 Reservation:
1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: Reservation
3. metadata:
4. name: reservation-cluster-mode
5. spec:
6. preAllocation: true
7. preAllocationPolicy:
8. mode: Cluster
9. template:
10. metadata:
11. namespace: default
12. spec:
13. containers:
14. - name: placeholder
15. resources:
16. requests:
17. cpu: 2
18. memory: 4Gi
19. schedulerName: koord-scheduler
20. owners:
21. - labelSelector:
22. matchLabels:
23. app: critical-app
24. ttl: 4h
  1. 调度器会选择优先级最高(基于 pre-allocatable-priority 注解)的可预分配 Pod 并将 Reservation 绑定到它。

案例:使用多 Pod 的 PreAllocation

本案例演示如何使用 enableMultiple 的 PreAllocation,当没有单个 Pod 能满足 Reservation 需求时,从多个 Pod 累积资源。

  1. 将多个 Pod 标记为可预分配:
1. # Pod 1 - 1 CPU, 2Gi 内存
2. apiVersion: v1
3. kind: Pod
4. metadata:
5. name: batch-job-1
6. labels:
7. pod.koordinator.sh/is-pre-allocatable: "true"
8. annotations:
9. pod.koordinator.sh/pre-allocatable-priority: "100"
10. spec:
11. containers:
12. - name: batch-job
13. image: busybox
14. resources:
15. requests:
16. cpu: 1
17. memory: 2Gi
18. ---
19. # Pod 2 - 1 CPU, 2Gi 内存
20. apiVersion: v1
21. kind: Pod
22. metadata:
23. name: batch-job-2
24. labels:
25. pod.koordinator.sh/is-pre-allocatable: "true"
26. annotations:
27. pod.koordinator.sh/pre-allocatable-priority: "90"
28. spec:
29. containers:
30. - name: batch-job
31. image: busybox
32. resources:
33. requests:
34. cpu: 1
35. memory: 2Gi
  1. 创建一个需要 2 CPU 和 4Gi 内存并启用 enableMultiple 的 Reservation:
1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: Reservation
3. metadata:
4. name: reservation-multi-pods
5. spec:
6. preAllocation: true
7. preAllocationPolicy:
8. mode: Cluster
9. enableMultiple: true
10. template:
11. metadata:
12. namespace: default
13. spec:
14. containers:
15. - name: placeholder
16. resources:
17. requests:
18. cpu: 2
19. memory: 4Gi
20. schedulerName: koord-scheduler
21. owners:
22. - labelSelector:
23. matchLabels:
24. app: high-priority-app
25. ttl: 4h
  1. 调度器会预分配 batch-job-1 和 batch-job-2(按 pre-allocatable-priority 注解排序优先级)来满足 Reservation 的资源需求。当这些 Pod 终止时,Reservation 将转换为预留已释放资源。

PreAllocation 的调度器配置

调度器可以为 PreAllocation 行为配置额外的选项。在调度器配置中添加以下配置:

1. apiVersion: kubescheduler.config.k8s.io/v1
2. kind: KubeSchedulerConfiguration
3. profiles:
4. - schedulerName: koord-scheduler
5. plugins:
6. reservation:
7. enabled:
8. - name: Reservation
9. pluginConfig:
10. - name: Reservation
11. args:
12. apiVersion: kubescheduler.config.k8s.io/v1
13. kind: ReservationArgs
14. preAllocationConfig:
15. # 启用集群范围的预分配模式
16. enableClusterMode: true
17. # 自定义标识可预分配 Pod 的标签键(可选)
18. clusterLabelKey: pod.koordinator.sh/is-pre-allocatable
19. # 自定义 Pod 优先级的注解键(可选)
20. clusterPriorityAnnotationKey: pod.koordinator.sh/pre-allocatable-priority
21. # 尽可能在不使用可预分配 Pod 的情况下放置 Reservation
22. preferNoPreAllocatedPods: true

配置选项:

选项 描述
--- ---
enableClusterMode 启用集群范围的预分配模式
clusterLabelKey 自定义标识可预分配候选者的标签键
clusterPriorityAnnotationKey 自定义可预分配 Pod 优先级的注解键
preferNoPreAllocatedPods 启用后,如果节点有足够资源,优先不使用可预分配 Pod 放置 Reservation