写在开篇
Node Affinity(节点亲和性)允许在节点级别上指定一些条件来控制Pod被调度到哪些节点上。它还有两种策略,本篇通过实战演示如何使用两种策略来控制Pod的调度。
测试环境
还是老样子,本次实战继续使用以下K8S集群环境进行:
节点 | 主机名 | IP |
---|---|---|
Master节点 | k8s-b-master | 192.168.11.100 |
工作节点1 | k8s-b-node01 | 192.168.11.101 |
工作节点2 | k8s-b-node02 | 192.168.11.102 |
工作节点3 | k8s-b-node03 | 192.168.11.103 |
工作节点4 | k8s-b-node04 | 192.168.11.104 |
工作节点5 | k8s-b-node05 | 192.168.11.105 |
工作节点6 | k8s-b-node06 | 192.168.11.106 |
开始实战
在本次实战中,使用一个名为goweb的测试应用程序来演示Node Affinity的使用。goweb是我用Golang开发的简单Web应用程序,用于测试和验证K8S的调度策略。当然了,你也可以自己开发一个类似的应用,然后使用自己的应用来进行本篇的实战内容。
策略1
在这个实战案例中,我将使用requiredDuringSchedulingIgnoredDuringExecution策略,该策略要求Pod只能调度到特定的节点上。 我将创建一个Node Affinity规则,要求goweb应用只能调度到主机名为k8s-b-node03的节点上。
首先,需要创建一个名为goweb-node-affinity.yaml的文件,并将以下内容复制到文件中:
apiVersion: apps/v1
kind: Deployment
metadata:
name: goweb
namespace: goweb-namespace
spec:
replicas: 3
selector:
matchLabels:
app: goweb
template:
metadata:
labels:
app: goweb
spec:
containers:
- name: goweb
image: 192.168.11.253/library/goweb:latest
ports:
- containerPort: 80
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-b-node03
保存文件后,使用以下命令应用goweb应用的Node Affinity规则:
kubectl apply -f goweb-node-affinity.yaml
现在,K8S调度器将只会将goweb应用调度到主机名为k8s-b-node03的节点上。
[root@k8s-b-master ~]# kubectl get pod -n goweb-namespace -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
goweb-5559b4bbf5-5mpd8 1/1 Running 0 64s 10.244.199.138 k8s-b-node03
goweb-5559b4bbf5-lbq4j 1/1 Running 0 63s 10.244.199.139 k8s-b-node03
goweb-5559b4bbf5-lqkzh 1/1 Running 0 70s 10.244.199.137 k8s-b-node03
策略2
在这个实战案例中,我将使用preferredDuringSchedulingIgnoredDuringExecution策略,该策略尽量将Pod调度到满足条件的节点上,但不是强制要求。 我将创建一个Node Affinity规则,优先将goweb应用调度到拥有标签app=goweb-node的节点上。
首先,我创建一个名为goweb-preferred-node-affinity.yaml的文件,并将以下内容复制到文件中:
apiVersion: apps/v1
kind: Deployment
metadata:
name: goweb
namespace: goweb-namespace
spec:
replicas: 3
selector:
matchLabels:
app: goweb
template:
metadata:
labels:
app: goweb
spec:
containers:
- name: goweb
image: 192.168.11.253/library/goweb:latest
ports:
- containerPort: 80
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: app
operator: In
values:
- goweb-node
保存文件后,使用以下命令应用goweb应用的Node Affinity规则:
kubectl apply -f goweb-preferred-node-affinity.yaml
现在,K8S调度器将优先将goweb应用调度到拥有标签app=goweb-node的节点上,但如果没有满足条件的节点,它仍然可以调度到其他节点上。
查看node标签和调度结果:
# 查看label:
[root@k8s-b-master ~]# kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-b-master Ready control-plane 49d v1.25.4 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-b-master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
k8s-b-node01 Ready 49d v1.25.4 app=goweb-node,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-b-node01,kubernetes.io/os=linux
k8s-b-node02 Ready 49d v1.25.4 app=goweb-node,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-b-node02,kubernetes.io/os=linux
...
# 调度结果:
[root@k8s-b-master ~]# kubectl get pod -n goweb-namespace -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
goweb-58799f9b4c-cd5j4 1/1 Running 0 16s 10.244.51.204 k8s-b-node01
goweb-58799f9b4c-hpwlt 1/1 Running 0 16s 10.244.232.147 k8s-b-node02
goweb-58799f9b4c-ksps5 1/1 Running 0 16s 10.244.232.146 k8s-b-node02
删除app标签后再次查看调度结果:
# 删除
kubectl label nodes k8s-b-node01 app-
kubectl label nodes k8s-b-node02 app-
# 调度结果:
[root@k8s-b-master ~]# kubectl get pod -n goweb-namespace -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
goweb-58799f9b4c-5hr8h 1/1 Running 0 2s 10.244.25.77 k8s-b-node05
goweb-58799f9b4c-5vnr9 1/1 Running 0 2s 10.244.232.148 k8s-b-node02
goweb-58799f9b4c-hwxjc 1/1 Running 0 2s 10.244.151.4 k8s-b-node06
注意到了吧?删除app标签后,就没有满足条件的节点了,但它仍然可以调度到其他节点上。通过实战,已经证实了这一点。
使用场景
下面聊聊两种策略在实际工作中可能的使用场景。
1. requiredDuringSchedulingIgnoredDuringExecution
它的特点是:强制要求Pod只能调度到特定节点的策略。可能适用的场景:
- 硬件特性要求:比如对底层硬件有特殊要求的应用,就可以将Pod调度到拥有特定标签货其它属性的节点上。
- 数据本地性要求:比如需要访问特定数据源的应用,将Pod调度到与数据源最近的节点上,这样可以减少网络延迟和提高性能。
- 资源隔离:将特定类型的任务或工作负载隔离到专用的节点上。
举个贴近实际的例子,假设有一组节点,其中几个节点拥有牛逼的CPU和内存资源。希望将一些需要较高计算能力的任务调度到这些节点上。这时候就可以使用此策略来指定这些节点的标签或其他属性,就可以将Pod限制在这些节点上了。
2. preferredDuringSchedulingIgnoredDuringExecution
它的特点是:优先选择满足条件的节点进行调度的策略。可能适用的场景:
- 资源利用率优化:比如希望将Pod调度到拥有特定资源的节点上,但如果没有满足条件的节点,仍然可以将Pod调度到其他节点上。
- 可用性和容错性:为了提高应用程序的可用性和容错性,我们可以将Pod调度到多个节点上,但仍然优先将其调度到特定节点上。如果该节点不可用,调度器将选择次优节点进行调度。
举个例子,假设有一组节点,其中一些节点的网卡连接的是光交换机,网络更快。希望将网络密集型的应用优先调度到这些节点上,这时候就可以指定这些节点的网络特性标签或其他属性。
再举个例子,假设在具有不同地理位置的多个数据中心中部署应用。就可以优先将Pod调度到与应用所服务的区域最近的节点上,以此来达到减少延迟、提高用户体验的效果。
我为什么要用“可能适用的场景”来说明?因为我觉得,看这篇文章的你应该可以想到更好的应用场景,欢迎留言讨论。
最后总结
接下来做个关键性的总结:
- Node Affinity有两种策略:
- requiredDuringSchedulingIgnoredDuringExecution:调度器只有在规则被满足的时候才能执行调度(硬策略)
- preferredDuringSchedulingIgnoredDuringExecution:调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该Pod(软策略)
- 匹配方式有两种:
- matchExpressions:基于标签的匹配,可以包含一个或多个条件,每个条件由键、运算符和值组成,适用于更复杂的标签匹配逻辑的场景。
- matchFields:允许基于节点的字段选择节点,而不是基于标签。比如指定节点上的字段名称和值进行匹配。这对于选择节点的特定属性非常有用,例如节点的操作系统、内存大小等。
- operator属性可能的枚举值:
- "DoesNotExist": 用于检查标签或注解不存在的情况。
- "Exists": 用于检查标签或注解存在的情况。
- "Gt": 用于数字类型的匹配,表示大于指定值。
- "In": 用于匹配给定值列表中的任何一个值。
- "Lt": 用于数字类型的匹配,表示小于指定值。
- "NotIn": 用于匹配不在给定值列表中的任何一个值。
希望本篇的实战能对你理解和使用K8S中的Node Affinity特性有所帮助。你可以根据实际需求和集群环境,灵活应用Node Affinity来优化应用的调度行为。好了,本篇就到此结束,有问题可以评论区留言讨论或者私信讨论。
注重运维实战,我们比谁都拼!日常分享实用干货,助你成为运维大神!探索技术的魅力,从这里开始!
点击链接,畅读精彩文章,从中获取洞见,为自己的技术之旅注入新的动力!关注我的微信公众号,不错过更多精彩内容。
【K8S(专注于深入研究K8S相关的各种技术和知识分享。)】:https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz...
【Go&Py(涵盖了Go和Python两种流行的编程语言。)】:https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz...
【Ops(运维领域的讨论和交流。)】:https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz...