前言
上一章内容主要讲解了Pod的资源管理,这篇文章主要讲解Pod的调度策略。
nodeSelector
将pod调度到指定节点上。看个案例: nginx.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
disk: ssd
这意味着,该pod只想运行在带有disk=ssd标签的Node上。此时,我部署的节点都没有这个标签,试着创建pod看看发生了什么
lua
kubectl create -f nginx.yaml
此时我们监控pod变化, kubectl get po -n nginx -w
,会发现pod一直处于Pending状态,我们使用describe查看详细信息,在Events事件中会看到如下内容
rust
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m10s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.
Warning FailedScheduling 2m9s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.
可以看到Pod调度失败,两个节点没有匹配Pod的节点亲和性(node affinity)或选择器(selector)的要求。
好,现在我们给k8s-node01节点增加标签disk=ssd,
ini
kubectl label no k8s-node01 disk=ssd
然后,我们可以如下命令,查看标签设置成功
sql
kubectl get no k8s-node01 --show-labels
此时,我们再次看pod的Events事件
scss
Warning FailedScheduling 10m default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.
Normal Scheduled 2m default-scheduler Successfully assigned nginx/nginx-deployment-68cb47c4b-lrdbw to k8s-node01
Normal Pulling 117s kubelet Pulling image "nginx"
发现调度成功了,并且调度到了拥有标签disk=ssd的k8s-node01节点上。
ps: 如果想要给节点取消标签,可以这样执行kubectl label no k8s-node01 disk-
,意思就是删除disk标签
nodeAffinity
nodeAffinity目前支持两种:preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution两种约束。
requiredDuringSchedulingIgnoredDuringExecution:硬需求,类似nodeSelector
preferredDuringSchedulingIgnoredDuringExecution:软需求,将Pod优先调度到满足标签节点上,但如果集群中不存在这样的节点,或者由于其他原因无法调度到这样的节点上,那么调度到其他节点上也是可以接受的。
requiredDuringSchedulingIgnoredDuringExecution使用案例:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
表面pod要被调度到包含标签disk且值为ssd的node上,此时我的节点都没有此标签,是调度不成功的,不多说了,和上面的nodeSelector一样的效果。
那此时,我们把requiredDuringSchedulingIgnoredDuringExecution改为preferredDuringSchedulingIgnoredDuringExecution,看下面案例:
yaml
# ......
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
# ......
上面的yaml文件修改nodeAffinity部分变为软亲和,然后我们再次执行该yaml文件,看看pod是否可以创建成功,这样执行后,会发现调度成功了。 笔者试验中,此pod被调度到了k8s-node02节点,虽然这个节点并没有disk=ssd的标签。
从上面的配置中可以看到In操作符,nodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。虽然没有节点排斥功能,但是用NotIn和DoesNotExist就可以实现排斥的功能了
以上就是节点亲和与反亲和
pod亲和性与反亲和性
podAffinity
Pod亲和与互斥的调度具体做法,就是通过在Pod的定义上增加topologyKey属性,来声明对应的目标拓扑区域内几种相关联的Pod 要"在一起或不在一起"。与节点亲和相同,Pod亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution和 preferredDuringSchedulingIgnoredDuringExecution。Pod的亲和性被定义于PodSpec的affinity字段的podAffinity子字段中;Pod间的互斥性则被定义于同一层次的podAntiAffinity子字段中。
podAffinity是一个用来定义Pod亲和性规则的概念。亲和性规则用于指定希望将一个 Pod 调度到另一个 Pod 的附近的偏好设置。
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- frontend
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
这个示例指定了一个必须满足的亲和性规则,要求将新的 Pod 调度到标签为 app=frontend 的其他 Pod 所在的节点上。
topologyKey 指定了用来匹配节点的拓扑约束键。
除了 requiredDuringSchedulingIgnoredDuringExecution 外,还可以使用 preferredDuringSchedulingIgnoredDuringExecution 类型的亲和性规则。这种类型的规则描述了偏好而非硬性要求,如果无法满足,Pod 仍然可以被调度到不符合条件的节点。例如:
yaml
# ......
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- frontend
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
这个示例设置了一个优先级为 100 的偏好规则,希望将新的 Pod 调度到标签为 app=frontend 的其他 Pod 所在的节点上。
podAntiAffinity
podAntiAffinity 是一种用来定义 Pod 反亲和性规则的概念。 反亲和性规则用于指定不希望将一个 Pod 调度到另一个 Pod 的附近的偏好设置。这种设置可以帮助提高应用程序的高可用性和容错能力,避免将同一应用程序的多个实例或相关的服务部署到同一节点上。
yaml
# ......
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- frontend
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
这个示例指定了一个必须满足的反亲和性规则,要求新的 Pod 不应该调度到已经有标签为 app=frontend 的其他 Pod 所在的节点上。
反亲和性规则也可以使用 preferredDuringSchedulingIgnoredDuringExecution 类型。这种类型描述了偏好而非硬性要求,如果无法满足,Pod 仍然可以被调度到不符合条件的节点。例如:
yaml
# ......
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- frontend
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
这个示例设置了一个优先级为 100 的反亲和性偏好规则,希望新的 Pod 尽量不要调度到标签为 app=backend 的其他 Pod 所在的节点上。
topologyKey
topologyKey可以理解为节点的拓扑级别,其后需要填写的是节点的某个标签的键。在Pod反亲和性中,topologyKey的作用是在拥有相同标签值的节点上,保证只有一个相关Pod存在。
Kubernetes内置了如下一些常用的默认拓扑域: 比如:kubernetes.io/hostname;
可以执行如下命令查看:
sql
kubectl get no k8s-node01 --show-labels
需要注意的是,以上拓扑域是由Kubernetes自己维护的,在Node节点初始化时,controller-manager会为Node打上许多标签,比如 kubernetes.io/hostname这个标签的值就会被设置为Node节点的 hostname。
另外,公有云厂商提供的Kubernetes服务(比如阿里云ack),还会给Node打上 topology.kubernetes.io/region和topology.kubernetes.io/zone标签,以确定 各个节点所属的拓扑域。
Taints和Tolerations
Taints和Tolerations用于保证Pod不被调度到不合适的Node上,其中Taints用于Node上,Tolerations用于Pod上。
支持的Taints类型:
- NoSchedule:新的Pod不调度到该Node上,不影响正在运行的Pod;
- PreferNoSchedule:尽量不调度到该Node上
- NoExecute:新的Pod不调度到该Node上,并且删除(evict)已在运行的Pod。Pod可以增加一个时间(tolerationSeconds)
好,接下来,我们进行实验:
在k8s-node02节点上,先跑一个pod,我跑了tomcat的pod,如下:
sql
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
tomcat-deployment-5d64468cf4-5ht2f 1/1 Running 0 23s 10.244.2.83 k8s-node02 <none> <none>
好了,前置条件准备完了,接下来,给k8s-node02节点添加污点(Taints),执行如下命令
NoSchedule
ini
kubectl taint no k8s-node02 for-special-user=fengxiao:NoSchedule
执行成功后,查询该节点详细信息kubectl describe no k8s-node02
,会看到如下内容
ini
Taints: for-special-user=fengxiao:NoSchedule
这就表明污点添加成功了。污点的键为 for-special-user,值为 fengxiao,效果是 NoSchedule,即不允许普通的 Pod 被调度到这个节点上。
接下来,我们创建pod,但是未容忍node上的污点,看看能否调度到k8s-node02节点(注意我这里给该节点增加的标签disk=ssd),
nginx-deployment.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
执行命令kubectl apply -f nginx-deployment.yaml
,创建之后,会发现pod一直处于Pending
状态,查看该pod详细信息,会看到如下输出内容
scss
1 node(s) had taint {for-special-user: fengxiao}, that the pod didn't tolerate, 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
再看看,k8s-node02节点上已经运行的pod, kubectl get po -n nginx -owide
,输出如下内容
sql
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-6c4cf4b58b-fbnnb 0/1 Pending 0 4m22s <none> <none> <none> <none>
tomcat-deployment-5d64468cf4-5ht2f 1/1 Running 0 23m 10.244.2.83 k8s-node02 <none> <none>
可以看到,NoSchedule确实是新的Pod不调度到该Node上,不影响正在运行的Pod。
PreferNoSchedule
上面案例我们实验了NoSchedule效果,现在实验PreferNoSchedule,增加PreferNoSchedule类型污点
ini
kubectl taint no k8s-node02 for-special-user=fengxiao:PreferNoSchedule
此时,查看pod状态,还是Pending
状态,这是因为剩余的Taint中存在effect=NoSchedule,则调度器不会把该Pod调度到这一节点上。
我们取消NoSchedule污点
ini
kubectl taint no k8s-node02 for-special-user=fengxiao:NoSchedule-
取消后,发现pod调度到该节点了。
NoExecute
增加PreferNoSchedule类型污点
ini
kubectl taint no k8s-node02 for-special-user=fengxiao:NoExecute
添加之后,会发现运行在该节点上的pod都被删除了.
以上都是pod未容忍污点的情况,下面我们尝试让pod容忍污点。
还是上面的nginx-deployment.yaml
,我们在spec部分稍加变更:
yaml
# ......
spec:
tolerations:
- key: "for-special-user"
operator: "Equal"
value: "fengxiao"
effect: "NoSchedule"
# ......
针对k8s-node02节点添加NoSchedule类型的污点,添加方法可以看上面文章有提到,添加之后,运行pod,发现此时 pod可以被调度到有污点的k8s-node02节点上了。
那如果增加NoExecute类型的污点呢,pod还会被驱逐吗?,我们继续实验,增加NoExecute类型的节点,观察pod变化
下面是我未增加NoExecute类型的污点时,该节点pod运行情况
sql
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-747797bbc7-k9mb5 1/1 Running 0 14m 10.244.2.87 k8s-node02 <none> <none>
tomcat-deployment-ddd48b668-wd49h 1/1 Running 0 24m 10.244.2.86 k8s-node02 <none> <none>
这两个pod中nginx-deployment添加了容忍(tolerations),tomcat-deployment未添加。
vbnet
spec:
tolerations:
- key: "for-special-user"
operator: "Equal"
value: "fengxiao"
effect: "NoExecute"
增加NoExecute类型污点后,pod发生如下变化
css
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-747797bbc7-k9mb5 1/1 Running 0 14m 10.244.2.87 k8s-node02 <none> <none>
tomcat-deployment-ddd48b668-wd49h 1/1 Running 0 24m 10.244.2.86 k8s-node02 <none> <none>
tomcat-deployment-ddd48b668-wd49h 1/1 Terminating 0 27m 10.244.2.86 k8s-node02 <none> <none>
tomcat-deployment-ddd48b668-bw4h9 0/1 Pending 0 0s <none> <none> <none> <none>
tomcat-deployment-ddd48b668-bw4h9 0/1 Pending 0 0s <none> <none> <none> <none>
tomcat-deployment-ddd48b668-wd49h 0/1 Terminating 0 27m 10.244.2.86 k8s-node02 <none> <none>
tomcat-deployment-ddd48b668-wd49h 0/1 Terminating 0 27m 10.244.2.86 k8s-node02 <none> <none>
tomcat-deployment-ddd48b668-wd49h 0/1 Terminating 0 27m 10.244.2.86 k8s-node02 <none> <none>
可以看到,未添加容忍的pod:tomcat-deployment-ddd48b668-wd49h被删除了,而添加容忍的pod:nginx-deployment-747797bbc7-k9mb5 未受影响
那我如果给给具有NoExecute 效果的Toleration加入一个可选的tolerationSeconds字段,情况会发生如何变化呢:
vbnet
spec:
tolerations:
- key: "for-special-user"
operator: "Equal"
value: "fengxiao"
effect: "NoExecute"
tolerationSeconds: 60
会发现pod在这个节点上存活60秒后被驱逐。如果60秒内,将污点去除,就不会将pod驱逐。
tolerations配置技巧
Pod的Toleration声明中的key和effect需要与Taint的设置保持一致
- operator的值是Exists时,无需设置value
- operator的值是Equal时,需要value相等
- operator未设置时,则默认值为Equal
- 空的key配合Exists操作符能够匹配所有键和值
- 空的effect匹配所有effect
案例总结
通过上面的例子,我们总结一下:
添加NoExecute类型的污点,会对该节点上运行的pod产生影响:
- 没有设置Toleration的Pod会被立刻驱逐
- 配置了对应Toleration的Pod,如果没有为tolerationSeconds赋 值,则会一直留在这一节点中
- 置了对应Toleration的Pod且指定了tolerationSeconds值,则会 在指定的时间后驱逐
我们通过describe命令查看pod的详细信息,会发现有一些默认Toleration
ini
Tolerations: for-special-user=fengxiao:NoSchedule
for-special-user=fengxiao:NoExecute for 60s
node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Kubernetes 调度器会在发现节点不就绪或不可达时自动向节点添加相应的污点。这是Kubernetes控制节点健康状况的一种机制:
- 当节点无法定期向控制平面发送心跳时,控制平面会认为节点不可达,自动添加 node.kubernetes.io/unreachable 污点。
- 当节点无法定期向控制平面发送节点状态更新时,控制平面会认为节点不就绪,自动添加 node.kubernetes.io/not-ready 污点。
添加的这种自动机制保证了在某些节点发生一些临时性问题时,Pod默认能够继续停留在当前节点运行5min等待节点恢复,而不是立即被驱逐,从而避免系统的异常波动。
最后
好了,终于写完了,Pod的调度是一个很重要的知识点,关于Pod调度原理,可能还需要一篇文章来阐述。