在 Kubernetes 中,调度 (scheduling) 指的是确保 Pod 匹配到合适的节点,以便 kubelet 能够运行它们。调度的工作由调度器和控制器协调完成。
调度器通过 Kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行。调度器会依据下文的调度原则来做出调度选择。控制器则会将调度写入 Kubernetes 的 API Server 中。
一般而言是基于最大资源空闲率的均衡调度来实现,我们还可以基于自己的调度行为、调度标识影响预选和优选的调度结果,进而来完成高级调度行为。
Pod 调度流程:
当用户向 APIServer 请求创建 Pod 时,APIServer 检查相关权限没问题后,将请求交给 Scheduler,由 Scheduler 在众多节点当中,选择一个匹配的节点做为运行此 Pod 的工作节点。Scheduler 的选择结果并不直接反映到节点之上,而是会告诉 APIServer ,并将相关信息保存到 etcd 当中。APIServer 指挥着被选定节点的 kubelet,或者说 kubelet 始终 Watch 着 APIServer 当中与当前节点相关联的事件变动,进而根据配置模版运行 Pod。
Pod 调度步骤:
Predicate(预选)
先排除完全不符合 Pod 运行法则的节点(最低资源需求、最大资源限额 limit)
Priority(优选)
基于一系列的算法函数,计算节点的优先级,找出最佳匹配节点
Select(选定)
将 Pod 绑定在优选后的节点之上
Pod 高级调度:
一般而言是基于最大资源空闲率的均衡调度来实现,我们还可以基于自己的调度行为、调度标识影响预选和优选的调度结果,进而来完成高级调度行为。
偶尔有特殊偏好的 Pod 要运行在特定节点(SSD 节点、GPU 节点)之上,可以对节点使用标签进行分类
$ kubectl explain pods.spec
......
nodeName
nodeSelector # 预选
......[string]string>
节点亲和性/反亲和性调度
Pod 亲和性/反亲和性调度(某一组 Pod 运行在同一节点或相邻节点)
污点、污点容忍调度(Taints-nodes,Tolerations-pod)
常见的预选策略:
CheckNodeCondition
检查节点是否可以在磁盘、网络不可用或未准备好的前提下把 Pod 调度到此节点
GeneralPredicates(通用预选策略)
HostName:检查 Pod 对象是否定义 pod.spec.hostname
PodFitsHostPort:检查 Pod 对象是否定义 pod.spec.containers.ports.hostPort
MatchNodeSelector:检查节点标签是否适配 pods.spec.nodeSelector
PodFitsResources:检查 Pod 的资源需求是否能被节点所满足
NoDiskConflict(默认没有启用)
检查 Pod 依赖的存储卷能否能满足需求
PodToleratesNodeTaints
检查 Pod 上的 spec.tolerations 可容忍的污点是否完全包含节点上的污点
PodToleratesNodeNoExecuteTaints(默认没有启用)
默认节点污点变动后,容忍之前调度在该节点之上的 Pod 存在。而该项则不容忍 Pod,会驱离 Pod
CheckNodeLabelPresence(默认没有启用)
检查节点标签存在性
CheckserviceAffinity(默认没有启用)
检查服务亲和性,将相同 Service 的 Pod 对象尽可能放在一块
MaxEBSVolumeCount
亚马逊弹性存储卷最大数量,默认 39
MaxGCEPDVolumeCount
谷歌容器引擎最大存储卷数量,默认 16
MaxAzureDiskVolumeCount
Azure 最大磁盘数量,默认 16
CheckVolumeBinding
检查节点上已绑定和未绑定的 PVC
NoVolumeZoneConflict
没有数据卷空间冲突(逻辑限制)
CheckNodeMemoryPressure
检查节点是否存在内存压力
CheckNodePIDPressure
检查节点是否存在进程压力
CheckNodeDiskPressure
检查节点是否存在磁盘压力
MatchInterPodAffinity
检查节点是否能满足 Pod 亲和性或反亲和性条件
常见的优选函数:
LeastRequested
# 节点空闲资源/节点总容量: 根据空闲比例评估
(cpu(capacity-sum(requested))*10/capacity)+(memory(capacity-sum(requested))*10/capacity)/2
capacity-sum(requested) # (总容量-已经被pod拿走的总容量)*10/总容量。*10是因为每一个优选函数的得分是10分,最后计算得分。
BalancedResourceAllocation
CPU 和内存资源的被占用率相近的胜出,目的是平衡节点资源的使用率。
NodePreferAvoidPods
节点亲向不要运行这个 Pod(优先级高),根据节点注解信息 "scheduler.alpha.kubernetes.io/preferAvoidPods" 判定,没有注解信息得分为 10,权重为 10000,存在此注解信息时,由控制器管控的 Pod 得分为 0。
TaintToleration
将 Pod 对象的 spec.tolerations 与节点的 taints 列表项进行匹配度检查,匹配的条目越多得分越低。
SelectorSpreading: 调度器将 pod 分散调度。
InterPodAffinity: 根据 Pod 间的亲和性。
NodeAffinity: 根据节点亲和性。
MostRequested: 根据最多被请求的节点。
NodeLabel: 根据节点标签。
ImageLocality: 根据满足当前 Pod 对象需求的已有镜像的体积大小之和。
高级调度设计机制:
节点选择器:nodeSelector
节点亲和器:nodeAnffinity
污点容忍:taints / tolerations
"新增节点标签"
$ kubectl label nodes worker2 disk=ssd
node/worker2 labeled
"创建测试Pod"
$ cat demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: qingyun
spec:
containers:
- name: myapp
image: zhangyyhub/myapp:v1.0
imagePullPolicy: IfNotPresent
nodeSelector: # 节点选择
disk: ssd
$ kubectl apply -f demo-pod.yaml
pod/demo-pod created
"验证测试Pod: 在节点标签所在节点之上"
$ kubectl get pods -n qingyun -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-pod 1/1 Running 0 14s 10.244.189.98 worker2 <none> <none>
"nodeAnffinity"
$ kubectl explain pods.spec.affinity
nodeAffinity <Object>
preferredDuringSchedulingIgnoredDuringExecution <[]Object> # 软亲和性,尽量满足
preference <Object> -required-
matchExpressions <[]Object> # 匹配表达式
matchFields <[]Object> # 匹配字段
key <string> -required- # key
operator <string> -required- # 操作符(In, NotIn, Exists, DoesNotExist, Gt, and Lt.)
values <[]string> # value
weight <integer> -required-
requiredDuringSchedulingIgnoredDuringExecution <Object> # 硬亲和性,必须满足
nodeSelectorTerms <[]Object> -required-
matchExpressions <[]Object> # 匹配表达式
matchFields <[]Object> # 匹配字段
key <string> -required- # key
operator <string> -required- # 操作符(In, NotIn, Exists, DoesNotExist, Gt, and Lt.)
values <[]string> # value
podAffinity <Object>
podAntiAffinity <Object>
实例:
"创建测试Pod"
cat demo-pod-nodeAffinity.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod-affinity
namespace: qingyun
spec:
containers:
- name: myapp
image: zhangyyhub/myapp:v1.0
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性,必须满足
nodeSelectorTerms:
- matchExpressions: # 匹配表达式
- key: disk # key
operator: In # 操作符
values: # value
- ssd
- essd
$ kubectl apply -f demo-pod-nodeAffinity.yaml
pod/demo-pod-affinity created
"验证测试Pod"
$ kubectl get pods -n qingyun -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-pod 1/1 Running 0 41m 10.244.189.98 worker2 <none> <none>
demo-pod-affinity 1/1 Running 0 19s 10.244.189.123 worker2 <none> <none>
"部署测试Pod"
$ cat demo-pod-nodeAffinity2.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod-affinity2
namespace: qingyun
spec:
containers:
- name: myapp
image: zhangyyhub/myapp:v1.0
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软亲和性,尽量满足
- preference:
matchExpressions: # 匹配表达式
- key: disk # key
operator: In # 操作符
values: # value
- hssd
- essd
weight: 60
$ kubectl apply -f demo-pod-nodeAffinity2.yaml
pod/demo-pod-affinity2 created
"验证测试Pod"
$ kubectl get nodes --show-labels | grep hssd
$ kubectl get nodes --show-labels | grep essd
$ kubectl get pods -n qingyun -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-pod 1/1 Running 0 52m 10.244.189.98 worker2 <none> <none>
demo-pod-affinity 1/1 Running 0 10m 10.244.189.123 worker2 <none> <none>
demo-pod-affinity2 1/1 Running 0 12s 10.244.235.255 k8s-master <none> <none>