kubernetes scheduler是负责Pod调度的进程(组件),它的作用是先对等待调度的Pod通过一些复杂的调度流程计算出其最佳的目标Node,再將Pod绑定到目标Node上。
随着kubernetes功能的不断增强和完善,Pod的调度也变得越来越复杂。Scheduler内部的实现机制也在不断优化,从最初的两阶段调度机制(Predicates&Priorities)发展到现在的调度框架(Scheduling Framework),以满足越来越复杂的调度场景。
Pod调度复杂的原因:kubernetes要努力满足不同类型应用的不同需求并努力让大家和平相处。
kubernetes集群里的Pod可以分为三类:无状态服务类,有状态集群类,批处理类。不同类型的Pod对资源占用的需求不同,对Node故障引发的中断/恢复及Node迁移方面的容忍度不同。
调度流程
kube-apiserver将待调度的Pod的信息发送给kube-scheduler,scheduler通过一系列的计算把Pod要绑定的最优Node(或者暂时不存在)信息返还返还给apiserver。
调度流程
- 过滤阶段(Filtering):遍历所有的Node,筛选出符合要求的候选Node。此阶段Scheduler会将不合适的Node全部过滤掉,只留下符合条件的候选Node。具体方式是通过一系列待定的Predicates对每个Node进行筛选,在筛选完成后通常会有多个候选Node供调度,从而进入打分阶段。如果筛选的结果集为空,则表示当前没有符合条件的Node,此时Pod会一直处于Pending状态。
- 打分阶段(Scoring):在过滤阶段的基础上,采用优选策略计算出每个候选Node的积分,积分最高者胜出。在挑选出最佳Node后,Scheduler会把目标Pod安置到该Node上,完成调度。
注:当Node处于以下状态时,Scheduler不在给它调度新的Pod。
- NotReady
- Unschedulable
- MemoryPressure(不再调度新的BestEffort PodPod到这个Node)
- DiskPressure
调度策略
指定Node名称的定向调度策略
Kubernetes支持在Pod的配置中通过指定nodeName字段的方式指定要调度的目标Node名称。这个字段的优先级高于nodeSelector和亲和性策略。设置了nodeName字段的Pod将不再参与调度器的调度过程,相当于已经完成了调度,apiserver将直接通知目标Node上的kubelet开始创建这个Pod。
使用nodeName具有以下限制:
- 如果指定的node不存在或者失联,则Pod将无法运行;
- 如果指定的node资源不足,则pod可能会运行失败;
- 在某些云环境下,node名称不一定是稳定的。
验证一下污点策略的优先级
示例:--- apiVersion: v1 kind: pod metadata: name: nginx spec: containers: - name: nginx image: nginx nodeName: 192.168.88.61
基于Node Label的调度策略
Node也是kubernetes管理的一种资源对象,可以为其设置多个Label。在Pod的配置中,可以方便的使用Label Selector来声明需要调度到具有指定Label的Node上,调度器会在具有这些Label的Node中进行选择。
通过基于Node Label的调度方式,可以把集群中具有不同特点的Node都贴上不同的Label(如“role=frontend”,“role=backend”,“role=database”等),在部署应用时可以根据应用的需求设置NodeSelector来进行指定Node范围的调度。
需要注意的是,如果指定了nodeselector条件,但是在集群中不存在包含相应Label的Node,那么集群中即便存在可供使用的Node,这个Pod也无法被成功调度。
示例:
---
apiVersion: v1
kind: pod
metadata:
name: pod-node-selector
spec:
nodeSelector:
disk: ssd
containers:
- name: busybox
image: busybox
command: ['sh','-c','sleep 3600']
亲和性调度策略
亲和性和反亲和性的机制,扩展了Pod的调度能力,实现了更加灵活和精细的策略。其具有如下优点:
- 更强的逻辑选择能力(不仅仅是“符合全部”的简单情况)
- 可以设置为软性限制或优选,使得调度器在无法找到全部满足要求的Node时,也会寻找其他Node来调度Pod。
- 除了基于Node上的Label,还可以依据Node上正在运行的其他Pod的Label来进行亲和性和反亲和性设置。这样可以定义一组规则来描述Pod间的亲和或者互斥关系。
亲和性调度功能包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两种类型。
节点亲和性调度策略
节点亲和性调度策略与NodeSelector类似,但表达能力更强,并且允许设置软性匹配规则。设置方法也是通过Node上的Label来设置Pod是否可以调度到目标Node上。
节点亲和性的配置类型有以下两种:
- requiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上,是硬性要求,它的功能与nodeSelector相似,但是使用的是更加灵活的语法。
- preferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,是软性限制。可以设置多个规则,还可以给每个规则设置权重值(weight),以定义判断的优先级顺序。
权重
可以为每个规则设置一个1-100的权重值(weight),用于给调度器提供一个类似于优先级的分数,调度器会为满足条件的Node加上weight设置的值,以选出最终得分最高的Node。
这两种策略中的IgnoredDuringExecution的意思是:如果一个Pod所在的节点在Pod运行期间其标签发生了变化,不再符合该Pod的亲和性需求,则系统将忽略Node上的Label变化,让Pod在原Node上继续运行。也就是说Pod正常运行后,不再对Node进行亲和性验证。
在Label的匹配规则中,使用逻辑操作符(operator)进行设置,可用使用的操作符及其含义如下:
- In:Label的值(value)在给定的集合中。
- NotIn: Label的值(value)不在给定的集合中。
- Exists:需要具有此Label Key,此时不需要设置key的值。
- DoseNotExist:需要不存在此Label的值,此时不需要设置key的值。
- Gt;Label的值(Value)的整数值需要大于给定的值。
- Lt:Label的值(Value)的整数值需要小于给定的值。
#要求运行在具有标签“kubernetes.io/arch=amd64”的节点上。
#要求尽量运行在具有标签“disk-type=ssd”的节点上,如果没有,也可以运行在其他节点上。
---
apiVersion: v1
kind: Pod
metadata:
name: pod-with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
{/tabs-pane}
{tabs-pane label="示例二"}
对于具有zone=north标签的节点,系统会在调度算法的得分上加1(weigh=1),对于具有dis-type=ssd标签的节点,系统会在调度算法得分上加50(weight=50),然后综合其他条件计算出最终得分,并将pod调度到得分最高的Node上。
apiVersion: v1
kind: Pod
metadata:
name: pod-with-node-affinity-weight
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: zone
operator: In
values:
- north
- weight: 50
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
{/tabs-pane}
Pod亲和性调度策略
Pod亲和性:存在某些相互依赖,需要频繁互相调用的Pod,它们需要被尽可能地部署在同一个节点、机架、机房、网段或者区域(Zone)内。
Pod反亲和性:处于避免竞争或者容错的需求,也可能使某些Pod尽可能地远离另外一些Pod。
简单来说,某几个Pod可以在同一个拓扑域中共存,就是“Pod Affinity”,不能在同一个拓扑域中共存,就是“Pod Anti Affinity”。
Pod亲和性与反亲和性调度的具体做法,就是通过在Pod的定义中增加topologyKey属性,来声明对应的目标拓扑域内几种相关联的Pod是“在一起还是不在一起”。
需要注意的是,对于Pod的反亲和性策略,要求目标Node上需要存在相同的Label,实际上就是要求每个节点都具有相同的topologyKey指定的Label。如果某些Node不具有topologyKey指定的Label,则可能造成不可预估的调度结果。
另外,设置Pod间的亲和性和反亲和性规则,会给调度器引入更多的计算量,在大规模集群中可能会造成性能问题,因此在配置Pod间的亲和性策略时需要谨慎。
关于topoloygKey:
拓扑域:一个拓扑域由一些Node组成。这些Node通常具有相同的地理空间坐标。比如,在同一个机架、机房或地区。一般用Region表示机架、机房等的拓扑区域,用Zone表示地区这样跨度更大的拓扑区域。在某些情况下,可以认为一个Node就是一个拓扑区域。
kubernetes为Node内置了一些常用的用于表示拓扑域概念的Label。
- kubernetes.io/hostname
- topoloyg.kubernetes.io/region
- topoloyg.kubernetes.io/zone
以上拓扑域是由kubernetes自己维护的。在node初始化时,Controller Manager会为Node设置许多标签。如,kubernetes.io/hostname这个标签的值会被设置为Node的hostname,云厂商会设置topoloyg.kubernetes.io/region和topoloyg.kubernetes.io/zone的值,以确定各个Node所属的拓扑域。
#带有两个Label
---
apiVersion: v1
kind: Pod
metadata:
name: pod-flag
labels:
security: "S1"
app: "nginx"
spec:
containers:
- name: nginx
image: nginx
{/tabs-pane}
{tabs-pane label="亲和性调度"}
#亲和性Label是“security=S1”
---
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
containers:
- name: pod-affinity
image: nginx
{/tabs-pane}
{tabs-pane label="反亲和性调度1"}
#新Pod与具有security=S1的Label的Pod在同一个zone的Node上运行
# 新Pod不能与具有app=nginx的Label的Pod在同一个Node上运行
---
apiVersion: v1
kind: Pod
metadata:
name: pod-anti-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- name: pod-anti-affinity
image: nginx
{/tabs-pane}
{tabs-pane label="反亲和性调度2"}
#在同一个Node上不运行多个Pod副本。
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-anti-affinity
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx
image: nginx
{/tabs-pane}
污点和容忍度调度策略
污点(Taint)配置在Node上,可以让Node拒绝一些具有某些特征的Pod在其上运行。容忍度(Toleration)配置在Pod中,用于高速系统允许调度到具有指定污点的节点上运行。
默认情况下,在Node上设置一个或多个Taint后,除非Pod声明中明确能够容忍这些污点,否则无法在这些Node上运行。
污点和容忍度的配合使用,可以避免将Pod调度到不合适的Node上。例如,某个Node存在问题(磁盘空间不足,计算资源不足,存在安全隐患等),希望新的Pod不要调度过来,就可以通过设置某些污点来实现。但设置了污点的Node并非不可用,仍是有效的Node。所以对于可用在这些Node上运行的Pod,可以给Pod设置与污点匹配的容忍度来实现调度。
污点策略:
- PreferNoSchedule 尽量不调度,一个Pod如果没有声明容忍这个Taint,则系统会尽量避免把这个Pod调度到这一个Node上,但不保证一定能够避免
- NoScheduler 不被调度,除非Pod设置了与Taint匹配的容忍度。
NoExecute 驱逐节点,不能在改node上运行,包括以下几种情况。
对于正在该Node上运行的Pod,如果不能容忍指定的污点,则会被立刻从该Node驱逐。
对于正在该Node上运行的Pod,如果能够容忍指定的污点,但未设置运行时限(tolerationSeconds),则会在该Pod上持续运行。
对于正在该Node上运行的Pod,如果能够容忍指定的污点,但设置了运行时限(tolerationSeconds),则会在到达运行时限后从该Node上被驱逐。
添加污点标签
kubectl taint node node-0001 key=v1:PreferNoSchedule
kubectl taint node node-0002 key=v2:NoScheduler
容忍策略
……
spec:
tolerations:
- operator: "Equal" # 完全匹配键值对
key: "k" # 键
value: "v2" # 值
effect: "NoSchedule" # 污点标签
……
{/tabs-pane}
{tabs-pane label="模糊匹配"}
……
spec:
tolerations:
- operator: "Exists" # 部分匹配,存在即可
key: "k" # 键
effect: "NoSchedule" # 污点标签
……
{/tabs-pane}
{tabs-pane label="所有污点标签"}
……
spec:
tolerations:
- operator: "Exists" # 模糊匹配
key: "k" # 键
effect: # 没有设置污点标签代表所有
……
{/tabs-pane}
优先级和抢占调度策略
技术背景:对于运行各种负载(如Service,Job)的中等规模或者大规模的集群来说,需要尽可能提高集群的资源利用率。常规做法是采用优先级方案,即不同类型的负载对应不同的优先级,同时允许集群中的所有负载 所需要的资源总量超过集群可提供的资源。在这种情况下,当资源不足时,系统可以选择释放一些低优先级的负载,保障高优先级的负载获得足够的资源稳定运行。
调度方式:
由集群管理员创建priorityClass,Priority是不受限于命名空间的资源类型
---
kind: PriorityClass
apiVersion: scheduling.k8s.io/v1
metadata:
name: high-non
globalDefault: false
preemptionPolicy: Never
value: 1000
description: non-preemptive
{/tabs-pane}
{tabs-pane label="低优先级不抢占"}
---
kind: PriorityClass
apiVersion: scheduling.k8s.io/v1
metadata:
name: low-non
globalDefault: false
preemptionPolicy: Never
value: 500
description: non-preemptive
{/tabs-pane}
{tabs-pane label="高优先级抢占"}
---
kind: PriorityClass
apiVersion: scheduling.k8s.io/v1
metadata:
name: high
globalDefault: false
preemptionPolicy: PreemptLowerPriority
value: 1000
description: non-preemptive
{/tabs-pane}
{/tabs-pane}
{tabs-pane label="低优先级抢占"}
---
kind: PriorityClass
apiVersion: scheduling.k8s.io/v1
metadata:
name: low
globalDefault: false
preemptionPolicy: PreemptLowerPriority
value: 500
description: non-preemptive
{/tabs-pane}
{/tabs-pane}
{tabs-pane label="使用方式"}
---
kind: Pod
apiVersion: v1
metadata:
name: php3
spec:
nodeSelector:
kubernetes.io/hostname: node-0002
priorityClassName: high-non # 优先级名称
containers:
……
{/tabs-pane}
其中,优先级的值越高,优先级越高,允许的设置范围是(-2147483648到1000000000),最大值是10亿,超过10亿的数字被系统保留,用于设置给系统组件。
在上面的配置中,globalDefault设置为true表示这个优先级配置是全局默认配置,即对没有配置priorityCKassName的Pod都默认使用这个优先级设置。
在系统没有设置全局默认优先级的情况下,对于没有配置priorityClassName的Pod,系统默认设置这些Pod的优先级为0。
description字段用于设置一个说明,通常用来描述该priorityClass的用途。
preemptionPolicy字段用于设置抢占策略。可用设置的策略有下面两个:
- PreemptLowerPriority:默认策略,表示允许具有该priorityClass优先级的Pod抢占较低优先级Pod的资源。
- Never:具有该优先级的Pod会被置于调度优先级队列中优先级数值更低的Pod之前,但不抢占其他Pod的资源,这些Pod将一直在调度列队中等待,知道有足够的可用资源后,才会被调度。
抢占式调度的主要流程
- 考察每个候选Node,找出为了满足新Pod的调度要求,需要释放的最少Pod实例;
- 以抢占导致的代价最小原则,对筛查出来的所有候选Node进行考察,挑选出最优的候选Node作为最终被占用的目标Node;
- 把目标Node与新Pod绑定,使得新Pod在下一轮的调度过程中可以使用这个目标Node来完成最终的调度流程,同时,调用APIServer删除目标Node上需要被释放的Pod以释放Node资源。
最优Node选择规则
- 拥有最小违反PodDisruptionBudget约束的Node;
- 把Node上被释放的拥有最高优先级的Pod都找出来并进行比较,优先级排名垫底的Pod所在的Node;
- 被释放的Pod的优先级之和(目标Pod需要的资源可能需要释放多个Pod才能满足)排名垫底的Node;
- 被释放的Pod数量最少的Node;
- 把Node上被释放的拥有最高优先级的Pod找出来,最晚启动的那个Pod所在的Node
- 如果上述步骤还是无法选出最佳Node(可供选择的Node还有多个),就随机选择一个。
注意事项
如果目标Pod与候选Node上的某个优先级比它低的Pod之间有亲和性,则发生资源抢占后(这个低优先级的Pod被删除),目标pod不满足节点亲和性规则,此时该Node不符合抢占要求,Scheduler会考虑其他候选Node,无法保证目标Pod调度成功。要避免此类情况,要注意Pod亲和性和优先级的关系——具有亲和性关系的Pod不应该被设置为低优先级的。
评论 (0)