Pod调度策略

调度概览

调度器通过 kubernetes 的 watch 机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。调度器会依据调度原则来做出调度选择。

本章节要记录的调度策略有以下几种:

  1. kube-scheduler
  2. nodeName
  3. nodeSelector
  4. 污点与容忍度
  5. Node亲和性调度
  6. Pod亲和性调度
  7. Pod互斥性调度

Pod工作流程

创建一个Pod的工作流程

  1. 管理员通过配置清单然后使用kubectl命令创建一个Pod资源对象,kubectl会将POST请求提交给API Server,在API Server收到POST请求前,k8s系统会将yaml配置清单转换为JSON格式提交给API Server。create pod阶段。
  2. API Server收到kubectl提交的POST请求后,将kubectl提交的内容写入到etcd中进行存储。write etcd阶段。
  3. Scheduler组件一直在不间断的watch API Server,Seheduler发现有新的资源对象需要创建,那么Scheduler将通过自身的算法把资源对象调度到相应的Node上,bind pod阶段。
  4. 然后API Server将Scheduler调度的事件写入到etcd中。write etcd阶段。
  5. Kubelet组件也在一直watch API Server状态,Kubelet从API Server得知新的资源是调度到本机来的,然后kubelet调用本机的docker进行创建容器。
  6. docker创建容器后,kubelet将状态汇报给API Server。
  7. API Server收到kubelet汇报的状态后将信息写入etcd

kube-scheduler调度流程

官方文档:https://kubernetes.io/zh/docs/concepts/scheduling-eviction/kube-scheduler/

kube-scheduler调度介绍

kube-scheduler 是 Kubernetes 集群的默认调度器,并且是集群控制面(master)的一部分。
对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的 Node 去运行这个 Pod。然而,Pod 内的每一个容器对资源都有不同的需求,而且 Pod 本身也有不同的资源需求。因此,Pod 在被调度到 Node 上之前,根据这些特定的资源调度需求,需要对集群中的 Node 进行一次过滤。

在一个集群中,满足一个 Pod 调度请求的所有 Node 称之为 可调度节点。如果没有任何一个 Node 能满足 Pod 的资源请求,那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的 Node。

调度器先在集群中找到一个 Pod 的所有可调度节点,然后根据一系列函数对这些可调度节点打分,然后选出其中得分最高的 Node 来运行 Pod。之后,调度器将这个调度决定通知给 kube-apiserver,这个过程叫做 绑定。

在做调度决定时需要考虑的因素包括:单独和整体的资源请求、硬件/软件/策略限制、亲和以及反亲和要求、数据局域性、负载间的干扰等等。

kube-scheduler 调度流程

kube-scheduler 给一个 pod 做调度选择包含两个步骤:

  1. 过滤(Predicates预选策略)
  2. 打分(Priorities优选策略)

过滤阶段:过滤阶段会将所有满足 Pod 调度需求的 Node 选出来。例如,PodFitsResources 过滤函数会检查候选 Node 的可用资源能否满足 Pod 的资源请求。在过滤之后,得出一个 Node 列表,里面包含了所有可调度节点;通常情况下,这个 Node 列表包含不止一个 Node。如果这个列表是空的,代表这个 Pod 不可调度。

打分阶段:在过滤阶段后调度器会为 Pod 从所有可调度节点中选取一个最合适的 Node。根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。最后,kube-scheduler 会将 Pod 调度到得分最高的 Node 上。如果存在多个得分最高的 Node,kube-scheduler 会从中随机选取一个。

偷一张图

官方文档:https://kubernetes.io/docs/reference/scheduling/policies/

过滤阶段需求:

  1. PodFitsHostPorts:检查Node上是否不存在当前被调度Pod的端口(如果被调度Pod用的端口已被占用,则此Node被Pass)。
  2. PodFitsHost:检查Pod是否通过主机名指定了特性的Node (是否在Pod中定义了nodeName)。
  3. PodFitsResources:检查Node是否有空闲资源(如CPU和内存)以满足Pod的需求。
  4. PodMatchNodeSelector:检查Pod是否通过节点选择器选择了特定的Node (是否在Pod中定义了nodeSelector)。
  5. NoVolumeZoneConflict:检查Pod请求的卷在Node上是否可用 (不可用的Node被Pass)。
  6. NoDiskConflict:根据Pod请求的卷和已挂载的卷,检查Pod是否合适于某个Node (例如Pod要挂载/data到容器中,Node上/data/已经被其它Pod挂载,那么此Pod则不适合此Node)
  7. MaxCSIVolumeCount::决定应该附加多少CSI卷,以及是否超过了配置的限制。
  8. CheckNodeMemoryPressure:对于内存有压力的Node,则不会被调度Pod。
  9. CheckNodePIDPressure:对于进程ID不足的Node,则不会调度Pod。
  10. CheckNodeDiskPressure:对于磁盘存储已满或者接近满的Node,则不会调度Pod。
  11. CheckNodeCondition:Node报告给API Server说自己文件系统不足,网络有写问题或者kubelet还没有准备好运行Pods等问题,则不会调度Pod。
  12. PodToleratesNodeTaints:检查Pod的容忍度是否能承受被打上污点的Node。
  13. CheckVolumeBinding:根据一个Pod并发流量来评估它是否合适(这适用于结合型和非结合型PVCs)。

打分阶段需求:

  1. SelectorSpreadPriority:优先减少节点上属于同一个 Service 或 Replication Controller 的 Pod 数量
  2. InterPodAffinityPriority:优先将 Pod 调度到相同的拓扑上(如同一个节点、Rack、Zone 等)
  3. LeastRequestedPriority:节点上放置的Pod越多,这些Pod使用的资源越多,这个Node给出的打分就越低,所以优先调度到Pod少及资源使用少的节点上。
  4. MostRequestedPriority:尽量调度到已经使用过的 Node 上,将把计划的Pods放到运行整个工作负载所需的最小节点数量上。
  5. RequestedToCapacityRatioPriority:使用默认资源评分函数形状创建基于requestedToCapacityResourceAllocationPriority
  6. BalancedResourceAllocation:优先平衡各节点的资源使用。
  7. NodePreferAvoidPodsPriority:根据节点注释对节点进行优先级排序,以使用它来提示两个不同的 Pod 不应在同一节点上运行。scheduler.alpha.kubernetes.io/preferAvoidPods
  8. NodeAffinityPriority:优先调度到匹配 NodeAffinity (Node亲和性调度)的节点上。
  9. TaintTolerationPriority:优先调度到匹配 TaintToleration (污点) 的节点上
  10. ImageLocalityPriority:尽量将使用大镜像的容器调度到已经下拉了该镜像的节点上。
  11. ServiceSpreadingPriority:尽量将同一个 service 的 Pod 分布到不同节点上,服务对单个节点故障更具弹性。
  12. EqualPriority:将所有节点的权重设置为 1。
  13. EvenPodsSpreadPriority:实现首选pod拓扑扩展约束。

kube-scheduler 调度示例

默认配置使用的就是kube-scheduler调度组件,我们下面例子启动三个Pod,看分别被分配到哪个Node。
1.创建资源配置清单

cat scheduler-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: scheduler-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: scheduler-pod
  template:
    metadata:
      labels:
        app: scheduler-pod
    spec:
      containers:
      - image: busybox:latest
        name: scheduler-pod
        command: [ "/bin/sh", "-c", "tail -f /etc/passwd" ]

2.使用kubectl创建资源对象

kubectl apply -f scheduler-pod.yaml

3.查看被kube-scheduler自动调度的Pod
两个Pod在Node03上,一个在Node02上

kubectl get pods -o wide | grep scheduler
scheduler-deploy-65d8f9c98-cqdm9    1/1     Running            0          111s    10.244.5.59   k8s-node03   <none>           <none>
scheduler-deploy-65d8f9c98-d4t9p    1/1     Running            0          111s    10.244.5.58   k8s-node03   <none>           <none>
scheduler-deploy-65d8f9c98-f8xxc    1/1     Running            0          111s    10.244.2.45   k8s-node02   <none>           <none>

4.我们查看一下Node资源的使用情况
Node01,可用内存2.7G

Node02,可用内存5.8G

Node03,可用内存5.6G

nodeName

nodeName会将Pod调度到指定的Node上

1.创建资源配置清单

cat nodeName-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nodename-pod
spec:
  nodeName: k8s-node02
  containers:
  - image: busybox:latest
    name: nodename-containers
    command: [ "/bin/sh", "-c", "tail -f /etc/passwd" ]

2.创建Pod资源对象
如下,nodename-pod被绑定在了k8s-node02上

kubectl get pods -o wide | grep name
nodename-pod                        1/1     Running            0          25s     10.244.2.46   k8s-node02   <none>           <none>

nodeSelector

nodeSelector用于将Pod调度到匹配Label的Node上,所以要先给node打上标签,然后在Pod配置清单中选择指定Node的标签。
先给规划node用途,然后打标签,例如将两台node划分给不同团队使用:

1.为Node添加标签
node02给开发团队用,node03给大数据团队用

#添加标签
kubectl label nodes k8s-node02 team=development
kubectl label nodes k8s-node03 team=bigdata

#查看标签
kubectl get nodes -o wide --show-labels

2.创建资源配置清单

cat nodeSelector-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nodeselector-pod
spec:
  nodeSelector:
    team: development
  containers:
  - image: busybox:latest
    name: nodeselector-containers
    command: [ "/bin/sh", "-c", "tail -f /etc/passwd" ]

3.创建Pod资源对象

kubectl apply -f nodeSelector-pod.yaml

4.查看pod被分配的Node

kubectl get pods -o wide | grep nodeselect
nodeselector-pod                    1/1     Running            0          49s     10.244.2.47   k8s-node02   <none>           <none>

4.删除标签

kubectl label nodes k8s-node02 team-
kubectl label nodes k8s-node03 team-

删除标签后pod还在正常运行

kubectl get pods -o wide | grep nodeselect
nodeselector-pod                    1/1     Running            0          11m     10.244.2.47   k8s-node02   <none>           <none>

把Pod删除然后再次创建Pod

kubectl delete pods/nodeselector-pod

kubectl apply -f nodeSelector-pod.yaml

#会发现该pod一直在等待中,找不到清单中配置标签的Node
kubectl get pods -o wide | grep nodeselect
nodeselector-pod                    0/1     Pending            0          55s     <none>        <none>       <none>           <none>

#事件:6个节点都不匹配 node selector
kubectl describe pods/nodeselector-pod | grep -A 10 Events
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/6 nodes are available: 6 node(s) didn't match node selector.
  Warning  FailedScheduling  <unknown>  default-scheduler  0/6 nodes are available: 6 node(s) didn't match node selector.

污点与容忍度

污点与容忍度(taint and tolerations)
官方文档:https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#example-use-cases

污点(taint)

污点是非常霸道的行为,我们可以给Node打上污点,打污点的程度有三个级别,分别如下

  • NoSchedule :为Node添加污点程度为NoSchedule,那么kube-scheduler将不在被调度Pod到本机。
  • PreferNoSchedule:为Node添加污点程度为PreferNoSchedule,那么kube-scheduler将尽量不调度Pod到本机。
  • NoExecute:为Node添加污点程度为NoExecute,那么kube-scheduler不仅不会调度Pod到本机,还会驱逐Node上已有的Pod。

污点应用场景:节点独占,例如具有特殊硬件设备的节点,如GPU计算型硬件需要给特定的应用去使用。

1.添加污点
为k8s-node02添加污点,污点程度为NoScheduletype=calculate为标签

kubectl taint node k8s-node02 type=calculate:NoSchedule

2.查看污点

 kubectl describe nodes k8s-node02 | grep Taints

这样的话我们创建Pod就不会被调度到我们打上污点的k8s-node02的节点上

3.创建Pod资源配置清单
我们创建3个Pod,看看其是否会将Pod调度到我们打污点Node上

cat taint-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: taint-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: taint-pod
  template:
    metadata:
      labels:
        app: taint-pod
    spec:
      containers:
      - image: busybox:latest
        name: taint-pod
        command: [ "/bin/sh", "-c", "tail -f /etc/passwd" ]

2.查看Pod被调度的Node
下面三个Pod都被调度到了Node03上,效果可能不是很明显,我们为Node02打了污点,还有Node01没有体会出来

kubectl apply -f taint-pod.yaml

kubectl get pods -o wide | grep taint
taint-deploy-748989f6d4-f7rbq       1/1     Running            0          41s     10.244.5.62   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-nzwjg       1/1     Running            0          41s     10.244.5.61   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-vzzdx       1/1     Running            0          41s     10.244.5.60   k8s-node03   <none>           <none>

4.扩容Pod
我们将Pod扩容至9台,让Pod分配到Node01节点,可以直观的展现污点

kubectl scale --replicas=9 deploy/taint-deploy -n default

kubectl get pods -o wide | grep taint
taint-deploy-748989f6d4-4ls9d       1/1     Running            0          54s     10.244.5.65   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-794lh       1/1     Running            0          68s     10.244.5.63   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-bwh5p       1/1     Running            0          54s     10.244.5.66   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-ctknr       1/1     Running            0          68s     10.244.5.64   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-f7rbq       1/1     Running            0          2m27s   10.244.5.62   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-hf9sf       1/1     Running            0          68s     10.244.3.51   k8s-node01   <none>           <none>
taint-deploy-748989f6d4-nzwjg       1/1     Running            0          2m27s   10.244.5.61   k8s-node03   <none>           <none>
taint-deploy-748989f6d4-prg2f       1/1     Running            0          54s     10.244.3.52   k8s-node01   <none>           <none>
taint-deploy-748989f6d4-vzzdx       1/1     Running            0          2m27s   10.244.5.60   k8s-node03   <none>           <none>

以上调度了两台Pod到Node02,目前Node03和Node01都可以分配到Pod,而被打了污点的Node02无法分配Pod

5.删除污点
删除污点之需要指定标签的 key 及污点程度

kubectl taint node k8s-node02 type:NoSchedule-

容忍度(tolerations)

上面为Node打了污点之后就没有Pod可以调度上去,那么我们必须要将专属的Pod给调度到打上污点的Node怎么办?那就给Pod添加上容忍度,被添加上容忍度的Pod则可以被调度到打了污点Node之上。

容忍度所用到的参数tolerations,tolerations参数下的还有以下几个二级参数:

  • operator:此值被称为运算符,值可以为[Equal|Exists],Equal表示污点的key是否等于value(默认参数),Exists只判断污点的key是否存在,使用该参数时,不需要定义value。
  • effect:指定匹配的污点程度,为空表示匹配所有程度的污点,值可以为 [NoSchedule|PreferNoSchedule|NoExecut]。
  • key:指定Node上污点的键key。
  • value:指定Node上污点的值value。

1.容忍度示例

#为node03打上污点
kubectl taint node k8s-node03 type=calculate:NoSchedule

#查看污点
kubectl describe nodes k8s-node03 | grep Taints
Taints:             type=calculate:NoSchedule

2.Pod资源配置清单

cat taint-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: taint-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: taint-pod
  template:
    metadata:
      labels:
        app: taint-pod
    spec:
      tolerations:
      - key: "type"
        operator: "Equal"
        value: "calculate"
        effect: "NoSchedule"
      containers:
      - image: busybox:latest
        name: taint-pod
        command: [ "/bin/sh", "-c", "tail -f /etc/passwd" ]

3.创建Pod资源对象

kubectl apply -f taint-pod.yaml

4.查看Pod分配的Node
以下两个Pod被调度到了Node03上,一个Pod被调度到了Node02上,我们做了容忍度是可以让Pod运行在被打了污点的Node上,但并不是所有打了容忍度的Pod都要运行在污点的Node上,所以 kube-scheduler 组件还会向其它Node进行调度Pod。

 kubectl get pods -o wide | grep taint
taint-deploy-9868f98d7-dkr4n        1/1     Running            0          17s     10.244.5.74   k8s-node03   <none>           <none>
taint-deploy-9868f98d7-f762b        1/1     Running            0          17s     10.244.5.75   k8s-node03   <none>           <none>
taint-deploy-9868f98d7-zg4hk        1/1     Running            0          3m22s   10.244.2.49   k8s-node02   <none>           <none>

5.为k8s-node01添加NoExecute驱赶程度的污点

#添加污点
kubectl taint node k8s-node01 type=data:NoExecute

#观察Node01上的Pod状态
kubectl get pods -o wide | grep k8s-node01

下图中Node01上的Pod正在被驱赶至其它Node上进行重启

kubectl explain pods.spec.affinity

Node亲和性调度

参数文档:kubectl explain pods.spec.affinity.nodeAffinity

亲和性调度机制极大的扩展了Pod的调度能力,主要增强功能如下:

  1. 更具表达力,即更精细的力度控制;
  2. 可以使用软限制、优先采用等限制方式,即调度器在无法满足优先需求的情况下,会使用其他次条件进行满足;
  3. 可以依据节点上正在运行的其他Pod的标签来进行限制,而非节点本身的标签,从而实现Pod之间的亲和或互斥关系。

目前有两种节点亲和力表达:

  1. requiredDuringSchedulingIgnoredDuringExecution:硬规则,必须满足指定的规则,调度器才可以调度Pod至Node上(类似nodeSelector,语法不同)。
  2. preferredDuringSchedulingIgnoredDuringExecution:软规则,优先调度至满足的Node的节点,但不强求,多个优先级规则还可以设置权重值。

IgnoredDuringExecution指:如果一个Pod所在的节点在Pod运行期间标签发生了变化,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该Pod能继续在该节点运行。

Pod亲和性调度

参数文档:kubectl explain pods.spec.affinity.podAffinity

Pod互斥性调度

参数文档:kubectl explain pods.spec.affinity.podAntiAffinity







「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
Kubernetes
JVM
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论