kubernetes调度策略
1、背景
在 Kubernetes 中,调度 (scheduling) 指的是确保 Pod 匹配到合适的节点,以便 kubelet 能够运行它们。调度的工作由调度器和控制器协调完成。
调度器通过 Kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行。调度器会依据下文的调度原则来做出调度选择。控制器则会将调度写入 Kubernetes 的 API Server 中。
一般而言是基于最大资源空闲率的均衡调度来实现,我们还可以基于自己的调度行为、调度标识影响预选和优选的调度结果,进而来完成高级调度行为。
2、操作前了解相关配置和要求
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 <string> nodeSelector <map[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 对象需求的已有镜像的体积大小之和。
3、操作步骤
高级调度设计机制:
节点选择器:nodeSelector
节点亲和器:nodeAnffinity
污点容忍:taints / tolerations
3.1 nodeSelector
"新增节点标签" $ 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>
3.2 nodeAnffinity
"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>
3.3 podAffinity
Pod 亲和性/反亲和性调度:一组 Pod 运行在一起,允许调度器将第一个 Pod 随机选择一个节点,第二个 Pod 根据第一个 Pod 实现调度。
可以根据标签分类:机架(rack)、一排(row)、机房(zone),来判断是否为同一位置。
"podAnffinity" $ kubectl explain pods.spec.affinity nodeAffinity <Object> podAffinity <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> # 硬亲和性,必须满足 labelSelector <Object> # 选定要亲和的Pod matchExpressions <[]Object> matchLabels <map[string]string> namespaceSelector <Object> # 选定要亲和的Namespace namespaces <[]string> # 选定要亲和的Namespace topologyKey <string> -required- # 位置拓扑键 podAntiAffinity <Object>
实例: podAffinity
"创建测试Pod" $ cat demo-pod-podAffinity.yaml apiVersion: v1 kind: Pod metadata: name: pod-frontend namespace: qingyun labels: app: myapp tier: frontend spec: containers: - name: myapp image: zhangyyhub/myapp:v1.0 imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Pod metadata: name: pod-afterend namespace: qingyun labels: app: db tier: afterend spec: containers: - name: myapp image: zhangyyhub/myapp:v1.0 imagePullPolicy: IfNotPresent affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性,必须满足 - labelSelector: matchExpressions: # 匹配表达式 - key: app # key operator: In # 操作符 values: # value - myapp topologyKey: kubernetes.io/hostname # 位置拓扑键 $ kubectl apply -f demo-pod-podAffinity.yaml pod/pod-frontend unchanged pod/pod-afterend created "验证测试Pod" $ kubectl get pods -n qingyun -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-afterend 1/1 Running 0 14s 10.244.235.141 worker1 <none> <none> pod-frontend 1/1 Running 0 3m26s 10.244.235.171 worker1 <none> <none>
实例: podAntiAffinity
"创建测试Pod" $ cat demo-pod-podAffinity2.yaml apiVersion: v1 kind: Pod metadata: name: pod-frontend namespace: qingyun labels: app: myapp tier: frontend spec: containers: - name: myapp image: zhangyyhub/myapp:v1.0 imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Pod metadata: name: pod-afterend namespace: qingyun labels: app: db tier: afterend spec: containers: - name: myapp image: zhangyyhub/myapp:v1.0 imagePullPolicy: IfNotPresent affinity: podAntiAffinity: # Pod反亲和 requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性,必须满足 - labelSelector: matchExpressions: - key: app operator: In values: - myapp topologyKey: kubernetes.io/hostname $ kubectl apply -f demo-pod-podAffinity2.yaml pod/pod-frontend created pod/pod-afterend created "验证测试Pod" $ kubectl get pods -n qingyun -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-afterend 1/1 Running 0 14s 10.244.235.143 worker1 <none> <none> pod-frontend 1/1 Running 0 14s 10.244.235.210 k8s-master <none> <none>
3.4 容忍调度
容忍调度/污点调度:
kubectl explain nodes.spec.taints effect <string> -required- # 定义对Pod排斥效果 NoSchedule # 仅影响调度过程,对现有Pod不影响 PreferNoSchedule # 最好不影响调度,如果必须调度也行 NoExecute # 即影响调度过程,也影响现有的Pod key <string> -required- timeAdded <string> value <string>
实例:
"打污点:生产环境专用" $ kubectl taint node worker1 node-type=production:NoSchedule
apiVersion: v1 kind: Deployment metadata: name: deploy-myapp namespace: qingyun spec: replicas: 2 selector: matchLabels: app: myapp release: canary template: metadata: labels: app: myapp release: canary spec: containers: - name: myapp image: zhangyyhub/myapp:v1.0 imagePullPolicy: IfNotPresent tolerations: - key: "node-type" operator: "Equal" # Exists(污点存在就行不在乎值) and Equal(等值比较) value: "production" effect: "NoExecute" # 即影响调度过程,也影响现有的Pod tolerationSeconds: 3600
4、注意事项
注意 kubernetes 调度策略添加,不要影响现有现有环境。
5、结果检查
分别使用节点选择器、节点亲和器和节点容忍进行测试。