在讲解本章之前,我先通过一个故事,来描绘一下 k8s 中 node 和 pod 的爱恨情仇。
雄性(node)| 雌性(pod) 在银河系以外的一个星球上,有着一群两性生物,分别是雌性(pod)和雄性(node)。雌性生物居多,而雄性生物由于优胜劣汰,只剩下 3 只优质的雄性生物(node)。雄雌在一起就容易产生吸引,就会有以下的情况产生: (1)雄(node)少雌(pod)多,而这三只优质的雄性生物性格、优点都是一致的,雌性生物选谁都一样。于是,雌性生物就分为均等分为三列和三只雄性生物在一起。这种类似于平均分配的原则。 k8s 中的概念:在 k8s 中是最常见最普通的 pod 分布方式,常用与 deployment 和 daemonset 控制器。
(2)时间一长,生物开始了进化。三只雄性(node)生物各自有了不一样的地方,编号 1 的雄性学会了种植(node01 label skill=‘grow’);编号 2 的雄性学会了打猎(node02 label skill=‘hunt’);编号 3 的雄性啥也没学会(没学会就保持默认)。普通的雌性(pod)仍然保持着平均分配的原则。而一些进化快的雌性(pod)生物发现了三只优质雄性(node)生物各自的不同,各自也开始有了一些选择。一些雌性更加青睐学会种植的雄性生物(nodeSelector skill=‘grow’) ;一些雌性更加青睐学打猎的雄性生物(nodeSelector skill=‘hunt’) ;而有些雌性生物喜欢的特点很奇特,它们喜欢会飞的雄性,而仅存的三只雄性生物都无法满足这一特点。如果有天一只雄性生物进化会飞了,它们就会依附与会飞的雄性生物,如果始终没有进化出会飞的雄性生物,则它们一直等下去这在 k8s 中,就是
yaml文件中nodeSelector的使用。 k8s 中的概念:当 node 打上特定的标签后,会出现如下情况:
nodeSelector ,则保持默认 schedule 调度算法的方式;nodeSelector ,且指定 nodeSelector 中的 key、value 符合某一个 node 中的某一个 key、value,则这个 pod 直接调度到该 node;nodeSelector ,指定 nodeSelector 中的 key、value 不包含在任何一个 node 中,则这个 pod 会一直处于 padding 状态。(3)三只雄性(node)不仅优点增长了,缺点也随之而来。编号 1 的雄性喜欢打脸(node01 taint hobby=‘face’);编号 2 的雄性喜欢打屁股(node01 taint hobby=‘hunkers’);编号 3 的雄性喜欢踩脚(node01 taint hobby=‘foot’);普通的雌性(pod)生物仍然保持平均分配的原则,而一些再次进化的雌性生物也有了自己的性格。能够容忍打脸的雌性则和编号 1 的雄性在一起(tolerations hobby=‘face’),但是这个容忍可能是永久,也可能是 1 天(tolerationSeconds=86400)。而这三只雄性生物偶尔会在一起鬼混,编号 1 的雄性生物说不定哪天就嗜好就变为了喜欢睡懒觉。而一些无法容忍它睡懒觉嗜好的雌性生物就会隔一段时间或者马上就离开它。 k8s 中的概念:这就是 污点 与 容忍。
kubernetes提供了四大类调度方式:
在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。
利用在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。
NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: node1 # 指定调度到node1节点上NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeselector
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeSelector:
nodeenv: pro # 指定调度到具有nodeenv=pro标签的节点上它在NodeSelector的基础之上的进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。
Affinity主要分为三类:
关于亲和性和反亲和性的使用场景的说明:
nodeAffinity 就是节点亲和性,调度可以分成软策略和硬策略两种方式,软策略就是如果你没有满足调度要求的节点的话,POD 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓了的策略;而硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不干的策略。nodeAffinity就有两上面两种策略:
pod.spec.affinity.nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution Node节点必须满足指定的所有规则才可以,相当于硬限制
nodeSelectorTerms 节点选择列表
matchFields 按节点字段列出的节点选择器要求列表
matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
key 键
values 值
operat or 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
preferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
preference 一个节点选择器项,与相应的权重相关联
matchFields 按节点字段列出的节点选择器要求列表
matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
key 键
values 值
operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
weight 倾向权重,在范围1-100。示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
nodeAffinity: #设置node亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
nodeSelectorTerms:
- matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
- key: nodeenv
operator: In
values: ["xxx","yyy"]apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-preferred
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
nodeAffinity: #设置node亲和性
preferredDuringSchedulingIgnoredDuringExecution: # 软限制
- weight: 1
preference:
matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签(当前环境没有)
- key: nodeenv
operator: In
values: ["xxx","yyy"]podAffinity主要实现以运行的Pod为参照,实现让新创建的Pod和参照的Pod在一个区域的功能。
pod.spec.affinity.podAffinity
requiredDuringSchedulingIgnoredDuringExecution 硬限制
namespaces 指定参照pod的namespace
topologyKey 指定调度作用域
labelSelector 标签选择器
matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
key 键
values 值
operator 关系符 支持In, NotIn, Exists, DoesNotExist.
matchLabels 指多个matchExpressions映射的内容
preferredDuringSchedulingIgnoredDuringExecution 软限制
podAffinityTerm 选项
namespaces
topologyKey
labelSelector
matchExpressions
key 键
values 值
operator
matchLabels
weight 倾向权重,在范围1-100示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-requred
namespace: dev
spec:
containers: # 容器配置
- name: nginx
image: nginx:1.17.1
imagePullPolicy: IfNotPresent
ports:
- name: nginx-port
containerPort: 80
protocol: TCP
affinity: # 亲和性配置
podAffinity: # Pod亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions: # 该Pod必须和拥有标签podenv=xxx或者podenv=yyy的Pod在同一个Node上,显然没有这样的Pod
- key: podenv
operator: In
values:
- "xxx"
- "yyy"
topologyKey: kubernetes.io/hostnamePodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不在一个区域中的功能。
它的配置方式和选项跟PodAffinty是一样的,这里不再做详细解释,直接做一个测试案例。
示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-podantiaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
podAntiAffinity: #设置pod亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions: # 匹配podenv的值在["pro"]中的标签
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname在 Kubernetes 中,节点亲和性 NodeAffinity 是 Pod 上定义的一种属性,能够使 Pod 按我们的要求调度到某个节点上,而 Taints(污点) 则恰恰相反,它是 Node 上的一个属性,可以让 Pod 不能调度到带污点的节点上,甚至会对带污点节点上已有的 Pod 进行驱逐。当然,对应的 Kubernetes 可以给 Pod 设置 Tolerations(容忍) 属性来让 Pod 能够容忍节点上设置的污点,这样在调度时就会忽略节点上设置的污点,将 Pod 调度到该节点。一般时候 Taints 通常与 Tolerations 配合使用。
查看 node 的污点
$ kubectl describe nodes k8s-master
...
Taints: node-role.kubernetes.io/master:NoSchedule
...
# 也可通过下面操作查看:
$ kubectl get nodes k8s-master -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node-role.kubernetes.io/master]]污点内容一般组成为 key、value 及一个 effect 三个元素,表现为:
<key>=<value>:<effect>这里的 value 可以为空,表现形式为:
node-role.kubernetes.io/master:NoSchedule
- key: node-role.kubernetes.io/master
- value: 空
- effect: NoSchedule一般我们需要想要设置某个节点只允许特定的 Pod 进行调度,这时候就得对节点设置污点,可以按 kubectl taint node [node] key=value[effect] 格式进行设置,其中 effect 可取值如下:
一般时候我们设置污点,就像下面例子一样对齐进行设置:
### 设置污点并不允许 Pod 调度到该节点
$ kubectl taint node k8s-master key1=value1:NoSchedule
### 设置污点尽量阻止污点调度到该节点
$ kubectl taint node k8s-master key2=value2:PreferNoSchedule
### 设置污点,不允许普通 Pod 调度到该节点,且将该节点上已经存在的 Pod 进行驱逐
$ kubectl taint node k8s-master key3=value3:NoExecute上面说明了如何对 Node 添加污点阻止 Pod 进行调度,下面再说一下如何删除节点上的污点,可以使用下面命令:
kubectl taint node [node] [key]-上面语法和创建污点类似,不过需要注意的是删除污点需要知道 key 和最后面设置一个 “-” 两项将污点删除,示例如下:
为了方便演示,先给节点设置污点:
### 设置污点1
$ kubectl taint node k8s-master key1=value1:PreferNoSchedule
node/k8s-master tainted
### 设置污点2
$ kubectl taint node k8s-master key2=value2:NoSchedule
node/k8s-master tainted
### 设置污点3,并且不设置 value
$ kubectl taint node k8s-master key2=:PreferNoSchedule
node/k8s-master tainted查看污点,可以看到上面设置的三个值:
$ kubectl describe nodes k8s-master
...
Taints: key2=value2:NoSchedule
node-role.kubernetes.io/master:NoSchedule
key1=value1:PreferNoSchedule
key2:PreferNoSchedule
...然后删除污点
### 删除污点,可以不指定 value,指定 [effect] 值就可删除该 key[effect] 的污点
$ kubectl taint node k8s-master key1:PreferNoSchedule-
### 也可以根据 key 直接将该 key2 的所有 [effect] 都删除:
$ kubectl taint node k8s-master key2-再次查看污点,可以看到以上污点都被删除:
$ kubectl describe nodes k8s-master
...
Taints: node-role.kubernetes.io/master:NoSchedule
...为了使某些 Pod 禁止调度到某些特定节点上,就可以对节点设置污点 taints。当然,如果希望有些 Pod 能够忽略节点的污点,继续能够调度到该节点,就可以对 Pod 设置容忍,让 Pod 能够容忍节点上设置的污点,例如:
对一个节点设置污点:
kubectl taint node k8s-node01 key=value:NoSchedule对于 Pod 设置容忍, 以下两种方式都可以:
### 容忍的 key、value 和对应 effect 也必须和污点 taints 保持一致
......
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
### 容忍 tolerations 的 key 和要污点 taints 的 key 一致,且设置的 effect 也相同,不需要设置 value
......
tolerations:
- key: "key"
operator: "Exists"
effect: "NoSchedule"概念
如果一个 node 有多个污点,且 pod 上也有多个容忍,只要 pod 中容忍能包含 node 上设置的全部污点,就可以将 pod 调度到该 node 上。如果 pod 上设置的容忍不能够包含 node 上设置的全部污点,且 node 上剩下不能被包含的污点 effect 为 PreferNoSchedule,那么也可能会被调度到该节点。
注意
当 pod 总存在 容忍,首先 pod 会选择没有污点的节点,然后再次选择容忍污点的节点。
在 kubernetes 中 deployment 设置容忍,示例如下:
apiVersion: apps/vl
kind: Deployment
metadata:
name: example
spec:
replicas: 5
template:
spec:
......
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"正常情况下, 如果一个污点带有 effect=NoExecute 被添加到了这个 Node。那么不能容忍这个污点的所有 Pod 就会立即被踢掉。而带有容忍标签的 Pod 就不会踢掉。然而,一个带有 effect=Noexecute 的容忍可以指定一个 tolerationSeconds 来指定当这个污点被添加的时候在多长时间内不被踢掉。例如:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "Noexecute"
tolerationSeconds: 3600如果这个 Pod 已经在这个带污点且 effect 为 NoExecute 的 node 上。这个 pod 可以一直运行到 3600s 后再被踢掉。如果这时候 Node 的污点被移除了,这个 Pod 就不会被踢掉。
Operator 默认是 Equal,可设置为 Equal 与 Exists 两种,按这两种进行示例:
Operator 是 Exists
容忍任何污点
例如一个空的 key,将匹配所有的 key、value、effect。即容忍任何污点。
tolerations:
- operator: "Exists"容忍某 key 值的污点
例如一个空的 effect,并且 key 不为空,那么将匹配所有与 key 相同的 effect:
tolerations:
- key: "key"
operator: "Exists"Operator 是 Equal
node 上有一个污点
Node 和 Pod 的 key 为 key1、value1 与 effect 相同则能调度:
#污点
key1=value1:NoSchedule
#Pod设置
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"node 上有多个污点
Node 的污点的 key、value、effect 和 Pod 容忍都相同则能调度:
## 设置污点
key1=value1:NoSchedule
key2=value2:NoExecute
## Pod设置容忍
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key2"
operator: "Equal"
value: "value2"
effect: "NoExecute"Node 的污点和 Pod 的大部分都相同,不同的是 Node 污点 effect 为 PreferNoSchedule 的,可能会调度:
## 污点
key1=value1:NoSchedule
key2=value2:NoExecute
key3=value3:PreferNoSchedule
## Pod设置容忍
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key2"
operator: "Equal"
value: "value2"
effect: "NoExecute"Node 的污点和 Pod 的大部分都相同,不同的是 Node 污点 effect 为 NoSchedule 和 NoExecute 的,不会被调度:
## 污点
key1=value1:NoSchedule
key2=value2:NoExecute
key3=value3:PreferNoSchedule
## Pod设置容忍
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key3"
operator: "Equal"
value: "value3"
effect: "PreferNoSchedule"对比理解 Exists 和 Equal 之间的区别:
在使用 kubernetes 时,会遇到 某个 node 节点明明已经 宕机了,查看 node 状态从 Ready 状态变为 NotReady 状态,但是 节点所在的 pod 却已经处于 running 状态,过了很长一段时间才会转为 Terminating 状态,这是为什么呢?
污点是对于 node 节点来讲,如果 node 节点 effect 设置为 NoExecute ,它会影响节点上已经运行的 Pod,如下所示:
此外,当某些条件为 true 时,节点控制器会自动污染节点。内置以下污点:
key | 注释 |
|---|---|
node.kubernetes.io/not-ready | 节点尚未准备好。这对应于 NodeCondition Ready 为 false。 |
node.kubernetes.io/unreachable | 无法从节点控制器访问节点。这对应于 NodeCondition Ready 为 Unknown。 |
node.kubernetes.io/out-of-disk | 节点磁盘不足。 |
node.kubernetes.io/memory-pressure | 节点有内存压力。 |
node.kubernetes.io/disk-pressure | 节点有磁盘压力。 |
node.kubernetes.io/network-unavailable | 节点的网络不可用。 |
node.kubernetes.io/unschedulable | 节点不可调度。 |
node.cloudprovider.kubernetes.io/uninitialized | 当 kubelet 从 “外部” 云提供程序开始时,此污点在节点上设置为将其标记为不可用。来自 cloud-controller-manager 的控制器初始化此节点后,kubelet 删除此污点。 |
通过上面知识的铺垫,当一个节点宕机时,kubernetes 集群会给它打上什么样的污点呢?
一个 Ready 状态的节点
$ kubectl get node k8s-node02 -o go-template={{.spec.taints}}
<no value>一个 NotReady 状态的节点
$ kubectl get node k8s-node02 -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node.kubernetes.io/unreachable timeAdded:2021-12-23T13:49:58Z] map[effect:NoExecute key:node.kubernetes.io/unreachable timeAdded:2021-12-23T13:50:03Z]]处于 NotReady 状态的节点被打上了下面两个污点:
Taints: node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unreachable:NoSchedule接下来测试 kubernetes 集群会给 Pod 分配什么样的容忍。
$ kubectl get po nginx-745b4df97d-mgdmp -o yaml
...
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300 ## 300/60=5min
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300 ## 300/60=5min
...看到这里,Pod 的失效机制已经很明白了, 当 node 节点处于 NotReady 状态或者 unreachable 状态时,Pod 会容忍它 5 分钟,然后被驱逐。而这 5 分钟内就算 Pod 处于 running 状态,也是无法正常提供服务的。因此,可以在 yaml 清单中 手动指明 0 容忍,清单文件如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 4
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 0
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 0
containers:
- image: nginx:alpine
name: nginx生成 Pod
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-84f6f75c6-c76fm 1/1 Running 0 6s 10.244.3.16 k8s-node02 <none> <none>
nginx-84f6f75c6-hsxq5 1/1 Running 0 6s 10.244.3.15 k8s-node02 <none> <none>
nginx-84f6f75c6-wkt52 1/1 Running 0 6s 10.244.1.63 k8s-node01 <none> <none>
nginx-84f6f75c6-xmkjs 1/1 Running 0 6s 10.244.3.17 k8s-node02 <none> <none>接下来强制关闭 k8s-node02 节点,查看 Pod 是否转移。
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-84f6f75c6-c76fm 1/1 Terminating 0 116s 10.244.3.16 k8s-node02 <none> <none>
nginx-84f6f75c6-csqf4 1/1 Running 0 13s 10.244.1.66 k8s-node01 <none> <none>
nginx-84f6f75c6-hsxq5 1/1 Terminating 0 116s 10.244.3.15 k8s-node02 <none> <none>
nginx-84f6f75c6-r2v4p 1/1 Running 0 13s 10.244.1.64 k8s-node01 <none> <none>
nginx-84f6f75c6-v4knq 1/1 Running 0 13s 10.244.1.65 k8s-node01 <none> <none>
nginx-84f6f75c6-wkt52 1/1 Running 0 116s 10.244.1.63 k8s-node01 <none> <none>
nginx-84f6f75c6-xmkjs 1/1 Terminating 0 116s 10.244.3.17 k8s-node02 <none> <none>在 node 节点转为 NotReady 状态后,Pod 立刻进行了转移。这是通过 在 yaml 清单文件中明确指定 容忍时间。还可以直接修改 apiserver 配置来修改默认容忍时间。
vim /etc/kubernetes/manifests/kube-apiserver.yaml
...
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=192.168.1.11
- --default-not-ready-toleration-seconds=1 ## 新增行
- --default-unreachable-toleration-seconds=1 ## 新增行
...修改保存后, kube-apiserver-k8s-masterpod 会自动重载最新配置。
$ kubectl get po nginx-84f6f75c6-wkt52 -o yaml
...
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 0
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 0
...对于小型集群,可以直接设置全局变量。
注意:当 kubernetes 集群只有一个 node 节点时,无法做到 Pod 转移,因为 Pod 已经无路可退了。