- 目录
- 实验环境
- 实验软件
- 本节实践
- 调度
- 创建一个Pod的工作流程
- 1.架构图
- 2.剖析过程
- 3.Pod中影响调度的主要属性
- 4.调度器需要充分考虑诸多的因素
- 5.Kubernetes中的资源分配
- 6.Init container的资源需求
- 1、nodeSelector
- 2、亲和性和反亲和性调度
- 1.节点亲和性
- 2.pod 亲和性和pod 反亲和性
- 3、污点与容忍
- 4、nodeName
- 思考
- 1.不用 DaemonSet,如何使用 Deployment 是否实现同样的功能?
- 2.同样的如果想在每个节点(或指定的一些节点)上运行2个(或多个)Pod 副本,如何实现?
- 关于我
- 最后
实验环境: 1、win10,vmwrokstation虚机; 2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点 k8s version:v1.22.2 containerd://1.5.5实验软件
链接:https://pan.baidu.com/s/1P3Z_ujk22dYDXzM37WI5FA?pwd=v01w
提取码:v01w
2022.2.18-39.亲合性调度-实验代码.zip
本节实践- 实践:nodeSelector测试(测试成功)-2022.5.16
- 实践:节点亲和性测试(测试成功)-2022.5.16
- 实践:pod亲和性(测试成功)-2022.5.16
- 实践:pod反亲和性(测试成功)-2022.5.16
- 实践:污点与容忍(测试成功)-2022.5.16
- 实践:nodeName测试(测试成功)-2022.5.16
一般情况下我们部署的 Pod 是通过集群的自动调度策略来选择节点的,默认情况下调度器考虑的是资源足够,并且负载尽量平均。但是有的时候我们需要能够更加细粒度的去控制 Pod 的调度,比如我们希望一些机器学习的应用只跑在有 GPU 的节点上;但是有的时候我们的服务之间交流比较频繁,又希望能够将这服务的 Pod 都调度到同一个的节点上。这就需要使用一些调度方式来控制 Pod 的调度了,主要有两个概念:亲和性和反亲和性,亲和性又分成节点亲和性(nodeAffinity)和 Pod 亲和性(podAffinity)。
创建一个Pod的工作流程 1.架构图Kubernetes基于list-watch机制的控制器架构,实现组件间交互的解耦。
其他组件监控自己负责的资源,当这些资源发生变化时,kube-apiserver会通知这些组件,这个过程类似于发布与订阅。
- 我们通过命令行创建一个pod:
- 当我们在执行命令kubectl run pod4 --image=nginx后,各组件之间的调用流程是如何的呢?
1、kubectl向apiserver发送一个创建pod的请求 2、apiserver接收到并向etcd写入存储,写入成功返回一个提示 #这个过程类似于老板和顾客之间的关系: api-server:开店老板 etcd:仓库 其他组件:顾客
- 注意:如果在执行如下kubectl run pod4 --image=nginx命令时,卡着了,说明什么问题呢?
–>说明:
etcd数据库写入有问题/达到性能瓶颈,或者,api-server和etcd 2者总有一个有问题:
- 继续:scheduler向apiserver查询未分配的pod资源,通过自身调度算法选择一个合适的node进行绑定(给这个pod资源打一个标记,标记分配到node1)注意:它这个调度算法还是比较复杂、均匀一点的,它会考虑到你的机器的硬件配置,pod属性等等一些综合的属性;
- 问题:如果scheduler组件有问题,那么此时pod会出现什么状态?
答:pod会出现通过kubectl get pod根本看不到你刚创建的pod信息的,更别说它的状态了,因为它根本没分配。
因此,如果你创建的pod信息根本看不到,那么会是哪个组件可能有问题?–>scheduler组件可能出在问题。
-
如果是pending状态:pod是已经绑定到某个节点了。
-
继续流程讲解:
4、kubelet向apiserver查询分配到自己节点的pod,调用docker api(/var/run/docker.sock)创建容器 5、kubelet获取docker创建容器的状态,并汇报给apiserver,apiserver更新状态到etcd存储 6、kubectl get pods就能查看pod状态 备注: kubelet的功能主要是管理容器: 这个是默认调用的docker api接口 [root@k8s-master ~]#ll /var/run/docker.sock srw-rw---- 1 root docker 0 Jun 14 11:46 /var/run/docker.sock
- 问题:controller-manager为什么没用到?
controller-manager是用于管理控制器,例如deployment(rs)、service,因为创建的是一个pod,不受它管理。
如果controller-manager要放在这里,一般是放在etcd后面的:
- kube-proxy为什么没用到?
proxy是用于管理pod网络,例如service,因为没创建service。
kube-proxy的主要功能就是维护好service,service是k8s的抽象资源;
- 扩展
比如,这个容器创建的时候创建失败了,不是一个running状态,也不是一个pending状态。可能就是docker在启动容器时,用你那个镜像启动容器失败了。所以这是你需要用docker去run一个镜像看能不能起来。
3.Pod中影响调度的主要属性⚠️ 注意
resources: {} 资源调度依据这个,挺重要的;
很多大厂都会去二开"schedulerName: default-scheduler"这个调度器的,会去加一些调度策略,进而完成他们的需求;
调度原因失败分析
kubectl get pod4.调度器需要充分考虑诸多的因素-o wide 查看调度失败原因:kubectl describe pod • 节点CPU/内存不足 • 有污点,没容忍 (tolerations) • 没有匹配到节点标签 (n)
资源高效利用:装箱率要高!
afinity:微服务,分步式系统,网络调用,本机调用,排除了网络调用,额外的传输时间,物理网卡带宽限制!
anti-affinity:某个业务的不同副本,不能让其跑在一台机器上,一个机架上,一个地域里,使其分布在不同的故障域。
locality:数据本地化,是一个很重要的概念,哪里有数据,我的作业就去哪里,这样可以减少数据拷贝的开销。k8s里的拉取镜像。
听起来很简单,但这个过程中会涉及Predicate、Priority等各种调度算法,还有优先级(Priority )、**抢占(Preemption)**等各种机制。在实际的调度设计中,有非常多需要考虑的问题,比如:
5.Kubernetes中的资源分配1. 公平:如何保证每个节点都能被分配资源
2. 资源高效利用:怎样压榨集群的资源能力,让资源被最大化使用
3. 效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
4. 灵活:允许用户根据自己的需求控制调度的逻辑
1.limits:在Cgroups里使用;cpu.cfs_quota/cpu.cfs_period(10w)=1
2.requests:cpu这个requests其实在Cgroup里也起作用。当你多个应用发生资源抢占时,他们抢占的cpu时间比较是多少呢?是通过cpu.share去调节的。k8s是如何实现的呢?这里如果设置的是一个cpu,request是1的话,那么cpu.share是1024。 如果你设置的是100m,相当于是0.1个cpu,那么cpu.share就是0.1*1024=102. 也就是cpu.requests也是最终会体现到Cgroups里面去的。
nodeSelector:用于将Pod调度到匹配Label的Node上,如果没有匹配的标签会调度失败。
作用:
- 约束Pod到特定的节点运行
- 完全匹配节点标签
应用场景:
- 专用节点:根据业务线将Node分组管理
- 配备特殊硬件:部分Node配有SSD硬盘、GPU
| 实践:nodeSelector测试(测试成功)-2022.5.16 |
在了解亲和性之前,我们先来了解一个非常常用的调度方式:nodeSelector。我们知道 label 标签是 kubernetes 中一个非常重要的概念,用户可以非常灵活的利用 label 来管理集群中的资源,比如最常见的 Service 对象通过 label 去匹配 Pod 资源,而 Pod 的调度也可以根据节点的 label 来进行调度。
- 我们可以通过下面的命令查看我们的 node 的 label:
[root@master1 ~]#kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS master1 Ready control-plane,master 109d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers= node1 Ready109d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node1,kubernetes.io/os=linux node2 Ready 109d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux
- 现在我们先给节点 node2 增加一个com=youdianzhishi的标签,命令如下:
[root@master1 ~]#kubectl label nodes node2 com=youdianzhishi node/node2 labeled
我们可以通过上面的 --show-labels 参数可以查看上述标签是否生效。
[root@master1 ~]#kubectl get node node2 --show-labels NAME STATUS ROLES AGE VERSION LABELS node2 Ready109d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,com=youdianzhishi,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux
- Pod里配置nodeSelector字段:
当节点被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在 Pod 的 spec 字段中添加 nodeSelector 字段,里面是我们需要被调度的节点的 label 标签,比如,下面的 Pod 我们要强制调度到 node2 这个节点上去,我们就可以使用 nodeSelector 来表示了:
$ vim 01-node-selector-demo.yaml
# 01-node-selector-demo.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox-pod
name: test-busybox
spec:
containers:
- command:
- sleep
- "3600"
image: busybox
imagePullPolicy: Always
name: test-busybox
nodeSelector: #注意:nodeSelector是和containers同级的;注意,这个放的顺序一定要放在containers后面。不然会报错的!
com: youdianzhishi
- 部署后,我们就可以通过 describe 命令查看调度结果:
hg@LAPTOP-G8TUFE0T:/mnt/c/Users/hg/Desktop/yaml$ kubectl apply -f 01-node-selector-demo.yaml pod/test-busybox created hg@LAPTOP-G8TUFE0T:/mnt/c/Users/hg/Desktop/yaml$ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test-busybox 1/1 Running 0 78s 10.244.2.210 node2[root@master1 ~]#kubectl describe po test-busybox Name: test-busybox Namespace: default Priority: 0 Node: node2/172.29.9.53 Start Time: Thu, 17 Feb 2022 19:45:11 +0800 Labels: app=busybox-pod Annotations: Status: Running IP: 10.244.2.210 IPs: IP: 10.244.2.210 Containers: test-busybox: Container ID: containerd://1b4d323942e6d305a4ea25f655eaced77f8cb8e4229eaf1972dc9dfb1246a0c0 Image: busybox Image ID: docker.io/library/busybox@sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678 Port: Host Port: Command: sleep 3600 State: Running Started: Thu, 17 Feb 2022 19:45:31 +0800 Ready: True Restart Count: 0 Environment: Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-p5z6t (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: kube-api-access-p5z6t: Type: Projected (a volume that contains injected data from multiple sources) TokenExpirationSeconds: 3607 ConfigMapName: kube-root-ca.crt ConfigMapOptional: DownwardAPI: true QoS Class: BestEffort Node-Selectors: com=youdianzhishi Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 2m45s default-scheduler Successfully assigned default/test-busybox to node2 Normal Pulling 2m43s kubelet Pulling image "busybox" Normal Pulled 2m26s kubelet Successfully pulled image "busybox" in 17.583571931s Normal Created 2m26s kubelet Created container test-busybox Normal Started 2m25s kubelet Started container test-busybox [root@master1 ~]#
我们可以看到 Events 下面的信息,我们的 Pod 通过默认的 default-scheduler 调度器被绑定到了 node2 节点。不过需要注意的是nodeSelector 属于强制性的,如果我们的目标节点没有可用的资源,我们的 Pod 就会一直处于 Pending 状态。
通过上面的例子我们可以感受到 nodeSelector 的方式比较直观,但是还不够灵活,控制粒度偏大。接下来我们再和大家了解下更加灵活的方式:节点亲和性(nodeAffinity)。
测试结束。
问题:
因pod中nodeSelector里的标签未出现在all node节点,但后续给node打好符合要求的标签,原来处于pending状态的pod会自动迁移过去的吗?–>会的。
2、亲和性和反亲和性调度前面我们了解了 kubernetes 调度器的调度流程,我们知道默认的调度器在使用的时候,经过了 predicates 和 priorities 两个阶段,但是在实际的生产环境中,往往我们需要根据自己的一些实际需求来控制 Pod 的调度,这就需要用到 nodeAffinity(节点亲和性)、podAffinity(pod 亲和性) 以及 podAntiAffinity(pod 反亲和性)。
亲和性调度可以分成软策略和硬策略两种方式:
- 软策略就是如果现在没有满足调度要求的节点的话,Pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓
- 硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然就不干了
对于亲和性和反亲和性都有这两种规则可以设置: preferredDuringSchedulingIgnoredDuringExecution 和requiredDuringSchedulingIgnoredDuringExecution,前面的就是软策略,后面的就是硬策略。
1.节点亲和性节点亲和性(nodeAffinity)主要是用来控制 Pod 要部署在哪些节点上,以及不能部署在哪些节点上的,它可以进行一些简单的逻辑组合了,不只是简单的相等匹配(比如前面的nodeSelector就是标签的=)。
nodeAffinity:节点亲和类似于nodeSelector,可以根据节点上的标签来约束Pod可以调度到哪些节点。
相比nodeSelector:
- 匹配有更多的逻辑组合,不只是字符串的完全相等,支持的操作符有:In、NotIn、Exists、DoesNotExist、Gt、Lt
- 调度分为软策略和硬策略,而不是硬性要求
- 硬(required):必须满足
- 软(preferred):尝试满足,但不保证
这里的匹配逻辑是 label 标签的值在某个列表中,现在 Kubernetes 提供的操作符有下面的几种:
- In:label 的值在某个列表中 (这里的操作符,我们一般只用到in就足够了;)
- NotIn:label 的值不在某个列表中
- Gt:label 的值大于某个值
- Lt:label 的值小于某个值
- Exists:某个 label 存在
- DoesNotExist:某个 label 不存在
⚠️ 注意:
但是需要注意的是如果 nodeSelectorTerms 下面有多个选项的话,满足任何一个条件就可以了;如果 matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 Pod。
比nodeSelector更高级的一个!
matchExpressions比selector更加灵活。
| 实践:节点亲和性测试(测试成功)-2022.5.16 |
- 比如现在我们用一个 Deployment 来管理8个 Pod 副本,现在我们来控制下这些 Pod 的调度,如下例子:
# 02-node-affinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-affinity
labels:
app: node-affinity
spec:
replicas: 8
selector:
matchLabels:
app: node-affinity
template:
metadata:
labels:
app: node-affinity
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
name: nginxweb
affinity: #定义亲和性
nodeAffinity: #节点亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- master1 #相当于只能调度到node1和node2节点。默认就不会调度到master1节点
preferredDuringSchedulingIgnoredDuringExecution: # 软策略(尽可能调度到node2节点)
- weight: 1
preference:
matchExpressions:
- key: com
operator: In
values:
- youdianzhishi
上面这个 Pod 首先是要求不能运行在 master1 这个节点上,如果有个节点满足 com=youdianzhishi 的话就优先调度到这个节点上。
由于上面 node02 节点我们打上了 com=youdianzhishi 这样的 label 标签,所以按要求会优先调度到这个节点来的。
- 现在我们来创建这个 Pod,然后查看具体的调度情况是否满足我们的要求。
➜ kubectl apply -f node-affinty-demo.yaml deployment.apps/node-affinity created ➜ kubectl get pods -l app=node-affinity -o wide #老师这个有部分pod被调度到node1节点 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES node-affinity-cdd9d54d9-bgbbh 1/1 Running 0 2m28s 10.244.2.247 node2node-affinity-cdd9d54d9-dlbck 1/1 Running 0 2m28s 10.244.4.16 node1 node-affinity-cdd9d54d9-g2jr6 1/1 Running 0 2m28s 10.244.4.17 node1 node-affinity-cdd9d54d9-gzr58 1/1 Running 0 2m28s 10.244.1.118 node1 node-affinity-cdd9d54d9-hcv7r 1/1 Running 0 2m28s 10.244.2.246 node2 node-affinity-cdd9d54d9-kvxw4 1/1 Running 0 2m28s 10.244.2.245 node2 node-affinity-cdd9d54d9-p4mmk 1/1 Running 0 2m28s 10.244.2.244 node2 node-affinity-cdd9d54d9-t5mff 1/1 Running 0 2m28s 10.244.1.117 node2
从结果可以看出有5个 Pod 被部署到了 node2 节点上,但是可以看到并没有一个 Pod 被部署到 master1 这个节点上,因为我们的硬策略就是不允许部署到该节点上,而 node2 是软策略,所以会尽量满足。
测试结束。
2.pod 亲和性和pod 反亲和性Pod 亲和性(podAffinity)主要解决 Pod 可以和哪些 Pod 部署在同一个拓扑域中的问题(其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等),而 Pod 反亲和性主要是解决 Pod 不能和哪些 Pod 部署在同一个拓扑域中的问题,它们都是处理的 Pod 与 Pod 之间的关系。比如一个 Pod 在一个节点上了,那么我这个也得在这个节点,或者你这个 Pod 在节点上了,那么我就不想和你待在同一个节点上。
这个是很重要的,线上业务基本要配置这种podAntiAffinity。
拓扑域
这里要重点理解下什么是拓扑域?–>你可以把它看成为一个分组。
| 实验:pod亲和性(测试成功)-2022.5.16 |
- 由于我们这里只有一个集群,并没有区域或者机房的概念,所以我们这里直接使用主机名来作为拓扑域,把 Pod 创建在同一个主机上面。
[root@master1 ~]# kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS master1 Ready control-plane,master 110d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers= node1 Ready110d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node1,kubernetes.io/os=linux node2 Ready 110d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,com=youdianzhishi,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux
- 同样,还是针对上面的资源对象,我们来测试下 Pod 的亲和性:
# 03-pod-affinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: nginxweb
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector: #去选择具有app in ["busybox-pod"]的pod所在的hostname这个域。
matchExpressions:
- key: app
operator: In
values:
- busybox-pod
topologyKey: kubernetes.io/hostname
上面这个例子中的 Pod 需要调度到某个指定的节点上,并且该节点上运行了一个带有 app=busybox-pod 标签的 Pod。我们可以查看有标签 app=busybox-pod 的 pod 列表:
[root@master1 ~]#kubectl get pods -l app=busybox-pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test-busybox 1/1 Running 5 (23m ago) 18h 10.244.2.210 node2
- 我们看到这个 Pod 运行在了 node2 的节点上面,所以按照上面的亲和性来说,上面我们部署的3个 Pod 副本也应该运行在 node2 节点上:
$ kubectl apply -f 03-pod-affinity-demo.yaml deployment.apps/pod-affinity created $ kubectl get pods -o wide -l app=pod-affinity NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-affinity-785f687c5-52t54 1/1 Running 0 60s 10.244.2.228 node2pod-affinity-785f687c5-g594p 1/1 Running 0 60s 10.244.2.229 node2 pod-affinity-785f687c5-s6j7h 1/1 Running 0 60s 10.244.2.227 node2
- 如果我们把上面的 test-busybox 和 pod-affinity 这个 Deployment 都删除,然后重新创建 pod-affinity 这个资源,看看能不能正常调度呢:
$ kubectl delete -f 01-node-selector-demo.yaml pod "test-busybox" deleted $ kubectl delete -f 03-pod-affinity-demo.yaml deployment.apps "pod-affinity" deleted $ kubectl apply -f 03-pod-affinity-demo.yaml deployment.apps/pod-affinity created $ kubectl get po NAME READY STATUS RESTARTS AGE pod-affinity-785f687c5-2256q 0/1 Pending 0 80s pod-affinity-785f687c5-7gpz5 0/1 Pending 0 80s pod-affinity-785f687c5-97gpj 0/1 Pending 0 80s
我们可以看到都处于 Pending 状态了,这是因为现在没有一个节点上面拥有 app=busybox-pod 这个标签的 Pod,而上面我们的调度使用的是硬策略,所以就没办法进行调度了,大家可以去尝试下重新将 test-busybox 这个 Pod 调度到其他节点上,观察下上面的3个副本会不会也被调度到对应的节点上去。(这里可以自己测试下,可以利用node1的kubernetes.io/hostname: node1标签用nodeSelector来实现)
- 我们这个地方使用的是 kubernetes.io/hostname 这个拓扑域,意思就是我们当前调度的 Pod 要和目标的 Pod 处于同一个主机上面,因为要处于同一个拓扑域下面。为了说明这个问题,我们把拓扑域改成 beta.kubernetes.io/os,同样的我们当前调度的 Pod 要和目标的 Pod 处于同一个拓扑域中,目标的 Pod 是拥有 beta.kubernetes.io/os=linux 的标签,而我们这里所有节点都有这样的标签,这也就意味着我们所有节点都在同一个拓扑域中,所以我们这里的 Pod 可以被调度到任何一个节点,重新运行上面的 app=busybox-pod 的 Pod,然后再更新下我们这里的资源对象:
$ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-affinity-6bf5bb4fc4-j6ctw 1/1 Running 0 22s 10.244.2.230 node2pod-affinity-6bf5bb4fc4-xb7tr 1/1 Running 0 22s 10.244.2.231 node2 pod-affinity-6bf5bb4fc4-xl6pn 1/1 Running 0 22s 10.244.1.97 node1
可以看到现在是分别运行在2个节点下面的,因为他们都属于 beta.kubernetes.io/os 这个拓扑域(而busybox-pod也刚好在这个域下,因此符合硬策略要求)。
⚠️ 这里需要注意下:通过上面这个实验可以看到,这2个node节点都属于beta.kubernetes.io/os这个拓扑域,但只有node1上有app=pod-affitity这个标签的pod,从结果可以看到也是可以调度到node2上的。
实验结束。
pod 反亲和性(podAntiAffinity)
Pod 反亲和性(podAntiAffinity)则是反着来的,比如一个节点上运行了某个 Pod,那么我们的模板 Pod 则不希望被调度到这个节点上面去了。
| 实验:pod反亲和性(测试成功)-2022.5.16 |
- 我们把上面的 podAffinity 直接改成 podAntiAffinity:
# 04-pod-antiaffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-antiaffinity
labels:
app: pod-antiaffinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-antiaffinity
template:
metadata:
labels:
app: pod-antiaffinity
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: nginxweb
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector: #3个pod副本不会调度到具有app=busybox-pod所在的hostanme这个域(节点)上面
matchExpressions:
- key: app
operator: In
values:
- busybox-pod
topologyKey: kubernetes.io/hostname #注意:pod反亲和性是直接不往这个域里直接调度pod的!!!
这里的意思就是如果一个节点上面有一个 app=busybox-pod 这样的 Pod 的话,那么我们的 Pod 就别调度到这个节点上面来,上面我们把app=busybox-pod 这个 Pod 固定到了 node2 这个节点上面的,所以正常来说我们这里的 Pod 不会出现在该节点上:
$ kubectl apply -f 04-pod-antiaffinity-demo.yaml deployment.apps/pod-antiaffinity created $ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-antiaffinity-57c57dd9f7-jspkt 1/1 Running 0 25s 10.244.1.100 node1pod-antiaffinity-57c57dd9f7-mm78w 1/1 Running 0 25s 10.244.1.99 node1 pod-antiaffinity-57c57dd9f7-x9mft 1/1 Running 0 25s 10.244.1.98 node1
我们可以看到没有被调度到 node2 节点上,因为我们这里使用的是 Pod 反亲和性。
- 大家可以思考下,如果这里我们将拓扑域更改成 beta.kubernetes.io/os 会怎么样呢?可以自己去测试下看看。
$ kubectl apply -f 04-pod-antiaffinity-demo.yaml deployment.apps/pod-antiaffinity created $ kubectl get po NAME READY STATUS RESTARTS AGE pod-antiaffinity-c5fb4db4d-jj5j7 0/1 Pending 0 4s pod-antiaffinity-c5fb4db4d-xxnhg 0/1 Pending 0 4s pod-antiaffinity-c5fb4db4d-zkbgp 0/1 Pending 0 4s
实验结束。
3、污点与容忍Taint(污点)与Tolerations(污点容忍)
Taints:避免Pod调度到特定Node上
Tolerations:允许Pod调度到持有Taints的Node上
应用场景:
- 专用节点:根据业务线将Node分组管理,希望在默认情况下不调度该节点,只有配置了污点容忍才允许分配
- 配备特殊硬件:部分Node配有SSD硬盘、GPU,希望在默认情况下不调度该节点,只有配置了污点容忍才允许分配
- 基于Taint的驱逐
对于 nodeAffinity 无论是硬策略还是软策略方式,都是调度 Pod 到预期节点上。而污点(Taints)恰好与之相反,如果一个节点标记为 Taints ,除非 Pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 Pod。
比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 Pod,则污点就很有用了,Pod 不会再被调度到 taint 标记过的节点。我们使用 kubeadm 搭建的集群默认就给 master 节点添加了一个污点标记,所以我们看到我们平时的 Pod 都没有被调度到 master 上去。
⚠️ 污点:其实是一个label标签,只不过它是一个特殊的label标签。
[root@master1 ~]#kubectl get node master1 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master1 Ready control-plane,master 110d v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=(注意,这个是一个空标签),node.kubernetes.io/exclude-from-external-load-balancers=
[root@master1 ~]#kubectl describe node master1
Name: master1
Roles: master
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=master1
kubernetes.io/os=linux
node-role.kubernetes.io/master=
......
Taints: node-role.kubernetes.io/master:NoSchedule
Unschedulable: false
......
我们可以使用上面的命令查看 master 节点的信息,其中有一条关于 Taints 的信息:node-role.kubernetes.io/master:NoSchedule,就表示master 节点打了一个污点的标记,其中影响的参数是 NoSchedule,表示 Pod 不会被调度到标记为 taints 的节点。除了 NoSchedule 外,还有另外两个选项:
- PreferNoSchedule:NoSchedule 的软策略版本,表示尽量不调度到污点节点上去
- NoExecute:该选项意味着一旦 Taint 生效(被打上taint),如该节点内正在运行的 Pod 没有对应容忍(Tolerate)设置,则会直接被逐出。 哈哈这个命令也是够狠。。。kubectl taint node k8s-node1 disktype=ssd:NoExecute
污点 taint 标记节点的命令如下:
➜ kubectl taint nodes node2 test=node2:NoSchedule node "node2" tainted
上面的命名将 node2 节点标记为了污点,影响策略是 NoSchedule,只会影响新的 Pod 调度,如果仍然希望某个 Pod 调度到 taint 节点上,则必须在 Spec 中做出 Toleration 定义,才能调度到该节点。
最后如果我们要取消节点的污点标记,可以使用下面的命令:
➜ kubectl taint nodes node2 test- node "node2" untainted
| 实践:污点与容忍(测试成功)-2022.5.16 |
- 比如现在我们想要将一个 Pod 调度到 master 节点:
# 05-taint-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: taint
labels:
app: taint
spec:
replicas: 3
selector:
matchLabels:
app: taint
template:
metadata:
labels:
app: taint
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
由于 master 节点被标记为了污点,所以我们这里要想 Pod 能够调度到改节点去,就需要增加容忍的声明:
tolerations: - key: "node-role.kubernetes.io/master" operator: "Exists" effect: "NoSchedule"
- 然后创建上面的资源,查看结果:
➜ kubectl apply -f taint-demo.yaml deployment.apps "taint" created ➜ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... taint-845d8bb4fb-57mhm 1/1 Running 0 1m 10.244.4.247 node2 taint-845d8bb4fb-bbvmp 1/1 Running 0 1m 10.244.0.33 master1 taint-845d8bb4fb-zb78x 1/1 Running 0 1m 10.244.4.246 node2 ......
我们可以看到有一个 Pod 副本被调度到了 master 节点,这就是容忍的使用方法。
那么问题来了:我这个pod可以调度到没打这个污点的node上去吗?还是说会优先调度到这个打了污点的节点上去呢?。。。
–>经测试:是可以调度上去的。pod配置了容忍之后,原来打了污点的node就可以和其他节点一样去负载pod了,具体调度策略是看调度器的。
- 最后如果我们要取消节点的污点标记,可以使用下面的命令:
➜ kubectl taint nodes node2 test- node "node2" untainted
实验结束。
Taints和Tolerations用于保证Pod 不被调度到不合适的Node上,其中Taint应用于Node上,而Toleration则应用于Pod上。
nodeSelector/nodeAffinity与Taint/Tolerations区别
nodeSelector/nodeAffinity--一种主观意识,我要分配在哪个node节点上去; 而Taint/Tolerations:是一种排斥行为,在node上排斥pod分配到自己身上; nodeSelector/nodeAffinity--管理员强迫node taint/toleration--node主动的一脸嫌弃
k8s的自愈能力/故障转移能力:–>污点和容忍!
tolerations 属性的写法
对于 tolerations 属性的写法,其中的 key、value、effect 与 Node 的 Taint 设置需保持一致, 还有以下几点说明:
- 如果 operator 的值是 Exists,则 value 属性可省略
- 如果 operator 的值是 Equal,则表示其 key 与 value 之间的关系是 equal(等于)
- 如果不指定 operator 属性,则默认值为 Equal
另外,还有两个特殊值:
-
空的 key 如果再配合 Exists 就能匹配所有的 key 与 value,也就是是能容忍所有节点的所有 Taints !!!
-
空的 effect 匹配所有的 effect
例子:
master节点上,为什么除了静态pod之外,上面还有calico/kube-proxy组件可以运行。
为什么呢?原因在这里:污点容忍是可以直接放宽条件写的,例如calico,不分什么key什么value,符合带有污点是NoSchedule都可以允许分配。
4、nodeNamenodeName:指定节点名称,用于将Pod调度到指定的Node上,不经过调度器 (nodeName指哪打哪,nodeName一般在测试中用,但工作中根本没怎么用到)
| 实践:nodeName测试(测试成功)-2022.5.16 |
- 创建pod的yaml并修改
[root@k8s-master ~]#kubectl run pod6 --image=nginx --dry-run=client -o yaml > pod6.yaml
[root@k8s-master ~]#vim pod6.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-example
labels:
app: nginx
spec:
nodeName: k8s-node1
containers:
- name: nginx
image: nginx:1.15
- 注意:原来k8s-node1节点是已经打好污点了的。
[root@k8s-master ~]#kubectl describe nodes |grep Taint Taints: node-role.kubernetes.io/master:NoSchedule Taints: ssd=ok:NoSchedule Taints:[root@k8s-master ~]#kubectl describe nodes k8s-node1 |grep Taint Taints: ssd=ok:NoSchedule [root@k8s-master ~]#
- apply并查看效果
[root@k8s-master ~]#kubectl apply -f pod6.yaml pod/pod6 created [root@k8s-master ~]#kubectl get pod -o wide
- 结论
如果在pod里指定了nodeName字段,那么即使该pod没有配置污点容忍,也会被强制开通的指定的节点上去的。
测试结束。
思考 1.不用 DaemonSet,如何使用 Deployment 是否实现同样的功能?我们知道 DaemonSet 控制器的功能就是在每个节点上运行一个 Pod,如何要使用 Deployment 来实现,首先就要设置副本数量为节点数,比如我们这里加上 master 节点一共3个节点,则要设置3个副本,要在 master 节点上执行自然要添加容忍,那么要如何保证一个节点上只运行一个 Pod 呢?是不是前面的提到的 Pod 反亲和性就可以实现,以自己 Pod 的标签来进行过滤校验即可,新的 Pod 不能运行在一个已经具有该 Pod 的节点上,是不是就是一个节点只能运行一个?
- 模拟的资源清单如下所示:
# 06-daemonset-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mock-ds-demo
spec:
replicas: 3
selector:
matchLabels:
app: mock-ds-demo
template:
metadata:
labels:
app: mock-ds-demo
spec:
tolerations: #加上容忍,这样才有机会运行在master节点上
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: ngpt
affinity:
podAntiAffinity: # pod反亲合性
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector:
matchExpressions:
- key: app # Pod的标签
operator: In
values: ["mock-ds-demo"]
topologyKey: kubernetes.io/hostname # 以hostname为拓扑域,相当于每一个节点都是一个拓扑域
- 创建上面的资源清单验证:
$ kubectl apply -f 06-daemonset-deployment.yaml deployment.apps/mock-ds-demo created $ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES mock-ds-demo-8694759c69-882fx 1/1 Running 0 18s 10.244.0.15 master1mock-ds-demo-8694759c69-frfhx 1/1 Running 0 18s 10.244.1.108 node1 mock-ds-demo-8694759c69-p5cjw 1/1 Running 0 18s 10.244.2.235 node2
可以看到我们用 Deployment 部署的服务在每个节点上都运行了一个 Pod,实现的效果和 DaemonSet 是一致的。
2.同样的如果想在每个节点(或指定的一些节点)上运行2个(或多个)Pod 副本,如何实现?DaemonSet 是在每个节点上运行1个 Pod 副本,显然我们去创建2个(或多个)DaemonSet 即可实现该目标,但是这不是一个好的接近方案,而 PodAntiAffinity 只能将一个 Pod 调度到某个拓扑域中去,所以都不能很好的来解决这个问题。
要实现这种更细粒度的控制,我们可以通过设置拓扑分布约束来进行调度,设置拓扑分布约束来将 Pod 分布到不同的拓扑域下,从而实现高可用性或节省成本,具体实现方式请看下文。
关于我我的博客主旨:
- 排版美观,语言精炼;
- 文档即手册,步骤明细,拒绝埋坑,提供源码;
- 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!
-
个人微信二维码:x2675263825 (舍得), qq:2675263825。
-
个人微信公众号:《云原生架构师实战》
-
个人csdn
https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421
-
个人博客:(www.onlyyou520.com)
-
开源干货
语雀:https://www.yuque.com/go/doc/73723298?#
好了,关于本次就到这里了,感谢大家阅读,最后贴上我女神的photo,祝大家生活快乐,每天都过的有意义哦,我们下期见!



