Kubernetes(k8s)亲和性调度
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes(k8s)亲和性调度相关的知识,希望对你有一定的参考价值。
参考技术A 一般情况下我们部署的 Pod 是通过集群的自动调度策略来选择节点的,默认情况下调度器考虑的是资源足够,并且负载尽量平均,但是有的时候我们需要能够更加细粒度的去控制 Pod 的调度,比如我们内部的一些服务 gitlab 之类的也是跑在Kubernetes集群上的,我们就不希望对外的一些服务和内部的服务跑在同一个节点上了,担心内部服务对外部的服务产生影响;但是有的时候我们的服务之间交流比较频繁,又希望能够将这两个服务的 Pod 调度到同一个的节点上。这就需要用到 Kubernetes 里面的一个概念:亲和性和反亲和性。亲和性有分成节点亲和性(nodeAffinity)和 Pod 亲和性(podAffinity)。
在了解亲和性之前,我们先来了解一个非常常用的调度方式:nodeSelector。我们知道label是kubernetes中一个非常重要的概念,用户可以非常灵活的利用 label 来管理集群中的资源,比如最常见的一个就是 service 通过匹配 label 去匹配 Pod 资源,而 Pod 的调度也可以根据节点的 label 来进行调度。
我们可以通过下面的命令查看我们的 node 的 label:
现在我们先给节点node02增加一个com=youdianzhishi的标签,命令如下:
我们可以通过上面的--show-labels参数可以查看上述标签是否生效。当 node 被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在 Pod 的spec字段中添加nodeSelector字段,里面是我们需要被调度的节点的 label 即可。比如,下面的 Pod 我们要强制调度到 node02 这个节点上去,我们就可以使用 nodeSelector 来表示了:(node-selector-demo.yaml)
然后我们可以通过 describe 命令查看调度结果:
可以看到 Events 下面的信息,我们的 Pod 通过默认的 default-scheduler 调度器被绑定到了node02节点。不过需要注意的是nodeSelector属于强制性的,如果我们的目标节点没有可用的资源,我们的 Pod 就会一直处于 Pending 状态,这就是nodeSelector的用法。
通过上面的例子我们可以感受到nodeSelector的方式比较直观,但是还够灵活,控制粒度偏大,接下来我们再和大家了解下更加灵活的方式:节点亲和性(nodeAffinity)。
之前了解了 kubernetes 调度器的一个调度流程,我们知道默认的调度器在使用的时候,经过了 predicates 和 priorities 两个阶段,但是在实际的生产环境中,往往我们需要根据自己的一些实际需求来控制 pod 的调度,这就需要用到 nodeAffinity(节点亲和性)、podAffinity(pod 亲和性) 以及 podAntiAffinity(pod 反亲和性)。
亲和性调度可以分成软策略和硬策略两种方式:
软策略就是如果你没有满足调度要求的节点的话,pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓了的策略
硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不干的策略。
对于亲和性和反亲和性都有这两种规则可以设置: preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution,前面的就是软策略,后面的就是硬策略。
nodeAffinity
节点亲和性主要是用来控制 pod 要部署在哪些主机上,以及不能部署在哪些主机上的。它可以进行一些简单的逻辑组合了,不只是简单的相等匹配。
比如现在我们用一个 Deployment 来管理3个 pod 副本,现在我们来控制下这些 pod 的调度,如下例子:(node-affinity-demo.yaml)
上面这个 pod 首先是要求不能运行在 node03 这个节点上,如果有个节点满足com=youdianzhishi的话就优先调度到这个节点上。
下面是我们测试的节点列表信息:
可以看到 node02 节点有com=youdianzhishi这样的 label,按要求会优先调度到这个节点来的,现在我们来创建这个 pod,然后使用descirbe命令查看具体的调度情况是否满足我们的要求。
从结果可以看出 pod 都被部署到了 node02,其他节点上没有部署 pod,这里的匹配逻辑是 label 的值在某个列表中,现在Kubernetes提供的操作符有下面的几种:
In:label 的值在某个列表中
NotIn:label 的值不在某个列表中
Gt:label 的值大于某个值
Lt:label 的值小于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在
如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件就可以了;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 POD。
pod 亲和性主要解决 pod 可以和哪些 pod 部署在同一个拓扑域中的问题(其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等),而 pod 反亲和性主要是解决 pod 不能和哪些 pod 部署在同一个拓扑域中的问题,它们都是处理的 pod 与 pod 之间的关系,比如一个 pod 在一个节点上了,那么我这个也得在这个节点,或者你这个 pod 在节点上了,那么我就不想和你待在同一个节点上。
由于我们这里只有一个集群,并没有区域或者机房的概念,所以我们这里直接使用主机名来作为拓扑域,把 pod 创建在同一个主机上面。
同样,还是针对上面的资源对象,我们来测试下 pod 的亲和性:(pod-affinity-demo.yaml)
上面这个例子中的 pod 需要调度到某个指定的主机上,至少有一个节点上运行了这样的 pod:这个 pod 有一个app=busybox-pod的 label。
我们查看有标签app=busybox-pod的 pod 列表:
我们看到这个 pod 运行在了 node02 的节点上面,所以按照上面的亲和性来说,上面我们部署的3个 pod 副本也应该运行在 node02 节点上:
如果我们把上面的 test-busybox 和 affinity 这个 Deployment 都删除,然后重新创建 affinity 这个资源,看看能不能正常调度呢:
我们可以看到处于Pending状态了,这是因为现在没有一个节点上面拥有busybox-pod这个 label 的 pod,而上面我们的调度使用的是硬策略,所以就没办法进行调度了,大家可以去尝试下重新将 test-busybox 这个 pod 调度到 node03 这个节点上,看看上面的 affinity 的3个副本会不会也被调度到 node03 这个节点上去?
我们这个地方使用的是kubernetes.io/hostname这个拓扑域,意思就是我们当前调度的 pod 要和目标的 pod 处于同一个主机上面,因为要处于同一个拓扑域下面,为了说明这个问题,我们把拓扑域改成beta.kubernetes.io/os,同样的我们当前调度的 pod 要和目标的 pod 处于同一个拓扑域中,目标的 pod 是不是拥有beta.kubernetes.io/os=linux的标签,而我们这里3个节点都有这样的标签,这也就意味着我们3个节点都在同一个拓扑域中,所以我们这里的 pod 可能会被调度到任何一个节点:
这就是 pod 亲和性的用法,而 pod 反亲和性则是反着来的,比如一个节点上运行了某个 pod,那么我们的 pod 则希望被调度到其他节点上去,同样我们把上面的 podAffinity 直接改成 podAntiAffinity,(pod-antiaffinity-demo.yaml)
这里的意思就是如果一个节点上面有一个app=busybox-pod这样的 pod 的话,那么我们的 pod 就别调度到这个节点上面来,上面我们把app=busybox-pod这个 pod 固定到了 node03 这个节点上面来,所以正常来说我们这里的 pod 不会出现在 node03 节点上:
这就是 pod 反亲和性的用法。
运维实战 容器部分 Kubernetes调度
运维实战 容器部分 Kubernetes调度
需求简介
- 生产环境有对
Pod
调度规划的真实需求, 比如端口冲突的业务不能部署在同一物理机如何实现, 比如某些业务尽可能部署在同一物理机如何实现 - 调度器通过
K8S
的watch
机制来发现集群中新创建且尚未被调度到Node
上的Pod
. 调度器会将发现的每一个未调度的Pod
调度到一个合适的Node
上来运行. - 集群默认使用
kube-scheduler
作为调度器, 可以自行编写调度组件替换原有的kube-scheduler
- 也可以在资源清单中指定使用的调度策略
- 做调度分配时需要考虑许多要素, 如: 单独和整体的资源请求, 硬件/软件/策略限制, 亲和以及反亲和要求, 数据局限性, 负载间的干扰等等
具体调度方法
nodeName
nodeName
是最简单的节点约束方法, 通常来说不推荐使用- 通过在资源清单中指定
Node
,Pod
会被自动部署到该Node
上, 但如果Node
不存在, 集群也会尝试这么做, 因而导致Pending
局限性
-
指定的节点不存在时不会只能解决
-
节点存在, 但不满足物理需求(如资源/空间不足), 调度也会失败
-
在生产环境中
Node
的名称并不总是稳定/可预测的 -
nodeName.yaml
文件内容
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: server3
- 测试结果
[root@Server2 Schedule]# kubectl apply -f nodeName.yaml
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 10s 10.244.141.226 server3 <none> <none>
- 如果集群中没有符合条件的
Node
, 比如上面的server3
改成server10
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: server10
- 则会出现调度失败, 并不会只能解决
- 因为集群中不存在
server10
而你又要求调度到server10
上, 就会一直Pending
[root@Server2 Schedule]# kubectl delete Pod nginx
Pod "nginx" deleted
[root@Server2 Schedule]# kubectl get Pod -o wide
No resources found in default namespace.
[root@Server2 Schedule]# kubectl apply -f nodeName.yaml
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 0/1 Pending 0 1s <none> server10 <none> <none>
nodeSelector
想必你也注意到了, nodeName
并不能指定一类机器, 而只能指定固定机器
如果我们想定义一类具有同样特点的机器, 如这些机器都使用固态硬盘, 可以通过对节点打tag
的方式来实现
与之对应, nodeSelector
是通过tag
进行调度筛选的机制, 也是节点选择约束的最简单推荐形式
nodeSelector.yaml
文件内容
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
- 测试流程
[root@Server2 Schedule]# kubectl apply -f nodeSelector.yaml
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 0/1 Pending 0 9s <none> <none> <none> <none>
- 此时, 所有结点上都没有
disktype: ssd
标签所以Pending
- 对
server3
附加disktype=ssd
的标签后, 调度正确实现
[root@Server2 Schedule]# kubectl label nodes server3 disktype=ssd
node/server3 labeled
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 67s 10.244.141.227 server3 <none> <none>
亲和与反亲和
- 实现了 “硬要求” 和 “软需求” 并存, 如"必须满足A, 尽量满足B(B不满足也可以)"这类需求, 这极大扩展了表达约束的类型
- 亲和和反亲和不止支持节点标签, 也支持
Pod
标签, 如B服务
只能放在A服务
不存在的Node
上这种需求
节点亲和
参数 | 含义 |
---|---|
requiredDuringSchedulingIgnoredDuringExecution | 必须满足 |
preferredDuringSchedulingIgnoredDuringExecution | 倾向满足 |
虽然这个参数很长, 但实际上是由两部分构成的, 前半部分表示软/硬
后半部分的IgnoreDuringExecution
表示如果在Pod
运行期间Node
的标签发生变化, 导致亲和性策略不能满足, 则继续运行当前的Pod
, 而不直接驱离
nodeaffinity
还支持多种规则匹配条件的配置
参数 | 含义 |
---|---|
In | label 的值在列表内 |
NotIn | label 的值不在列表内 |
Gt | label 的值大于设置的值, 不支持Pod 亲和性 |
Lt | label 的值小于设置的值, 不支持Pod 亲和性 |
Exists | 设置的label 存在 |
DoesNotExist | 设置的label 不存在 |
- 测试用
node-affinity.yaml
文件内容
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
spec:
containers:
- name: nginx
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
-
这里的需求是 满足
disktype=ssd
的节点,key
+operator
+values
共同完成了这一指名 -
测试流程, 可以看到测试用的
node-affinity
被调度到了server3
上
[root@Server2 Schedule]# kubectl apply -f nodeAffinity_required.yaml
Pod/node-affinity created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-affinity 1/1 Running 0 5s 10.244.141.228 server3 <none> <none>
倾向满足
的案例
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
spec:
containers:
- name: nginx
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- server3
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
在这个资源清单中存在两条要求:
- 必须满足 不能调度到
server3
上 - 倾向满足
Node
包含disktype=ssd
的tag
这样, 即使server4
上并没有这个tag
, 也可以调度到server4
上了
[root@Server2 Schedule]# kubectl apply -f nodeAffinity_preferred.yaml Pod/node-affinity created[root@Server2 Schedule]# kubectl get Pod -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESnode-affinity 1/1 Running 0 27s 10.244.22.24 server4 <none> <none>
Pod亲和
PodAffinity
主要解决Pod
可以和哪些Pod
部署在同一个拓扑域中的问题(拓扑域用主机标签实现, 可以是单个主机, 也可以是多个主机组成的cluster
,zone
等.) 与之效能相反的PodAntiAffinity
主要解决Pod
不能和哪些Pod
部署在同一个拓扑域中的问题. 它们处理的是Kubernetes
集群内部Pod
和Pod
之间的关系.Pod
间亲和与反亲和在与更高级别的集合(例如ReplicaSets
,StatefulSets
,Deployments
等)一起使用时, 它们可能更加有用. 可以轻松配置一组应位于相同定义拓扑(例如, 节点)中的工作负载.Pod
间亲和与反亲和需要大量的处理, 这可能会显著减慢大规模集群中的调度. \\
测试样例
- 创建一个自主的包含
nginx
服务的Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
- 创建一个包含
Pod
亲和需求的资源清单
apiVersion: v1
kind: Pod
metadata:
name: mysql
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql
env:
- name: "MYSQL_ROOT_PASSWORD"
value: "westos"
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
[root@Server2 Schedule]# kubectl apply -f nodeName.yaml
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESnginx 1/1 Running 0 11s 10.244.141.230 server3 <none> <none>
[root@Server2 Schedule]# kubectl apply -f PodAffinity.yaml
Pod/demo created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdemo 1/1 Running 0 6s 10.244.22.25 server4 <none> <none> nginx
1/1 Running 0 54s 10.244.141.230 server3 <none> <none>
可以看到, 两个Pod
分别被调度到了2个不同的Node
Taints
NodeAffinity
节点亲和性的目的是让Pod
可以按照我们的需求调度到一个或一类Node
上;
Taints
与之相反, 它能让Node
拒绝运行Pod
, 甚至驱离已经在该Node
上运行的Pod
Taints
(污点)是Node
的一个属性, 设置了Taints
后, K8S
集群就不会把Pod
调度到这个Node
上了
如果想让Pod
调度到有Taints
的节点, 就需要给Pod
设置Tolerations
(容忍)属性
主节点上天生具有Taints
, 因此才有了默认不会调度到主节点上的说法
- 相关命令
##为node1增加一个NoSchedule(不允许调度)的污点
kubectl taint nodes node1 key=value:NoSchedule
##查询server1上的污点情况
kubectl describe nodes server1 |grep Taints
##删除node1上的NoSchedule(不允许调度)污点属性
$ kubectl taint nodes node1 key:NoSchedule-
- 一个标准的
Deployment
控制器部署nginx
的清单
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
- 主节点默认不允许调度
[root@Server2 Schedule]# kubectl describe nodes server2 |grep TaintsTaints:
node-role.kubernetes.io/master:NoSchedule
- 为Server4增加驱离属性
[root@Server2 Schedule]# kubectl taint node server4 key1=v1:NoExecute
node/server4 tainted
[root@Server2 Schedule]# kubectl get Pod -o wide
-
如果设置正确, 你将只会在
server3
上看到Pod
, 因为server2
和server4
都不能用于部署 -
增加容忍设置的版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "key1"
operator: "Equal"
value: "v1"
effect: "NoExecute"
Taints设置的取值
参数 | 含义 |
---|---|
NoSchedule | 不会被调度到标记节点 |
PreferNoSchedule | NoSchedule 的软策略版本 |
NoExecute | 不会调度到标记节点, 并且该节点内的Pod 如无对应的Tolerate 设置还会被驱逐 |
Tolerations相关设置
Tolerations
中定义的key
, value
, effect
, 要与node
上设置的taint
保持一致, 但并不是都要填写
- 当
operator
为Exists
时, 可以省略value
- 当
operator
为Equal
时,key
与value
之间的关系必须相等 - 如果不指定
operator
属性, 则默认值为Equal
- 当不指定
key
, 再配合Exists
就能匹配所有的key
与value
, 可以容忍所有污点 - 当不指定
effect
, 则匹配所有的effect
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
对满足key=value 且污点策略为NoSchedule的节点进行容忍
tolerations:
- operator: "Exists"
effect: "NoSchedule"
容忍所有污点并不驱离
以上是关于Kubernetes(k8s)亲和性调度的主要内容,如果未能解决你的问题,请参考以下文章