k8s调度策略
我们在创建pod资源的时候,pod会根据schduler进行调度,那么默认会调度到随机的一个工作节点,如果我们想要pod调度到指定节点或者调度到一些具有相同特点的节点,应该怎么办呢?下面介绍k8s中的调度策略,主要有以下调度策略:
-
节点选择器调度
- 节点亲和调度
- pod亲和调度
- pod反亲和调度
- 污点-容忍度调度
节点选择器
nodeName
通过指定节点名称,把pod调度到指定节点,下面的pod将会被调度到k8s-node01
节点。
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: default
labels:
app: nginx
spec:
nodeName: k8s-node01
containers:
- name: nginx
image: nginx:1.21.5
imagePullPolicy: IfNotPresent
pod创建成功后,通过kubectl get pod -o wide
发现被调度到了k8s-node01
节点。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodename 1/1 Running 0 5s 10.81.85.226 k8s-node01 <none> <none>
nodeSelector
首先给k8s-node02
节点添加一个标签type=nginx
#给节点添加标签
kubectl label node k8s-node02 type=nginx
#查看节点-显示标签
kubectl get node --show-labels
创建pod并将该pod调度到标签为type=nginx
的节点上
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: default
labels:
app: nginx
spec:
nodeSelector:
type: nginx
containers:
- name: nginx
image: nginx:1.21.5
imagePullPolicy: IfNotPresent
pod创建成功后,通过kubectl get pod -o wide
查看pod状态,如下所示pod被成功调度到k8s-node02
节点。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodename 1/1 Running 0 9s 10.81.58.214 k8s-node02 <none> <none>
节点亲和性
节点亲和性属性定义可以通过kubectl explain pods.spec.affinity.nodeAffinity
查看,如下所示:
nodeAffinity <Object> # POD 对 node 节点的亲和性
preferredDuringSchedulingIgnoredDuringExecution <[]Object> # 软亲和性要求,尽量满足亲和性
preference <Object> # 亲和的节点对象
matchExpressions <[]Object> # 查找表达式
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
matchFields <[]Object> # 查找字段
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
weight <integer> # 权重 1 - 100
requiredDuringSchedulingIgnoredDuringExecution <Object> # 硬亲和性要求,不满足则 Pending
nodeSelectorTerms <[]Object> # 选择器对象列表
matchExpressions <[]Object> # 选择器对象列表
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
matchFields <[]Object> # 查找字段
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
preferredDuringSchedulingIgnoredDuringExecution
表示有节点尽量满足这个位置定义的亲和性,这不是一个必须的条件,也叫软亲和性
requiredDuringSchedulingIgnoredDuringExecution
表示必须有节点满足这个位置定义的亲和性,这是个硬性条件,也叫硬亲和性
软亲和性
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity1
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21.5
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软亲和性要求,不满足也可以
- preference:
matchExpressions:
- key: type:
operator: In
values:
- tomcat
weight: 50
上面的pod在调度时,尽管找不到标签为type=tomcat
的节点,任然可以调度成功,这是软亲和性的特点。
硬亲和性
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity2
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21.5
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性要求,不满足就Pending
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- tomcat
pod创建成功后,我们查看pod状态如下:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodeaffinity2 0/1 Pending 0 6s <none> <none> <none> <none>
上面的pod在调度时,因为找不到标签为type=tomcat
的节点,所以调度不成功,一直显示Pending
状态,这是硬亲和性的特点。
pod亲和性
pod亲和性属性定义可以通过kubectl explain pods.spec.affinity.podAffinity
查看,如下所示:
podAffinity <Object> # POD 对其他 POD 的亲和性
preferredDuringSchedulingIgnoredDuringExecution <[]Object> # 软性亲和性,尽量满足亲和性
podAffinityTerm <Object> # 亲和的 POD 对象
labelSelector <Object> # 标签选择器对象列表
matchExpressions <[]Object> # 标签选择器对象,选 POD 标签
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
matchLabels <map[string]string> # 集合标签选择器
namespaces <[]string> # 名称空间的列表
topologyKey <string> # 亲和判断条件
weight <integer> # 权重 1 - 100
requiredDuringSchedulingIgnoredDuringExecution <[]Object> # 硬性亲和性,不满足则 Pending
labelSelector <Object> # 标签选择器对象列表
matchExpressions <[]Object> # 标签选择器对象,选 POD 标签
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
matchLabels <map[string]string> # 集合标签选择器
namespaces <[]string> # 名称空间的列表
topologyKey <string> # 亲和判断条件
pod亲和性跟节点亲和性一样,也有软亲和性和硬亲和性,它的特点跟节点亲和性里的软亲和性和硬亲和性一样。下面用硬亲和性举个例子,软亲和性大家自己动手写写。
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: nginx:1.21.5
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
namespace: default
labels:
app: db
spec:
containers:
- name: nginx
image: nginx:1.21.5
imagePullPolicy: IfNotPresent
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性要求,不满足的 Pending
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname # 亲和性的依据为同一个主机名则亲和
上面有两个pod,第一个pod设置了标签app=myapp
,第二个pod通过硬亲和性去匹配标签为app=myapp
的pod,所以两个pod会被调度到同一个节点。查看pod状态如下(可以删除后重新创建,多运行几次,观察两个pod总是调度相同的节点上):
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod1 1/1 Running 0 9s 10.81.85.230 k8s-node01 <none> <none>
pod2 1/1 Running 0 9s 10.81.85.231 k8s-node01 <none> <none>
pod反亲和性
节点亲和性属性定义可以通过kubectl explain pods.spec.affinity.podAntiAffinity
查看,如下所示:
podAntiAffinity <Object> # POD 对其他 POD 的反亲和性
preferredDuringSchedulingIgnoredDuringExecution <[]Object> # 软性反亲和性,尽量满足亲和性
podAffinityTerm <Object> # 反亲和的 POD 对象
labelSelector <Object> # 标签选择器对象列表
matchExpressions <[]Object> # 标签选择器对象,选 POD 标签
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
matchLabels <map[string]string> # 集合标签选择器
namespaces <[]string> # 名称空间的列表
topologyKey <string> # 亲和判断条件
weight <integer> # 权重 1 - 100
requiredDuringSchedulingIgnoredDuringExecution <[]Object> # 硬性反亲和性,不满足则 Pending
labelSelector <Object> # 标签选择器对象列表
matchExpressions <[]Object> # 标签选择器对象,选 POD 标签
key <string> # 标签
operator <string> # 操作:比较
values <[]string> # 值
matchLabels <map[string]string> # 集合标签选择器
namespaces <[]string> # 名称空间的列表
topologyKey <string> # 亲和判断条件
反亲和性跟亲和性刚好相反,看下下面的例子,第一个pod定义了标签app=myapp
,第二个pod通过反亲和性规则app=myapp
,因此第二个pod不会调度到第一个pod相同的节点上。
apiVersion: v1
kind: Pod
metadata:
name: pod3
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:1.21.5
---
apiVersion: v1
kind: Pod
metadata:
name: pod4
namespace: default
labels:
app: db
spec:
containers:
- name: busybox
image: nginx:1.21.5
imagePullPolicy: IfNotPresent
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性要求,不满足的 Pending
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname # 反亲和性的依据为同一个主机名
查看两个pod的状态如下所示,分别被调度到了两个不同的节点
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod3 1/1 Running 0 5s 10.81.85.232 k8s-node01 <none> <none>
pod4 1/1 Running 0 5s 10.81.58.215 k8s-node02 <none> <none>
pod亲和性就是pod和pod更倾向腻在一起,把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;
pod反亲和性就是pod和pod更倾向不腻在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。
污点-容忍度
node污点
污点只用在node上的键值属性nodes.spec.taints
,它的作用是拒绝不能容忍这些污点的pod运行的,因此需要在pod上定义容忍度pods.spec.tolerations
,它也是键值数据,是一个列表,表示pod可以容忍的污点列表。一个pod能不能运行在一个节点上,就是pods.spec.tolerations
列表中是否包括了nodes.spec.taints
中的数据。
node污点的属性格式详见kubectl explain node.spec.taints
,如下所示:
taints <[]Object> # 污点对象列表
effect <string> # 当 POD 不能容忍这个污点的时候,要采取的行为,也就是排斥不容忍污点的 POD
NoSchedule # 影响调度过程,但是已经调度完成 POD 无影响
PreferNoSchedule # 影响调度过程,尝试驱逐调度已经完成的但不容忍新污点的 POD
NoExecute # 新增的污点,影响新的调度过程,且强力驱逐调度已经完成的但不容忍新污点的 POD
key <string> # 键
timeAdded <string> #
value <string> # 值
#给节点1打上污点,键为 node-type 值为 production,污点动作NoSchedule
kubectl taint node k8s-node01 node-type=production:NoSchedule
#删除污点
kubectl taint node k8s-node01 node-type-
节点k8s-node01
被打上污点后,创建一个pod不设置容忍度,看它的调度情况是怎么样的。
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:1.21.5
ports:
- name: http
containerPort: 80
pod创建成功后,如下所示,发现pod都被调度到k8s-node02
节点上,符合预期,因为k8s-node01
节点设置了污点,如果pod不添加容忍度是不会被调度到该节点的。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-5c75f74dc5-2z7hg 1/1 Running 0 7s 10.81.58.216 k8s-node02 <none> <none>
myapp-deploy-5c75f74dc5-4sww5 1/1 Running 0 6s 10.81.58.217 k8s-node02 <none> <none>
myapp-deploy-5c75f74dc5-fsdwh 1/1 Running 0 6s 10.81.58.219 k8s-node02 <none> <none>
myapp-deploy-5c75f74dc5-w9w82 1/1 Running 0 6s 10.81.58.218 k8s-node02 <none> <none>
此时给k8s-node02
节点也打上污点,如下所示:
kubectl taint node k8s-node02 node-type=dev:NoExecute
通过kubectl get pods -o wide
命令查看pod状态如下所示,发现k8s-node02
节点新增的污点驱逐了不能容忍污点的pod ,所以所有pod都被挂起。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-5c75f74dc5-4dfzl 0/1 Pending 0 20s <none> <none> <none> <none>
myapp-deploy-5c75f74dc5-654bx 0/1 Pending 0 20s <none> <none> <none> <none>
myapp-deploy-5c75f74dc5-b5bl4 0/1 Pending 0 20s <none> <none> <none> <none>
myapp-deploy-5c75f74dc5-wnz2r 0/1 Pending 0 20s <none> <none> <none> <none>
pod容忍度
pod容忍度的属性格式详见kubectl explain node.spec.tolerations
,如下所示:
tolerations <[]Object> # 容忍度对象
effect <string> # 能否容忍 node 上的污点驱逐策略,为空表示容忍任何驱逐策略
NoSchedule # 能容忍 node 污点的 NoSchedule
PreferNoSchedule # 能容忍 node 污点的 PreferNoSchedule
NoExecute # 能容忍 node 污点的 NoExecute
key <string> # 污点的键
operator <string> # Exists 污点存在不管什么值,Equal 污点的值必须等值
tolerationSeconds <integer> # 容忍时间,即如果被驱逐,可以等多久再走,默认 0 秒,NoExecute 使用
value <string> # 污点的值
上面分别给k8s-node01
节点和k8s-node02
节点都设置了污点,下面创建pod并添加容忍度,值为dev
污点动作为NoExecute
。
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:1.21.5
ports:
- name: http
containerPort: 80
tolerations:
- key: node-type
operator: Equal
value: dev
effect: NoExecute
运行上面的pod,可以发现pod成功调度到了k8s-node02
节点,因为pod设置了node-type=dev:NoExecute
的容忍度。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-667785b946-4kxml 1/1 Running 0 6s 10.81.58.222 k8s-node02 <none> <none>
myapp-deploy-667785b946-9dqgn 1/1 Running 0 6s 10.81.58.220 k8s-node02 <none> <none>
myapp-deploy-667785b946-cp7db 1/1 Running 0 6s 10.81.58.221 k8s-node02 <none> <none>
myapp-deploy-667785b946-xf5b5 1/1 Running 0 6s 10.81.58.223 k8s-node02 <none> <none>
字段选择器
上面介绍亲和性时,示例中的匹配规则都是用的表达式匹配,匹配规则有两种:表达式匹配-matchExpressions
和字段匹配-matchFields
。
表达式匹配通过节点、pod的标签进行匹配,字段匹配根据字段选择器进行匹配,在k8s中不同的资源类型支持不同的字段选择器。 所有资源类型都支持metadata.name
和metadata.namespace
字段。下面是一些使用字段选择器查询的例子:
metadata.name=my-service
metadata.namespace!=default
status.phase=Pending
使用不被支持的字段选择器会产生错误,例如:
kubectl get ingress --field-selector foo.bar=baz
下面这个命令将筛选出status.phase
字段值为Running
的所有pod
kubectl get pods --field-selector status.phase=Running
链式选择器
kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always
匹配亲和性
下面创建pod,通过字段选择器进行匹配,匹配条件为metadata.name=k8s-node02
,该pod将会被调度到k8s-node02
节点(前提是不考虑污点和容忍度)。
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity2
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21.5
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性要求,不满足就Pending
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: In
values:
- k8s-node02