八、高级调度任务、污点与容忍、亲和力
1. Job
1.1 Job概述
Job 负责处理任务,即仅执行一次的任务,并将继续重试 Pod 的执行,它保证批处理任务的一个或多个 Pod 直到指定数量的 Pod 成功终止。
- Job 跟踪记录成功完成的 Pod 个数, 当数量达到指定的成功个数阈值时,任务(即 Job)结束。
- 删除 Job 的操作会清除所创建的全部 Pod。
- 挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
1.2 使用Job
创建一个Job的yaml文件
[root@k8s-master01 job]# cat /yaml/job/test-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
labels:
job-name: echo
name: echo
namespace: default
spec:
#suspend: true # 1.21+ 开启后不会自动进行。
#ttlSecondsAfterFinished: 100 #Job在执行结束之后(状态为completed或 Failed)自动清理。设置为0表示执行结束立即删除,不设置则不会清除,需要开启TTLAfterFinished特性。
backoffLimit: 2 #如果任务执行失败,失败多少次后不再执行。
completions: 5 #有多少个Pod执行成功,认为任务是成功的。如果为空默认和parallelism数值一样。
parallelism: 3 #并行执行任务的数量,如果parallelism数值大于未完成任务数,只会创建未完成的数量; 比如completions是4,并发是3,第一次会创建3个Pod执行任务, 第二次只会创建1个Pod执行任务。
template:
spec:
containers:
- command:
- echo
- Hello, Job
name: echo
image: registry.cn-beijing.aliyuncs.com/dotbalo/busybox
imagePullPolicy: IfNotPresent
resources: {}
restartPolicy: Never
创建并查看任务执行情况
查看Job的详细信息
Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 >
这样的 Label 标签,而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,从而 保证了 Job 与它所管理的 Pod 之间的匹配关系。而 Job 控制器之所以要使用这种携带了 UID 的 Label,就是为了避免不同 Job 对象所管理的 Pod 发生重合。
1.3 Job如何终止与清理
Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常也不会被删除。 保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告或者其它诊断性输出。
Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。 在查看了 Job 状态之后删除老的 Job 的操作留给了用户自己。 你可以使用 kubectl 来删除 Job, 当使用 kubectl 来删除 Job 时,该 Job 所创建的 Pod 也会被删除。
默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never
) 或者某个容器出错退出(restartPolicy=OnFailure
)。 这时,Job 基于前述的 spec.backoffLimit
来决定是否以及如何重试。 一旦重试次数到达 .spec.backoffLimit
所设的上限,Job 会被标记为失败, 其中运行的 Pod 都会被终止。
终止 Job 的另一种方式是设置一个活跃期限。 你可以为 Job 的 .spec.activeDeadlineSeconds
设置一个秒数值。 该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。 一旦 Job 运行时间达到 activeDeadlineSeconds
秒,其所有运行中的 Pod 都会被终止, 并且 Job 的状态更新为 type: Failed
及 reason: DeadlineExceeded
。
Tips:Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。
2. CronJob
2.1 CronJob概述
CronJob 其实就是在 Job 的基础上加上了时间调度,我们可以在给定的时间点运行一个任务,也可以周期性地在给定时间点运行。这个实际上和我们 Linux 中的 crontab 就非常类似了。
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
步长可被用于范围组合。范围后面带有 /<数字> 可以声明范围内的步幅数值。 例如,0-23/2 可被用在小时字段来声明命令在其他数值的小时数执行 (V7 标准中对应的方法是 0,2,4,6,8,10,12,14,16,18,20,22)。 步长也可以放在通配符后面,因此如果你想表达 “每两小时”,就用 */2 。
2.2 使用CronJob
创建一个CronJob的yaml文件
[root@k8s-master01 job]# cat /yaml/job/cronjob.yaml
apiVersion: batch/v1 #batch/v1beta1 1.21以上 batch/v1
kind: CronJob
metadata:
labels:
run: hello
name: hello
namespace: default
spec:
concurrencyPolicy: Allow
#Allow:允许同时运行多个任务。
#Forbid:不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建。
#Replace:如果之前的任务尚未完成,新的任务会替换的之前的任务。
failedJobsHistoryLimit: 1 #应保留多少已失败的任务,0则不保留。
jobTemplate:
metadata:
spec:
template:
metadata:
labels:
run: hello
spec:
containers:
- args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: hello
resources: {}
restartPolicy: OnFailure
securityContext: {}
schedule: '*/1 * * * *' #调度周期,和Linux一致,分别是分时日月周。
successfulJobsHistoryLimit: 3 #应保留多少已完成的任务,0则不保留。
suspend: false #如果设置为true,则暂停后续的任务,默认为false。
创建并查看任务执行情况
现在将cronjob.yaml中的suspend: false
改为suspend: true
,重新更新一下CronJob资源,能看到下图的任务暂停情况。
2.3 CronJob的终止与清理
删除CronJob即可。
3. 初始化容器
3.1 初始化容器概述
初始化容器(Init容器),顾名思义,初始化容器是用来进行初始化操作的。很多情况下,程序的启动需要依赖各类配置、资源,但是又不能继承在原有的启动命令或者镜像当中,因为程序的镜像可能并没有加载配置命令,此时InitContainer就起到了很大的作用。
初始化容器在Pod内的应用容器启动之前运行,可以包括一些应用镜像中不存在的实用工具和安装脚本,用以在程序启动时进行初始化,比如创建文件、修改内核参数、等待依赖程序启动等。
3.2 初始化容器作用
在生产环境中,为了应用的安全和优化镜像的体积,业务镜像一般不会安装高危工具和并不常用的运维工具,比如curl、sed、awk、python或dig等,同时建议使用非root用户去启动容器。但是某些应用启动之前可能需要检测依赖的服务有没有成功运行,或者需要高权限去修改一些系统级配置,而这些检测或配置更改都是一次性的,所以在制作业务镜像时没有必要为了一次配置的变更去安装一个配置工具,更没有必要因为使用一次高权限而把整个镜像改成以root身份运行。
Init容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
- Init容器可以包含安装过程中应用容器中不存在的实用工具或个性化代码。
- Init容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
- Init容器可以以root身份运行,执行一些高权限命令。
- Init容器相关操作执行完成后就会退出,不会给业务容器带来安全隐患。
由于Init容器必须在应用容器启动之前运行完成,因此Init容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足一组先决条件,Pod内的所有应用容器才会并行启动。
3.3 初始化容器的特点
每个Pod中可以包含多个容器,同时Pod也可以有一个或多个先于应用容器启动的Init容器,在Pod定义中和containers同级,按顺序逐个执行,当所有的Init容器运行完成时,Kubernetes才会启动Pod内的普通容器。
Init容器与普通的容器非常像,除了如下几点:
- 它们总是运行到完成。
- 上一个运行完成才会运行下一个。
- 如果Pod的Init容器失败,Kubernetes会不断地重启该Pod,直到Init容器成功为止,但是Pod对应的restartPolicy值为Never,Kubernetes不会重新启动Pod。
Tips:Init容器不支持lifecycle、livenessProbe、readinessProbe和startupProbe,因为它们必须在Pod就绪之前运行完成。
3.4 使用初始化容器
初始化容器的常用案例:
- 某些服务需要依赖其他组件才能启动,比如后端应用需要数据库启动之后,应用才能正常启动,所以此时需要检测数据库实例是否正常,待数据库可以正常使用时,再启动后端应用。
- 利用一个git镜像进行拉取代码到指定目录中去。
- 对某个挂载目录进行初始化授予权限。
- 运行多个初始化容器,比如一个应用需要等待该应用所在Namespace的一个Service对应的Pod启动,也需要一个DB的Service启动,当两个InitContainer都成功后,才可以启动业务应用容器。
初始化容器创建文件,然后主容器运行后可以看到此文件。
[root@k8s-master01 init]# cat /yaml/init/init.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: test-init
name: test-init
spec:
replicas: 3
selector:
matchLabels :
app: test-init
template:
metadata:
labels:
app: test-init
spec:
volumes:
- name: data
emptyDir: {}
initContainers :
- command:
- sh
- -c
- touch /mnt/test-init.txt
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: init-touch
volumeMounts :
- name: data
mountPath: /mnt
- command:
- sh
- -c
- for i in `seq 1 100`; do echo $i; sleep 1; done
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: echo
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: test-init
volumeMounts :
- name: data
mountPath: /mnt
创建并查看Pod状态
看到初始化容器事件的进行,-c 进入到eco容器中就能看到还有任务在进行。
等到初始化容器都完成后,进入到Pod中查看初始化容器的操作有没有进行,没问题!
4. 污点(Taint)与容忍(Toleration)
4.1 污点与容忍基本概念
污点Taint作用在节点上,能够使节点排斥一类特定的Pod,也就是不能“兼容”该节点的污点的Pod。容忍Toleration是应用于 Pod 上的,容忍允许调度器调度带有对应污点的 Pod。 容忍允许调度但并不保证调度。
污点和容忍相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod,该节点不会接受任何不能容忍该污点的 Pod。
4.2 污点使用场景
污点一般用于调度Pod部署的节点,常用调度场景如下:
- Pod不能部署在Master节点上
- 某个节点需要维护/升级,需要将该节点上的Pod迁移到其他节点
- GPU的Pod只能部署在GPU服务器上
- Pod不能部署在没有完成可用性测试的节点上
- 等等场景!
4.3 污点种类
污点分为外置污点和内置污点,主要说明如下:
- 外置污点:人工使用Taint命令给节点打上的污点
- 内置污点:系统自带的污点
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready
:节点未准备好。这相当于节点状况Ready
的值为 "False
"。node.kubernetes.io/unreachable
:节点控制器访问不到节点. 这相当于节点状况Ready
的值为 "Unknown
"。node.kubernetes.io/memory-pressure
:节点存在内存压力。node.kubernetes.io/disk-pressure
:节点存在磁盘压力。node.kubernetes.io/pid-pressure
: 节点的 PID 压力。node.kubernetes.io/network-unavailable
:节点网络不可用。node.kubernetes.io/unschedulable
: 节点不可调度。node.cloudprovider.kubernetes.io/uninitialized
:如果 kubelet 启动时指定了一个“外部”云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute
效果的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点,但是被驱逐的Pod需要手动进行删除。
4.4 使用污点
查节点有哪些污点
#格式:kubectl describe node 节点名称 | grep Taints 筛选污点,最好再加-A 10 20,查看更多污点。
# kubectl describe node k8s-master01 | grep Taints
Taints: node-role.kubernetes.io/master:NoSchedule
# kubectl get node k8s-master01 -o go-template --template {{.spec.taints}}
[map[effect:NoSchedule key:node-role.kubernetes.io/master]]
打上污点
#格式:kubectl taint node 节点名称 污点key=污点value:effect规则
kubectl taint nodes k8s-node01 ssd=true:NoSchedule
effect规则:
- NoSchedule:禁止调度到该节点,已经在该节点上的Pod不受影响。
- NoExecute:禁止调度到该节点,如果不符合这个污点,会立马被驱逐(或在一段时间后,默认300s,可修改)。
- PreferNoSchedule:尽量避免将Pod调度到指定的节点上,如果没有更合适的节点,可以部署到该节点。
改污点
#格式:kubectl taint node 节点名称 污点key=污点value:effect规则 --overwrite
kubectl taint nodes k8s-node01 ssd=false:NoSchedule --overwrite
删除污点
#格式:kubectl taint node 节点名称 污点key=污点value:effect规则-
kubectl taint nodes k8s-node01 ssd=false:NoSchedule-
#格式:kubectl taint node 节点名称 污点key:effect规则-
kubectl taint nodes k8s-node01 ssd:NoSchedule-
#格式:kubectl taint node 节点名称 污点key- (删除所有该同样的key)
kubectl taint nodes k8s-node01 ssd-
4.5 容忍Toleration配置
Kubernetes处理多个Taint和Toleration的过程就像一个过滤器:从一个节点的所有Taint开始遍历,过滤掉那些Pod中存在与之相匹配的Toleration的Taint,余下未被过滤的Taint的effect值决定了Pod是否会被分配到该节点,特别是以下情况:
- 如果未被过滤的Taint中存在一个以上effect值为NoSchedule的Taint,则Kubernetes不会将Pod分配到该节点。(如果Pod已经在节点上运行了,无影响)
- 如果未被过滤的Taint中不存在effect值为NoExecute的Taint,但是存在effect值为PreferNoSchedule的Taint,则Kubernetes会尝试将Pod分配到该节点。
- 如果未被过滤的Taint中存在一个以上effect值为NoExecute的Taint,则Kubernetes不会将Pod分配到该节点(如果Pod还未在节点上运行),或者将Pod从该节点驱逐(如果Pod已经在节点上运行)。
Tips:Node上的污点Taint相当于锁头,容忍Toleration相当于钥匙,要是Taint有三个污点,Toleration只有两个与之对应的配置,那就相当于有一个锁头钥匙没法开,此时就会看该污点所配置的规则。
4.5.1 完全匹配
tolerations:
- key: "taintKey"
operator: "Equal"
value: "taintValue"
effect: "NoSchedule"
4.5.2 不完全匹配
tolerations:
- key: "taintKey"
operator: "Exists"
effect: "NoSchedule"
4.5.3 大范围匹配(不推荐key为内置Taint)
容忍中未定义 effect
但是定义了 key
,Kubernetes 认为此容忍匹配所有 effect
- key: "taintKey"
operator: "Exists"
4.5.4 匹配所有(不推荐)
容忍中未定义 key
但是定义了 operator 为 Exists
,Kubernetes 认为此容忍匹配所有的污点
tolerations:
- operator: "Exists"
4.5.5 自定义驱逐时间
#自定义驱逐时间(默认300s),下面配置示例是3600秒
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
当满足如下条件时,Kubernetes 认为容忍和污点匹配:
- 键(key)相同
- 效果(effect)相同
- 污点operator为:
Exists
(此时污点中不应该指定value
)- 或者
Equal
(此时容忍的value
应与污点的value
相同)
如果不指定 operator
,则其默认为 Equal
4.6 污点与容忍使用示例
以下所有示例只是练习污点与容忍的使用,生产环境配置不合理。
4.6.1 将Pod部署在Master节点
此时所有的Master都有这么一个污点 node-role.kubernetes.io/master:NoSchedule
,只有key和value组成。
如果要想Pod运行在这些节点上,就得给它配一把容忍 “钥匙” 开这个污点 “锁”,那Pod的yaml配置如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
#匹配该key即可
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
此时还不一定会调度到Master节点上呢,因为Node节点都没有污点,此时给所有Node节点打上下面这条污点,看最终创建后Pod在哪里。
kubectl taint node Node节点名称 nodeploy:NoSchedule
都在Master节点上了
4.6.2 禁止新Pod再部署到Master
之前所有节点的污点都不变,此时向所有Master添加一条新的污点。
kubectl taint node Master节点名称 nodeploy=master:NoSchedule
已经在运行的Pod,不受新污点(规则为NoSchedule)的影响,但此时已经Master和Node节点都不允许调度。Master节点新添加的污点 “锁” ,没有容忍 “钥匙” 可以开了。
Tips:测试完上面两个实验,记得删除全部污点以及所有Pod。
4.6.3 将容器驱逐
创建一个新Pod,然后查看它在哪个节点上。
[root@k8s-master01 taint]# cat deployment-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: deploy-nginx
spec:
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: deploy-nginx
image: nginx:1.14.2
ports:
- containerPort: 80
tolerations:
- key: "ssd"
value: "false"
effect: "NoExecute"
operator: "Equal"
node01、02、03、04上都有,比如现在Node04需要进行升级,此时配置污点让容器驱逐。
#在k8s-node04上进行
kubectl taint node k8s-node04 ssd=true:NoExecute
如果要驱逐到一个节点呢,全部在Node01,那么就在Node02、03上进行
kubectl taint node k8s-node04 ssd=true:NoExecute
Tips:到此删除deployment,同时将所有新添加的污点进行删除,生产环境中不大建议使用污点与容忍来控制!
5. 亲和力(Affinity)
亲和力(Affinity)是 Kubernetes 中的一种机制,用于指定 Pod 与其他资源(如节点、Pod 或标签)之间的关联性和偏好。
亲和力定义了 Pod 对其他资源的偏好和互动方式,以便在调度过程中进行合适的匹配。通过使用亲和力规则,可以将 Pod 调度到满足特定条件的节点上,或者将 Pod 与其他相关的 Pod 进行关联,以实现特定的调度需求和业务需求。
可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该Pod。
Tips:nodeSelector 只能选择拥有所有指定标签的节点!
污点是排斥,即宿主机不允许某些Pod部署到“自己身上”,而亲和力表示Pod需要和指定的Pod或节点部署在一起,当然也可以进行互斥。
5.1 亲和力策略
亲和性调度可以分成软策略和硬策略两种方式:
- 软策略就是如果现在没有满足调度要求的节点的话,Pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓。
- 硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然就不干了。
亲和性和反亲和性都有这两种规则可以设置:
- 软策略
preferredDuringSchedulingIgnoredDuringExecution
- 硬策略
requiredDuringSchedulingIgnoredDuringExecution
5.2 亲和力分类
Affinity根据功能的不同分为节点亲和力(Node Affinity)和Pod亲和力(Pod Affinity)。
- 每种亲和力还有两种不同的“实现方法”,即必须和尽量。
- 亲和力又分为软亲和力(非强制性)和硬亲和力(强制性)。
- Pod之间可以相互排斥,也可以相互吸引,所以Pod亲和力又分为PodAffinity(Pod亲和力)和PodAntiAffinity(Pod反亲和力)。
5.3 亲和力配置详解
5.3.1 默认规则
如果同时指定了NodeSelector和NodeAffinity,需要两者都满足才能被调度。
如果配置了多个NodeSelectorTerms,满足其一即可调度到指定的节点上。
如果配置了多个MatchExpressions,需要全部满足才能调度到指定的节点上。
如果删除了被调度节点上的标签,Pod不会被删除,也就是说亲和力配置只有在调度的时候才会起作用。
5.3.2 Node硬亲和力规则
requiredDuringSchedulingIgnoredDuringExecution:硬亲和力配置
#与containers同级
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node01
- k8s-node02
#该配置是匹配有label标签 key是kubernetes.io/hostname,且values是k8s-node01、k8s-node02之一即可。
nodeSelectorTerms:节点选择器配置,可以配置多个matchExpressions(满足其一)
matchExpressions:可以配置多个key、value类型的选择器(都需要满足),其中values可以配置多个 (满足其一)
operator:标签匹配方式
- In:相当于key = value的形式
- NotIn:相当于key != value的形式
- Exists:节点存在label的key为指定的值即可,不能配置values字段
- DoesNotExist:节点不存在label的key为指定的值即可,不能配置values字段
- Gt:大于value指定的值
- Lt:小于value指定的值
5.3.3 Node软亲和力规则
preferredDuringSchedulingIgnoredDuringExecution:软亲和力配置
#与containers同级
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 3
preference:
matchExpressions:
- key: SSD
operator: In
values:
- "true"
- weight: 2
preference:
matchExpressions:
- key: HDD
operator: In
values:
- "true"
#在pod副本为40情况下,会有几率调度到HDD=true这个标签的节点上,主要还得看SSD节点的负载,由调度器去权重!
weight:软亲和力的权重,权重越高优先级越大,范围1-100
preference:软亲和力配置项,和weight同级,可以配置多个,可以配置多个key、value类型的选择器(都需要满足),其中values可以配置多个 (满足其一)
operator:标签匹配方式
- In:相当于key = value的形式
- NotIn:相当于key != value的形式
- Exists:节点存在label的key为指定的值即可,不能配置values字段
- DoesNotExist:节点不存在label的key为指定的值即可,不能配置values字段
- Gt:大于value指定的值
- Lt:小于value指定的值
5.3.4 Pod亲和力、反亲和力规则
#Pod亲和力(软、硬)
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: key名称
operator: In
values:
- value值
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- key: key名称
operator: In
values:
- value值
#Pod反亲和力(软、硬)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: key名称
operator: In
values:
- value值
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- key: key名称
operator: In
values:
- value值
topologyKey: kubernetes.io/hostname
labelSelector:Pod选择器配置,可以配置多个
matchExpressions:可以配置多个key、value类型的选择器(都需要满足),其中values可以配置多个 (满足其一)
weight:软亲和力的权重,权重越高优先级越大,范围1-100
topologyKey:匹配的拓扑域的key,也就是节点上label的key,key和value相同的为同一个域,用于标注不同的机房和地区。默认填kubernetes.io/hostname
,也就是在每个节点上去进行Pod的反亲和力。
operator:标签匹配方式**(Pod亲和力、反亲和力都无Gt、Lt)**
- In:相当于key = value的形式
- NotIn:相当于key != value的形式
- Exists:节点存在label的key为指定的值即可,不能配置values字段
- DoesNotExist:节点不存在label的key为指定的值即可,不能配置values字段
5.4 常用方案
5.4.1 同一个应用部署至不同的Node节点
在使用Kubernetes时,一般都会有很多节点运行容器,此时可以使用Pod反亲和力将同一个应用部署到不同的节点上,达到更高的可用率,以免同一个应用部署到相同的宿主机带来的风险。
编写yaml文件
[root@k8s-master01 ~]# cat /yaml/affinity/podAntiAffinity-node.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 4
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: nginx
此时创建一个Pod为4副本的Deployment,查看Pod情况,分别在四个node节点上创建。
进行更改副本数,新增的两个副本就调度分配不了了。因为topologyKey有四个域(也就是4个Node节点)都分配了Pod,yaml配置了反亲和力且Pod的label是app=pod
,匹配上了,无法在这些Node节点上创建。
报错信息:0/7个节点可用:3个节点具有pod无法容忍的污点{node-role.kubernetes.io/master:},4个节点与pod反亲和规则不匹配。
Tips:实验完成,删除Deployment。
5.4.2 尽量调度到高配置Node节点
例如目前有四台Node节点,有两台节点是配置了SSD高速硬盘,两台是配置了传统的HDD机械硬盘。那想让它们优先部署在SSD的节点,但也不排除部署在HDD节点,可以通过使用节点软亲和力来配置。
编写yaml文件
[root@k8s-master01 affinity]# cat node-affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: node-nginx
name: node-affinity-nginx
spec:
replicas: 6
selector:
matchLabels:
app: node-nginx
template:
metadata:
labels:
app: node-nginx
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 3
preference:
matchExpressions:
- key: SSD
operator: In
values:
- "true"
- weight: 2
preference:
matchExpressions:
- key: HHD
operator: In
values:
- "true"
containers:
- name: node-affinity-nginx
image: nginx:1.14.2
ports:
- containerPort: 80
配置label标签
kubectl label node k8s-node01 k8s-node02 HDD=true
kubectl label node k8s-node03 k8s-node04 SSD=true
此时创建一个Pod为6副本的Deployment,查看Pod情况,此时都调度在Node03、04节点上,应该是该节点负载不高。
更改副本数为40,再查看Pod情况,已经有Pod调度在Node01、02上来,再进行筛选统计一下。
kubectl scale deployment node-affinity-nginx --replicas=40
5.4.3 同一个应用多区域部署
拓扑域,主要针对宿主机,相当于对宿主机进行区域的划分。用label进行判断,不同的key和不同的value是属于不同的拓扑域,同一个key和不同的value属于不同一个区域。
例如有多地机房通过专线互联,一个集群下的节点分别在不同的区域。项目上线时,想把Pod分布在不同的区域里,且同一区域不能重复部署该项目。可以通过label标注各地地区节点,同时使用Pod的反亲和力和topologyKey来实现。
先删除Master节点上的污点来增加可部署的节点
kubectl taint node k8s-master01 node-role.kubernetes.io/master:NoSchedule-
kubectl taint node k8s-master02 node-role.kubernetes.io/master:NoSchedule-
kubectl taint node k8s-master03 node-role.kubernetes.io/master:NoSchedule-
[root@k8s-master01 affinity]# kubectl describe node | grep Taint
Taints: <none>
Taints: <none>
Taints: <none>
Taints: <none>
Taints: <none>
Taints: <none>
Taints: <none>
根据标签区分区域,其中: k8s-master01、k8s-master02、k8s-master03为南山区,region=nanshan
k8s-node01、k8s-node02为罗湖区,region=luohu
k8s-node03、k8s-node04为龙岗区,region=longgang
kubectl label node k8s-master01 k8s-master02 k8s-master03 region=nanshan
kubectl label node k8s-node01 k8s-node02 region=luohu
kubectl label node k8s-node03 k8s-node04 region=longgang
编写yaml文件
[root@k8s-master01 affinity]# cat web-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web-nginx
name: web-nginx
spec:
replicas: 1
selector:
matchLabels:
app: web-nginx
template:
metadata:
labels:
app: web-nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-nginx
topologyKey: region
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: web-nginx
此时创建一个Pod为1副本的Deployment,查看Pod情况,部署在node02,也就是罗湖区。
再依次增加到4副本,第二次部署在node03,是龙岗区。第三次部署在master01上,是南山区。增加到第四个副本时,就已经是Pending了,因为三个区域已经部署过了,此时在各区域内反亲和力产生了作用,不能在同一区域继续部署了。
本篇文章内容参考杜宽的《云原生Kubernetes全栈架构师》,视频、资料文档等,大家可以多多支持!还有k8s训练营、“我为什么这么菜”知乎博主等资料文档,感谢无私奉献!