Kubernetes
的默认调度器以预选、优选、选定机制完成将每个新的Pod
资源绑定至为其选出的目标节点上,不过,它只是Pod
对象的默认调度器,默认情况下调度器考虑的是资源足够,并且负载尽量平均。
在使用中,用户还可以自定义调度器插件,并在定义Pod
资源配置清单时通过spec.schedulerName
指定即可使用,这就是亲和性调度。
一、Node亲和性调度
NodeAffinity
意为Node
节点亲和性的调度策略,是用于替换NodeSelector
的全新调度策略。
这些规则基于节点上的自定义标签和Pod
对象上指定的标签选择器进行定义 。 节点亲和性允许Pod
对象定义针对一组可以调度于其上的节点的亲和性或反亲和性,不过,它无法具体到某个特定的节点 。
例如,将Pod
调度至有着特殊CPU
的节点或一个可用区域内的节点之上 。
定义节点亲和性规则时有两种类型的节点亲和性规则 :硬亲和性required
和软亲和性preferred
。 硬亲和性实现的是强制性规则,它是Pod
调度时必须要满足的规则,而在不存在满足规则的节点时 , Pod
对象会被置为Pending
状态。 而软亲和性规则实现的是一种柔性调度限制,它倾向于将Pod
对象运行于某类特定的节点之上,而调度器也将尽量满足此需求,但在无法满足调度需求时它将退而求其次地选择一个不匹配规则的节点。
定义节点亲和规则的关键点有两个,一是为节点配置合乎需求的标签,另一个是为Pod
对象定义合理的标签选择器,从而能够基于标签选择出符合期望的目标节点。不过,如preferredDuringSchedulinglgnoredDuringExecution
和requiredDuringSchedulinglgnoredDuringExecution
名字中的后半段符串lgnoredDuringExecution
隐含的意义所指,在Pod
资源基于节点亲和性规则调度至某节点之后,节点标签发生了改变而不再符合此节点亲和性规则时 ,调度器不会将Pod
对象从此节点上移出,因为,它仅对新建的Pod
对象生效。 节点亲和性模型如图所示:
1.1 Node硬亲和性
为Pod
对象使用nodeSelector
属性可以基于节点标签匹配的方式将Pod
对象强制调度至某一类特定的节点之上 ,不过它仅能基于简单的等值关系定义标签选择器,而nodeAffinity
中支持使用 matchExpressions
属性构建更为复杂的标签选择机制。例如,下面的配置清单示例中定义的Pod
对象,其使用节点硬亲和规则定义可将当前Pod
对象调度至拥有zone
标签且其值为foo
的节点之上!
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
将上面配置清单中定义的资源创建于集群之中,由其状态信息可知它处于Pending
阶段,这是由于强制型的节点亲和限制场景中不存在能够满足匹配条件的节点所致:
$ kubectl apply -f required-nodeAffinity-pod.yaml
pod/with-required-nodeaffinity created
$ kubectl get pods with-required-nodeaffinity
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity 0/1 Pending 0 8s
通过describe
查看对应的events
$ kubectl describe pods with-required-nodeaffinity
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
规划为各节点设置节点标签 ,这也是设置节点亲和性的前提之一
$ kubectl label node k8s-node-01 zone=foo
node/k8s-node-01 labeled
$ kubectl label node k8s-node-02 zone=foo
node/k8s-node-02 labeled
$ kubectl label node k8s-node-03 zone=bar
node/k8s-node-03 labeled
查看调度结果
$ kubectl describe pods with-required-nodeaffinity
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Normal Scheduled <unknown> default-scheduler Successfully assigned default/with-required-nodeaffinity to k8s-node-01
在定义节点亲和性时,requiredDuringSchedulinglgnoredDuringExecution
字段的值是一个对象列表,用于定义节点硬亲和性,它可由一到多个nodeSelectorTerm
定义的对象组成, 彼此间为“逻辑或”的关系,进行匹配度检查时,在多个nodeSelectorTerm
之间只要满足其中之一 即可。nodeSelectorTerm
用于定义节点选择器条目,其值为对象列表,它可由一个或多个matchExpressions
对象定义的匹配规则组成,多个规则彼此之间为“逻辑与”的关系, 这就意味着某节点的标签需要完全匹配同一个nodeSelectorTerm
下所有的matchExpression
对象定义的规则才算成功通过节点选择器条目的检查。而matchExmpressions
又可由 一到多 个标签选择器组成,多个标签选择器彼此间为“逻辑与”的关系 。
下面的资源配置清单示例中定义了调度拥有两个标签选择器的节点挑选条目,两个标签选择器彼此之间为“逻辑与”的关系,因此,满足其条件的节点为node01
和node03
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-2
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo", "bar"]}
- {key: ssd, operator: Exists, values: []}
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
构建标签选择器表达式中支持使用操作符有In
、Notln
、Exists
、DoesNotExist
、Lt
和Gt
等
- In:
label
的值在某个列表中 - NotIn:
label
的值不在某个列表中 - Gt:
label
的值大于某个值 - Lt:
label
的值小于某个值 - Exists:某个
label
存在 - DoesNotExist:某个
label
不存在
另外,调度器在调度Pod
资源时,节点亲和性MatchNodeSelector
仅是其节点预选策 略中遵循的预选机制之一,其他配置使用的预选策略依然正常参与节点预选过程。 例如将上面资源配置清单示例中定义的Pod
对象容器修改为如下内容并进行测试
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-3
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo", "bar"]}
- {key: ssd, operator: Exists, values: []}
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
resources:
requests:
cpu: 6
memory: 20Gi
在预选策略PodFitsResources
根据节点资源可用性进行节点预选的过程中,它会获取给定节点的可分配资源量(资源问题减去已被运行于其上的各Pod
对象的requests
属性之和),去除那些无法容纳新Pod
对象请求的资源量的节点,如果资源不够,同样会调度失败。
由上述操作过程可知,节点硬亲和性实现的功能与节点选择器nodeSelector
相似, 但亲和性支持使用匹配表达式来挑选节点,这一点提供了灵活且强大的选择机制,因此可被理解为新一代的节点选择器。
1.2 Node软亲和性
节点软亲和性为节点选择机制提供了一种柔性控制逻辑,被调度的Pod
对象不再是“必须”而是“应该”放置于某些特定节点之上,当条件不满足时它也能够接受被编排于其他不符合条件的节点之上。另外,它还为每种倾向性提供了weight
属性以便用户定义其优先级,取值范围是1 ~ 100
,数字越大优先级越高 。
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-with-node-affinity
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
Pod
资源模板定义了节点软亲和性以选择运行在拥有zone=foo
和ssd
标签(无论其值为何)的节点之上, 其中zone=foo
是更为重要的倾向性规则, 它的权重为60
,相比较来说,ssd
标签就没有那么关键, 它的权重为30
。 这么一来, 如果集群中拥有足够多的节点,那么它将被此规则分为四类 : 同时满足拥有zone=foo
和ssd
标签、仅具有zoo=foo
标 签、 仅具有ssd
标签, 以及不具备此两个标签, 如图所示:
示例环境共有三个节点,相对于定义的节点亲和性规则来说,它们所拥有的倾向性权重分别如图所示。在创建需要3
个Pod
对象的副本时,其运行效果为三个Pod
对象被分散运行于集群中的三个节点之上,而非集中运行于某一个节点 。
之所以如此,是因为使用了节点软亲和性的预选方式,所有节点均能够通过调度器上MatchNodeSelector
预选策略的筛选,因此,可用节点取决于其他预选策略的筛选结果。在第二阶段的优选过程中,除了NodeAffinityPriority
优选函数之外,还有其他几个优选函数参与优先级评估,尤其是SelectorSpreadPriority
,它会将同一个ReplicaSet
控制器管控的所有Pod
对象分散到不同的节点上运行以抵御节点故障带来的风险 。不过,这种节点亲和性的权重依然在发挥作用,如果把副本数量扩展至越过节点数很多,如15
个, 那么它们将被调度器以接近节点亲和性权重比值90:60:30
的方式分置于相关的节点之上。
二、Pod亲和性调度
2.1 位置拓补
Pod
亲和性调度需要各相关的Pod
对象运行于“同一位置”, 而反亲和性调度则要求它们不能运行于“同一位置” 。同一位置取决于节点的位置拓扑, 拓扑的方式不同。
如果以基于各节点的kubernetes.io/hostname
标签作为评判标准,那么很显然,“同一位置” 意味着同一个节点,不同节点即不同的位置, 如图所示
如果是基于所划分的故障转移域来进行评判,同一位置, 而server2
和server3
属于另一个意义上的同一位置:
因此,在定义Pod
对象的亲和性与反亲和性时,需要借助于标签选择器来选择被依赖的Pod
对象,并根据选出的Pod
对象所在节点的标签来判定“同一位置”的具体意义。
2.2 Pod硬亲和
Pod
强制约束的亲和性调度也使用requiredDuringSchedulinglgnoredDuringExecution
属性进行定义。Pod
亲和性用于描述一个Pod
对象与具有某特征的现存Pod
对象运行位置的依赖关系,因此,测试使用Pod
亲和性约束,需要事先存在被依赖的Pod
对象,它们具有特别的识别标签。例如创建一个有着标签app=tomcat
的Deployment
资源部署一个Pod
对象:
$ kubectl run tomcat -l app=tomcat --image tomcat:alpine
再通过资源清单定义一个Pod
对象,它通过labelSelector
定义的标签选择器挑选感兴趣的现存Pod
对象, 而后根据挑选出的Pod
对象所在节点的标签kubernetes. io/hostname
来判断同一位置的具体含义,并将当前Pod
对象调度至这一位置的某节点之上:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity-1
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["tomcat"]}
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
事实上,kubernetes.io/hostname
标签是Kubernetes
集群节点的内建标签,它的值为当前节点的节点主机名称标识,对于各个节点来说,各有不同。因此,新建的Pod
象将被部署至被依赖的Pod
对象的同一节点上,requiredDuringSchedulingIgnoredDuringExecution
表示这种亲和性为强制约束。
基于单一节点的Pod
亲和性只在极个别的情况下才有可能会用到,较为常用的通常是基于同一地区 region
、区域zone
或机架rack
的拓扑位置约束。例如部署应用程序服务myapp
与数据库db
服务相关的Pod
时,db Pod
可能会部署于如上图所示的foo
或bar
这两个区域中的某节点之上,依赖于数据服务的myapp Pod
对象可部署于db Pod
所在区域内的节点上。当然,如果db Pod
在两个区域foo
和bar
中各有副本运行,那么myapp Pod
将可以运行于这两个区域的任何节点之上。
依赖于亲和于这两个Pod
的其他Pod
对象可运行于zone
标签值为foo
和bar
的区域内的所有节点之上。资源配置清单如下
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
在调度示例中的Deployment
控制器创建的Pod
资源时,调度器首先会基于标签选择器 查询拥有标签app=db
的所有Pod
资源,接着获取到它们分别所属 的节点的zone
标签值,接下来再查询拥有匹配这些标签值的所有节点,从而完成节点预选。而后根据优选函数计算这些节点的优先级,从而挑选出运行新建Pod
对象的节点。
需要注意的是,如果节点上的标签在运行时发生了更改,以致它不再满足Pod
上的亲和性规则,但该Pod
还将继续在该节点上运行,因此它仅会影响新建的Pod
资源;另外,labelSelector
属性仅匹配与被调度器的Pod
在同一名称空间中的Pod
资源,不过也可以通过为其添加 namespace
字段以指定其他名称空间 。
2.3 Pod软亲和
类似于节点亲和性机制,Pod
也支持使用preferredDuringSchedulinglgnoredDuringExecution
属性定义柔性亲和机制,调度器会尽力确保满足亲和约束的调度逻辑,然而在约束条 件不能得到满足时,它也允许将Pod
对象调度至其他节点运行。下面是一个使用了Pod
软亲和性调度机制的资源配置清单示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-preferred-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["cache"]}
topologyKey: zone
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
它定义了两组亲和性判定机制,一个是选择cache Pod
所在节点的zone
标签,并赋予了较高的权重80
,另一个是选择db Pod
所在节点的 zone
标签,它有着略低的权重20
。于是,调度器会将目标节点分为四类 :cache Pod
和db Pod
同时所属的zone
、cache Pod
单独所属的zone
、db Pod
单独所属的zone
,以及其他所有的zone
。
2.4 Pod反亲和
podAffinity
用于定义Pod
对象的亲和约束,对应地,将其替换为podAntiAffinty
即可用于定义Pod
对象的反亲和约束。不过,反亲和性调度一般用于分散同一类应用的Pod
对象等,也包括将不同安全级别的Pod
对象调度至不同的区域、机架或节点等。下面的资源配置清单中定义了由同一Deployment
创建但彼此基于节点位置互斥的Pod
对象:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-anti-affinity
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["myapp"]}
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: 192.168.99.181:5000/wod/nginx:1.15.1
由于定义的强制性反亲和约束,因此,创建的4
个Pod
副本必须运行于不同的节点中。不过,如果集群中一共只存在3
个节点,因此,必然地会有一个Pod
对象处于Pending
状态。
类似地,Pod
反亲和性调度也支持使用柔性约束机制,在调度时,它将尽量满足不把位置相斥的Pod
对象调度于同一位置,但是,当约束关系无法得到满足时,也可以违反约束而调度。可参考podAffinity
的柔性约束示例将上面的Deployment
资源myapp-with-pod-anti-affinity
修改为柔性约束并进行调度测试。