共计 26618 个字符,预计需要花费 67 分钟才能阅读完成。
API Server 接受客户端提交 Pod 对象创建请求后的操作过程中,有一个重要的步骤就是由调度器程序 kube-scheduler 从当前集群中选择一个可用的最佳节点来接收并运行它,通常是默认的调度器 kube-scheduler 负责执行此类任务。
对于每个待创建的 Pod 对象来说,调度过程通常分为两个阶段—》过滤—》打分,过滤阶段用来过滤掉不符合调度规则的 Node,打分阶段建立在过滤阶段之上,为每个符合调度的 Node 进行打分,分值越高则被调度到该 Node 的机率越大。
Pod 调度策略除了系统默认的 kube-scheduler 调度器外还有以下几种实现方式:
1.nodeName(直接指定 Node 主机名)
2.nodeSelector(节点选择器,为 Node 打上标签,然后 Pod 中通过 nodeSelector 选择打上标签的 Node)
3. 污点 taint 与容忍度 tolerations
4.NodeAffinity 节点亲和性
5.PodAffinity Pod 亲和性
6.PodAntAffinity Pod 反亲和性
以下几章节内容主要讲解上面几种调度策略以及 kube-scheduler 调度器的调度原理。
Pod 调度之 kube-scheduler
官方文档:
https://kubernetes.io/zh/docs/concepts/scheduling-eviction/kube-scheduler/
kube-scheduler 调度介绍
kube-scheduler 是 Kubernetes 集群的默认调度器,并且是集群控制面 (master) 的一部分。对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的 Node 去运行这个 Pod。
然而,Pod 内的每一个容器对资源都有不同的需求,而且 Pod 本身也有不同的资源需求。因此,Pod 在被调度到 Node 上之前,根据这些特定的资源调度需求,需要对集群中的 Node 进行一次过滤。
在一个集群中,满足一个 Pod 调度请求的所有 Node 称之为可调度节点。如果没有任何一个 Node 能满足 Pod 的资源请求,那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的 Node。
调度器先在集群中找到一个 Pod 的所有可调度节点,然后根据一系列函数对这些可调度节点打分,然后选出其中得分最高的 Node 来运行 Pod。之后,调度器将这个调度决定通知给 kube-apiserver,这个过程叫做绑定。
在做调度决定是需要考虑的因素包括:单独和整体的资源请求、硬件 / 软件 / 策略限制、亲和以及反亲和要求、数据局域性、负载间的干扰等等。
kube-scheduler 调度流程
kube-scheduler 给一个 pod 做调度选择包含两个步骤:
1. 过滤(Predicates 预选策略)2. 打分(Priorities 优选策略)
过滤阶段:过滤阶段会将所有满足 Pod 调度需求的 Node 选出来。例如,PodFitsResources 过滤函数会检查候选 Node 的可用资源能否满足 Pod 的资源请求。在过滤之后,得出一个 Node 列表,里面包含了所有可调度节点;通常情况下,这个 Node 列表包含不止一个 Node。如果这个列表是空的,代表这个 Pod 不可调度。
打分阶段:在过滤阶段后调度器会为 Pod 从所有可调度节点中选取一个最合适的 Node。根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。最后,kube-scheduler 会将 Pod 调度到得分最高的 Node 上。如果存在多个得分最高的 Node,kube-scheduler 会从中随机选取一个。
整体流程如下图所示:
过滤阶段
官方文档:
https://kubernetes.io/docs/reference/scheduling/policies/
在调度时的过滤阶段到底时通过什么规则来对 Node 进行过滤的呢?就是通过以下规则!
1.PodFitsHostPorts:检查 Node 上是否不存在当前被调度 Pod 的端口(如果被调度 Pod 用的端口已被占用,则此 Node 被 Pass)。
2.PodFitsHost:检查 Pod 是否通过主机名指定了特性的 Node (是否在 Pod 中定义了 nodeName)
3.PodFitsResources:检查 Node 是否有空闲资源 (如 CPU 和内存) 以满足 Pod 的需求。
4.PodMatchNodeSelector:检查 Pod 是否通过节点选择器选择了特定的 Node (是否在 Pod 中定义了 nodeSelector)。
5.NoVolumeZoneConflict:检查 Pod 请求的卷在 Node 上是否可用 (不可用的 Node 被 Pass)。
6.NoDiskConflict:根据 Pod 请求的卷和已挂载的卷,检查 Pod 是否合适于某个 Node (例如 Pod 要挂载 /data 到容器中,Node 上 /data/ 已经被其它 Pod 挂载,那么此 Pod 则不适合此 Node)
7.MaxCSIVolumeCount::决定应该附加多少 CSI 卷,以及是否超过了配置的限制。
8.CheckNodeMemoryPressure:对于内存有压力的 Node,则不会被调度 Pod。
9.CheckNodePIDPressure:对于进程 ID 不足的 Node,则不会调度 Pod
10.CheckNodeDiskPressure:对于磁盘存储已满或者接近满的 Node,则不会调度 Pod。
11.CheckNodeCondition:Node 报告给 API Server 说自己文件系统不足,网络有写问题或者 kubelet 还没有准备好运行 Pods 等问题,则不会调度 Pod。
12.PodToleratesNodeTaints:检查 Pod 的容忍度是否能承受被打上污点的 Node。
13.CheckVolumeBinding:根据一个 Pod 并发流量来评估它是否合适(这适用于结合型和非结合型 PVCs)。
打分阶段
官方文档:
https://kubernetes.io/docs/reference/scheduling/policies/ 当过滤阶段执行后满足过滤条件的 Node,将进行打分阶段。
1.SelectorSpreadPriority:优先减少节点上属于同一个 Service 或 Replication Controller 的 Pod 数量
2.InterPodAffinityPriority:优先将 Pod 调度到相同的拓扑上(如同一个节点、Rack、Zone 等)
3.LeastRequestedPriority:节点上放置的 Pod 越多,这些 Pod 使用的资源越多,这个 Node 给出的打分就越低,所以优先调度到 Pod 少及资源使用少的节点上。
4.MostRequestedPriority:尽量调度到已经使用过的 Node 上,将把计划的 Pods 放到运行整个工作负载所需的最小节点数量上。
5.RequestedToCapacityRatioPriority:使用默认资源评分函数形状创建基于 requestedToCapacity 的
ResourceAllocationPriority。
6.BalancedResourceAllocation:优先平衡各节点的资源使用。
7.NodePreferAvoidPodsPriority:根据节点注释对节点进行优先级排序,以使用它来提示两个不同的 Pod 不应在同一节点上运行。
scheduler.alpha.kubernetes.io/preferAvoidPods。
8.NodeAffinityPriority:优先调度到匹配 NodeAffinity(Node 亲和性调度)的节点上。
9.TaintTolerationPriority:优先调度到匹配 TaintToleration (污点) 的节点上
10.ImageLocalityPriority:尽量将使用大镜像的容器调度到已经下拉了该镜像的节点上。
11.ServiceSpreadingPriority:尽量将同一个 service 的 Pod 分布到不同节点上,服务对单个节点故障更具弹性。
12.EqualPriority:将所有节点的权重设置为 1。
13.EvenPodsSpreadPriority:实现首选 pod 拓扑扩展约束。
kube-scheduler 调度示例
默认配置使用的就是 kube-scheduler 调度组件,我们下面例子启动三个 Pod,看分别被分配到哪个 Node。
1. 创建资源配置清单
cat scheduler-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: scheduler-deploy
spec:
replicas: 3
selector:
matchLabels:
app: scheduler-pod
template:
metadata:
labels:
app: scheduler-pod
spec:
containers:
- image: busybox:latest
name: scheduler-pod
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
2. 使用 kubectl 创建资源对象
kubectl apply -f scheduler-pod.yaml
3. 查看被 kube-scheduler 自动调度的 Pod 两个 Pod 在 Node03 上,一个在 Node02 上
kubectl get pods -o wide | grep scheduler
scheduler-deploy-65d8f9c98-cqdm9 1/1 Running 0 111s 10.244.5.59 k8s-node03 <none> <none>
scheduler-deploy-65d8f9c98-d4t9p 1/1 Running 0 111s 10.244.5.58 k8s-node03 <none> <none>
scheduler-deploy-65d8f9c98-f8xxc 1/1 Running 0 111s 10.244.2.45 k8s-node02 <none> <none>
4. 我们查看一下 Node 资源的使用情况 Node01, 可用内存 2.7G
Node02,可用内存 5.8G
Node03,可用内存 5.6G
所以默认的 kube-scheduler 调度策略经过了过滤和打分后,将以上三个 Pod 分布在 Node2 和 Node3 上。
Pod 调度之 nodeName
nodeNamed 这种调度方式比较简单,我们可以指定 Pod 在哪台 Node 上进行运行,通过 spec.nodeName 参数来指定 Node 主机名称即可。
1、创建资源配置清单
cat nodeName-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nodename-pod
spec:
#指定该 Pod 运行在 k8s-node02 节点上
nodeName: k8s-node02
containers:
- image: busybox:latest
name: nodename-containers
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
2、创建 Pod 资源对象
kubectl apply -f nodeName-pod.yaml
3、查看 Pod 调度节点
如下,nodename-pod 被绑定在了 k8s-node02 上
kubectl get pods -o wide | grep name
nodename-pod 1/1 Running 0 25s 10.244.2.46 k8s-node02 <none> <none>
Pod 调度之 nodeSelector
nodeSelector 用于将 Pod 调度到匹配 Label 的 Node 上, 所以要先给 node 打上标签,然后在 Pod 配置清单中选择指定 Node 的标签。先给规划 node 用途,然后打标签,例如将两台 node 划分给不同团队使用:
为 Node 添加标签
k8s-node02 给开发团队用,k8s-node03 给大数据团队用
1. 添加标签
kubectl get nodes -o wide --show-labels
2. 查看标签
kubectl apply -f nodeSelector-pod.yaml
创建资源配置清单
cat nodeSelector-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nodeselector-pod
spec:
nodeSelector: #指定标签选择器
team: development #label指定开发团队的label
containers:
- image: busybox:latest
name: nodeselector-containers
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
创建 Pod 对象
kubectl apply -f nodeSelector-pod.yaml
查看 pod 被分配的 Node
kubectl get pods -o wide | grep nodeselect
nodeselector-pod 1/1 Running 0 49s 10.244.2.47 k8s-node02 <none> <none>
删除标签
kubectl label nodes k8s-node02 team-
kubectl label nodes k8s-node03 team-
删除标签后 pod 还在正常运行
kubectl get pods -o wide | grep nodeselect
nodeselector-pod 1/1 Running 0 11m 10.244.2.47 k8s-node02 <none> <none>
把 Pod 删除然后再次创建 Pod
kubectl get pods -o wide | grep nodeselectnodeselector-pod 0/1 Pending 0 55s <none> <none> <none> <none>
会发现该 pod 一直在等待中,找不到清单中配置标签的 Node
kubectl describe pods/nodeselector-pod | grep -A 10 EventsEvents: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling <unknown> default-scheduler 0/6 nodes are available: 6 node(s) didn't match node selector. Warning FailedScheduling <unknown> default-scheduler 0/6 nodes are available: 6 node(s) didn't match node selector.
事件:6 个节点都不匹配 node selector
kubectl describe pods/nodeselector-pod | grep -A 10 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/6 nodes are available: 6 node(s) didn't match node selector.
Warning FailedScheduling <unknown> default-scheduler 0/6 nodes are available: 6 node(s) didn't match node selector.
Pod 调度之污点与容忍
官方文档:
https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#example-use-cases
污点(taint)是定义在 Node 之上的键值型的数据,用于让节点拒绝将 Pod 调度运行于其上,除非该 Pod 对象具有接纳 Node 污点的容忍度。而容忍度(tolerations)是定义在 Pod 对象的键值型属性数据,用于·配置其可容忍的 Node 污点,而且调度器仅能将 Pod 对象调度至其能够容忍该 Node 污点的 Node 之上。
如下图所示:
•Pod- A 具备 k8s-node01 污点的容忍度,所以能够被调度器调度至 k8s-node01 上。•Pod- A 不完全具备 k8s-node02 污点的容忍度,所以不能被调度至 k8s-node02。•Pod- A 虽然具备容忍度,但同样可以调度至没有污点的 k8s-node03 节点上。•Pod- B 自身没有容忍度,所以只能被调度至没有污点的 k8s-node03 节点上。
污点介绍及定义
1. 污点类型介绍 污点定义在 nodeSpec 中,容忍度定义在 PodSpec 中,他们都是键值型数据,但又都额外支持一个效果(effect)标记,语法格式为“key=value:effect”, 其中 key 和 value 的用法及格式与资源注解信息相似,而 effect 则用于定义对 Pod 对象的排斥等级,它主要包含以下三种排斥类型。
•NoSchedule:为 Node 添加污点等级为 NoSchedule, 除容忍此污点的 Pod 以外的其它 Pod 将不再被调度到本机。
•PreferNoSchedule:为 Node 添加污点等级为 PreferNoSchedule, 不能容忍此污点的 Pod 对象尽量不要调度到当前节点,如果没有其它节点可以供 Pod 选择时,也会接受没有容忍此污点的 Pod 对象。
•NoExecute:为 Node 添加污点等级为 NoExecute,能容忍此污点的 Pod 将被调度到此节点,而且节点上现存的 Pod 对象因节点使用了 NoExceute 等级的污点,则现存的 Pod 将被驱赶至其它满足条件的 Node(除非现存 Pod 容忍了当前节点的污点)。
2.Master 污点介绍 以 kubeadm 部署的 kubernetes 集群,其 Master 节点将自动添加污点信息以阻止不能容忍此污点的 Pod 对象调度至此节点,因此用户可以手动创建 Pod 来容忍 Master 的污点。
查看 kubernetes 集群中 master 节点的污点:
kubectl get pods -o wide | grep nodeselectnodeselector-pod 1/1 Running 0 49s 10.244.2.47 k8s-node02 <none> <none>
不过,有些系统级别应用,如 kube-proxy 或者 kube-flannel 等也是以 Pod 形式运行在集群上,这些资源在创建时就添加上了相应的容忍度以确保他们被 DaemonSet 控制器创建时能够调度至 Master 节点运行一个实例。
查看系统级别 Pod 的容忍度
kubectl describe pods/etcd-k8s-master01 pods/etcd-k8s-master01 pods/kube-flannel-ds-amd64-2smzp pods/kube-flannel-ds-amd64-2smzp -n kube-system | grep Tolerations
Tolerations: :NoExecute
Tolerations: :NoExecute
Tolerations: :NoSchedule
Tolerations: :NoSchedule
另外,这类 Pod 是构成 Kubernetes 系统的基础且关键的组件,它们甚至还定义了更大的容忍度,从下面某 kube-flannel 实例的容忍度定义来看,它还能容忍那些报告了磁盘压力或内存压力的节点,以及未就绪的节点和不可达的节点,以确保它们能在任何状态下正常调度至集群节点上运行。
kubectl describe pods kube-flannel-ds-amd64-2smzp -n kube-system
Tolerations: :NoSchedule
node.kubernetes.io/disk-pressure:NoSchedule
node.kubernetes.io/memory-pressure:NoSchedule
node.kubernetes.io/network-unavailable:NoSchedule
node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/pid-pressure:NoSchedule
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unschedulable:NoSchedule
Events: <none>
3. 定义污点
定义污点语法 kubectl taint nodes <node-name> <key>=<value>:<effect>
node-name:指定需要打污点的 Node 主机名 key=value:指定污点的键值型数据 effect:为污点的等级
key 名称长度上线为 253 个字符,可以使用字母或者数字开头,支持字母、数字、连接符、点号、下划线作为 key 或者 value。value 最长是 63 个字符。污点通常用于描述具体的部署规划,它们的键名形式如 node-type、node-role、node-project、node-geo 等。
1. 添加污点 为 k8s-node02 添加污点,污点程度为 NoSchedule,type=calculate 为标签
kubectl taint node k8s-node02 type=calculate:NoSchedule
2. 查看污点
kubectl describe nodes k8s-node02 | grep Taints
这样的话我们创建 Pod 就不会被调度到我们打上污点的 k8s-node02 的节点上
3. 创建 Pod 资源配置清单 我们创建 3 个 Pod,看看其是否会将 Pod 调度到我们打污点 Node 上
cat taint-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: taint-deploy
spec:
replicas: 3
selector:
matchLabels:
app: taint-pod
template:
metadata:
labels:
app: taint-pod
spec:
containers:
- image: busybox:latest
name: taint-pod
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
2. 查看 Pod 被调度的 Node 下面三个 Pod 都被调度到了 Node03 上,效果可能不是很明显,我们为 Node02 打了污点,还有 Node01 没有体现出来
kubectl apply -f taint-pod.yaml
kubectl get pods -o wide | grep taint
taint-deploy-748989f6d4-f7rbq 1/1 Running 0 41s 10.244.5.62 k8s-node03 <none> <none>
taint-deploy-748989f6d4-nzwjg 1/1 Running 0 41s 10.244.5.61 k8s-node03 <none> <none>
taint-deploy-748989f6d4-vzzdx 1/1 Running 0 41s 10.244.5.60 k8s-node03 <none> <none>
4. 扩容 Pod 我们将 Pod 扩容至 9 台,让 Pod 分配到 Node01 节点,可以直观的展现污点
kubectl scale --replicas=9 deploy/taint-deploy -n default
kubectl get pods -o wide | grep taint
taint-deploy-748989f6d4-4ls9d 1/1 Running 0 54s 10.244.5.65 k8s-node03 <none> <none>
taint-deploy-748989f6d4-794lh 1/1 Running 0 68s 10.244.5.63 k8s-node03 <none> <none>
taint-deploy-748989f6d4-bwh5p 1/1 Running 0 54s 10.244.5.66 k8s-node03 <none> <none>
taint-deploy-748989f6d4-ctknr 1/1 Running 0 68s 10.244.5.64 k8s-node03 <none> <none>
taint-deploy-748989f6d4-f7rbq 1/1 Running 0 2m27s 10.244.5.62 k8s-node03 <none> <none>
taint-deploy-748989f6d4-hf9sf 1/1 Running 0 68s 10.244.3.51 k8s-node01 <none> <none>
taint-deploy-748989f6d4-nzwjg 1/1 Running 0 2m27s 10.244.5.61 k8s-node03 <none> <none>
taint-deploy-748989f6d4-prg2f 1/1 Running 0 54s 10.244.3.52 k8s-node01 <none> <none>
taint-deploy-748989f6d4-vzzdx 1/1 Running 0 2m27s 10.244.5.60 k8s-node03 <none> <none>
以上调度了两台 Pod 到 Node02,目前 Node03 和 Node01 都可以分配到 Pod,而被打了污点的 Node02 无法分配 Pod
5. 删除污点 删除污点之需要指定标签的 key 及污点程度
kubectl taint node k8s-node02 type:NoSchedule-
容忍度介绍及定义
Pod 对象的容忍度可以通过其 spec.tolerations 字段进行添加,根据使用的操作符不同,主要有两种可用的形式:
1. 容忍度与污点信息完全匹配的等值关系,使用 Equal 操作符。2. 判断污点是否存在的匹配方式,使用 Exists 操作富。
容忍度所用到的参数 tolerations,tolerations 参数下的还有以下几个二级参数:
•operator:此值被称为运算符,值可以为[Equal|Exists],Equal 表示污点的 key 是否等于 value(默认参数),Exists 只判断污点的 key 是否存在,使用该参数时,不需要定义 value。
•effect:指定匹配的污点程度,为空表示匹配所有等级的污点,值可以为 [NoSchedule|PreferNoSchedule|NoExecut]。
•key:指定 Node 上污点的键 key。
•value:指定 Node 上污点的值 value。
•tolerationSeconds:用于定于延迟驱赶当前 Pod 对象的时长,如果设置为 0 或者负值系统将立即驱赶当前 Pod。(单位为秒)
1. 为 Node 打上不同的等级污点
kubectl taint nodes k8s-node01 nodes=gpu:NoSchedule
kubectl taint nodes k8s-node02 data=ssd:PreferNoSchedule
kubectl taint nodes k8s-node03 traffic=proxy:NoExecute
2. 查看三个 Node 被打上的污点
kubectl describe nodes k8s-node01 k8s-node02 k8s-node03 | grep Taint
Taints: nodes=gpu:NoSchedule
Taints: data=ssd:PreferNoSchedule
Taints: traffic=proxy:NoExecute
3. 创建容忍 NoSchedule 级别污点的 Pod 并查看 Pod 调度结果
cat pod-tolerations.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-noschedule
spec:
containers:
- name: gpu-container
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
tolerations:
- key: "nodes" #指定污点的 key
operator: "Equal" #Equal值表示我们指定的 key 必须要等于 value
value: "gpu" #指定 value
effect: "NoSchedule" #指定污点级别
#调度结果如我们期望所致在 node01 上
kubectl get pods -o wide | grep pod-noschedule
pod-noschedule 1/1 Running 0 58s 10.244.3.62 k8s-node01 <none> <none>
4. 创建容忍 PreferNoSchedule 级别污点的 Pod 并查看 Pod 调度结果
cat pod-tolerations.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-prefernoschedule
spec:
containers:
- name: ssd-container
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
tolerations:
- key: "data"
operator: "Exists" #Exists 参数,只判断 key 等于 data 是否存在,不需要关心 value 是什么
effect: "PreferNoSchedule
# 调度结果如我们期望所致在 node02 上
kubectl get pods -o wide | grep pod-prefer
pod-prefernoschedule 1/1 Running 0 45s 10.244.2.82 k8s-node02 <none> <none>
5. 创建容忍 NoExecute 级别污点的 Pod 并查看调度结果
apiVersion: v1
kind: Pod
metadata:
name: pod-noexecute
spec:
containers:
- name: proxy-container
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
tolerations:
- key: "traffic"
operator: "Equal"
value: "proxy"
effect: "NoExecute" #指定污点级别
tolerationSeconds: 300 #指定驱赶当前 Node 上Pod的延迟时间
#调度结果在 Node03 上
kubectl get pods -o wide | grep pod-noexecute
pod-noexecute 1/1 Running 0 24s 10.244.5.89 k8s-node03 <none> <none>
6. 创建没有容忍度的 Pod 并查看调度结果 PreferNoSchedule 污点级别为尽量不接受没有容忍此污点的 Pod,如果没有其它节点可以供 Pod 选择时,也会接受没有容忍此污点的 Pod 对象。所以创建一个没有容忍度的 Pod 看其调度结果。
cat pod-tolerations.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-notolerations
spec:
containers:
- name: notolerations-container
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
#如下,调度结果与我们料想一致
kubectl get pods -o wide | grep pod-notolerations
pod-notolerations 1/1 Running 0 29s 10.244.2.83 k8s-node02 <none> <none>
实践中,若集群中的一组机器专用于为运行非生产典型的容器应用而备置,而且它们可能随时按需上下线,那么就应该为其添加污点信息,以确保仅那些能够容忍此污点的非生产型 Pod 对象可以调度到其上,另外,某些有种特殊硬件的节点需要专用于运行一类有着 SSD、或者 GPU 的设备,也应该为其添加污点信息以排除其它 Pod 对象。
Pod 调度之节点亲和性调度
官方文档:
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
节点亲和性调度程序是用来确定 Pod 对象调度位置的一组规则,这些规则基于节点上的自定义标签和 Pod 对象上指定的标签选择器进行定义。
节点亲和性允许 Pod 对象定义针对一组可以调度于其上的节点的亲和性或反亲和性,不过,它无法具体到某个特定的节点。例如将 Pod 调度至有着 CPU 的节点或者一个可用区域内的节点之上。定义节点亲和性规则时有两种类型的节点亲和性规则:
1. 硬亲和性(required):硬亲和性实现的是强制性规则,它是 Pod 调度时必须要满足的规则,而在不存在满足规则的 Node 时,Pod 对象会被置为 Pending 状态。
2. 软亲和性(preferred):软亲和性规则实现的是一种柔性调度限制,它倾向于将 Pod 对象运行于某类特定节点之上,而调度器也将尽量满足此需求,但在无法满足需求时它将退而求其次地选择一个不匹配规则的节点之上。
定义节点亲和性规则的关键点有两个:
1. 为节点配置合乎需求的标签。
2. 为 Pod 对象定义合理的标签选择器,从而能够基于标签选择器选择出符合需求的标签。
不过,如
requiredDuringSchedulingIgnoredDuringExecution 和
preferredDuringSchedulingIgnoredDuringExecution 名字中的后半段字符串 IgnoredDuringExecution 隐藏的意义所指,在 Pod 资源基于节点亲和性规则调度至某节点之后,节点标签发生了改变而不在符合此类节点亲和性规则时,调度器不会将 Pod 对象从此节点移除,因为它仅对新建的 Pod 对象生效。
帮助文档:kubectl explain
pods.spec.affinity.nodeAffinity
节点硬亲和性
节点硬亲和性类似于 Pod 对象使用 nodeSelector 属性可以基于节点标签匹配的方式将 Pod 对象调度至某一个节点之上。不过它仅能基于简单的等值关系定义标签选择器,而 nodeAffinity 中支持使用 matchExpressions 属性构建更为复杂的标签选择机制。
节点硬亲和性参数解析:
•nodeSelectorTerms:节点选择列表(比 nodeSelector 高级一点)。
•matchExpressions:按照节点 label 列出节点选择器列表。(与 matchFields 是两种方式,不过结果是一至)
•matchFields:按照节点字段列出节点选择器列表。(与 matchExpressions 是两种方式,不过结果是一至)
•key:指定要选择节点 label 的 key。
•values:指定要选择节点 label 的 value,值必须为数组 [“value”],如果操作符为 In 或者 Notin,value 则不能为空,如果操作符为 Exists 或者 DoesNotExist,value 则必须为空[], 如果操作符为 Gt 或 Lt,则 value 必须有单个元素,该元素将被解释为整数。
•operator:操作符,指定 key 与 value 的关系。
•In:key 与 value 同时存在,一个 key 多个 value 的情况下,value 之间就成了逻辑或效果。
•NotIn:label 的值不在某个列表中。
•Exists:只判断是否存在 key,不用关心 value 值是什么。
•DoesNotExist:某个 label 不存在。
•Gt:label 的值大于某个值。
•Lt:label 的值小于某个值
1. 为 Node 打上标签
#node01 打两个标签 ssd=true及 zone=foo
kubectl label node k8s-node01 ssd=true zone=foo
#node02 打一个标签 zone=foo
kubectl label node k8s-node02 zone=foo
#node03 打两个标签 ssd=true zone=bar
kubectl label node k8s-node03 ssd=true zone=bar
2. 创建 Pod 资源配置清单 下面 Pod 资源清单中,该 Pod 将被绑定到标签 key 为 zone,value 为 foo 的 Node 上,符合该规则有两个 Node,分别是 Node01 和 Node02,下面资源配置清单中只创建了一个 Pod,可以来观察下该 Pod 会被调度至 Node01 还是 Node02。
apiVersion: apps/v1
kind: Deployment
metadata:
name: required-nodeaffinity-deploy
labels:
type: required-deploy
spec:
replicas: 3
selector:
matchLabels:
app: required-nodeaffinity-pod
template:
metadata:
labels:
app: required-nodeaffinity-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"] }
containers:
- name: myapp
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd"]
3. 查看 Pod 被调度至哪台 Node 如下结果可以看到三个 Pod 都被调度到了 Node01 上
kubectl apply -f required-nodeAffinity-pod.yaml
kubectl get pods -o wide | grep requ
required-nodeaffinity-deploy-55448998fd-hm9ww 0/1 ContainerCreating 0 7s <none> k8s-node01 <none> <none>
required-nodeaffinity-deploy-55448998fd-pkwph 0/1 ContainerCreating 0 7s <none> k8s-node01 <none> <none>
required-nodeaffinity-deploy-55448998fd-z94v2 0/1 ContainerCreating 0 7s <none> k8s-node01 <none> <none>
4. 创建 Pod 资源清单 2 如下 value 有两个值,两个值之间是或关系,可以调度到 key 为 zone,values 为 foo 或者 bar 标签的 Node 上,下面配置清单中有两个 key,两个 key 之间是与关系,第二个 key 在第一个 key 的基础上,Node 还有有标签 key 为 ssd,values 无需关心,因为使用操作符 Exists。下面的配置清单中,只能调度到标签 key 为 zone,values 为 foo 或者 bar 以及 key 为 ssd 的 Node 上,满足此需求的 Node 有 Node01 和 Node03。
cat required-nodeAffinity-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: required-nodeaffinity-deploy
labels:
type: required-deploy
spec:
replicas: 3
selector:
matchLabels:
app: required-nodeaffinity-pod
template:
metadata:
labels:
app: required-nodeaffinity-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo","bar"] } #foo 和 bar 之间是或关系
- {key: ssd, operator: Exists, values: []} #两个 matchExpressions 之间是与关系
containers:
- name: myapp
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd"]
5. 查看 Pod 调度结果 如下三个 Pod 都被调度在 Node03 节点
kubectl apply -f required-nodeAffinity-pod.yaml
kubectl get pods -o wide | grep required-nodeaffinity
required-nodeaffinity-deploy-566678b9d8-c84nd 1/1 Running 0 65s 10.244.5.97 k8s-node03 <none> <none>
required-nodeaffinity-deploy-566678b9d8-pn27p 1/1 Running 0 65s 10.244.5.95 k8s-node03 <none> <none>
required-nodeaffinity-deploy-566678b9d8-x8ttf 1/1 Running 0 65s 10.244.5.96 k8s-node03 <none> <none>
节点软亲和性
节点软亲和性为节点选择机制提供了一种柔性控逻辑,当调度的 Pod 对象不再是 ” 必须 ”,而是“应该”放置于某些特性节点之上,当条件不满足时,它也能够接受编排于其它不符合条件的节点之上,另外,它还为每种倾向性提供了 weight 属性以便用户定义其优先级,取值范围是 1 -100,数字越大优先级越高。
节点软亲和性参数解析:
•preference:节点选择器,与相应的权重相关联。
•weight:在 1 -100 范围内,与匹配相应的节点选项相关联的权重。
•matchExpressions:按照节点 label 列出节点选择器列表。(与 matchFields 是两种方式,不过结果是一至)
•matchFields:按照节点字段列出节点选择器列表。(与 matchExpressions 是两种方式,不过结果是一至)
•key:指定要选择节点 label 的 key。
•values:指定要选择节点 label 的 value,值必须为数组 [“value”],如果操作符为 In 或者 Notin,value 则不能为空,如果操作符为 Exists 或者 DoesNotExist,value 则必须为空[“”], 如果操作符为 Gt 或 Lt,则 value 必须有单个元素,该元素将被解释为整数。
•operator:操作符,指定 key 与 value 的关系。
•In:key 与 value 同时存在,一个 key 多个 value 的情况下,value 之间就成了逻辑或效果。
•NotIn:label 的值不在某个列表中。
•Exists:只判断是否存在 key,不用关心 value 值是什么。
•DoesNotExist:某个 label 不存在。
•Gt:label 的值大于某个值。
•Lt:label 的值小于某个值
1. 创建 Pod 资源配置清单 如下示例中,Pod 模版定义了 Node 软亲和性运行在标签 key 为 zone 和 values 为 foo 或 bar 上,以及 key 为 ssd(值无需担心是什么) 的 Node 之上,符合以下需求的是 Node01 和 Node03,但是如下第一个条件 key 为 zoo 的权重为 60,而 key 为 ssd 的为 30,所以第一个条件的权重要比第二个条件的权重高一倍,我们下面运行了 3 个 Pod,调度器应该会在 Node01 上分配两个 Pod,Node03 上分配 1 个 Pod。
cat preferred-nodeAffinitt-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: preferred-nodeaffinity-deploy
labels:
type: preferred-deploy
spec:
replicas: 3
selector:
matchLabels:
app: preferred-nodeaffinity-pod
template:
metadata:
labels:
app: preferred-nodeaffinity-pod
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo","bar"] }
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
containers:
- name: myapp
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd"]
2. 查看调度结果
kubectl get pods -o wide | grep preferred
preferred-nodeaffinity-deploy-5bf4699fd9-pcxvz 1/1 Running 0 5m32s 10.244.5.98 k8s-node03 <none> <none>
preferred-nodeaffinity-deploy-5bf4699fd9-phm8b 1/1 Running 0 5m32s 10.244.3.106 k8s-node01 <none> <none>
preferred-nodeaffinity-deploy-5bf4699fd9-xf87j 1/1 Running 0 5m32s 10.244.3.105 k8s-node01 <none> <none>
3. 创建标签 key 为 app, 值为 proxy 或者 web 的 Pod
cat app_proxy.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-proxy-pod
labels:
app: proxy
spec:
containers:
- name: app-proxy
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
4. 查看调度结果
kubectl apply -f app_proxy.yaml
# 具备标签 key 为 app,values 的 Pod 被调度到了 node03 上
kubectl get pods -owide | grep app-proxy
app-proxy-pod 1/1 Running 0 42s 10.244.5.102 k8s-node03 <none> <none>
# 如下 podAffinity 硬亲和性调度也被调度到此 Node
kubectl get pods -owide | grep podaff
podaffinity-required-pod 1/1 Running 0 2m30s 10.244.5.103 k8s-node03 <none> <none>
Pod 调度之 Pod 亲和性调度
podAffinity 介绍
出于高效通信的需求,我们要把几个 Pod 对象运行在比较近的位置,例如 APP Pod 与 DB Pod, 我们的 APP Pod 需要连接 DB Pod,这个时候就需要把这两个 Pod 运行在较近的位置以便于网络通信,一般可以按照区域、机房、地区等来划分。像这种类型就属于 Pod 的亲和性调度。但是有时候出于安全或者分布式考虑,也有可能将 Pod 运行在不同区域、不同机房,这个时候 Pod 的关系就是为反亲和性。
podAffinity 也被分为硬亲和性和软亲和性,其原理与 Node 中的硬亲和性及软亲和性一致。
硬亲和性(required):硬亲和性实现的是强制性规则,它是 Pod 调度时必须要满足的规则,而在不存在满足规则的 Node 时,Pod 对象会被置为 Pending 状态。
软亲和性(preferred):软亲和性规则实现的是一种柔性调度限制,它倾向于将 Pod 对象运行于某类特定节点之上,而调度器也将尽量满足此需求,但在无法满足需求时它将退而求其次地选择一个不匹配规则的节点之上。
定义 Pod 亲和性规则的关键点有两个:
1. 为节点配置合乎需求的标签。
2. 为 Pod 对象定义合理的标签选择器 labelSelector,从而能够基于标签选择器选择出符合需求的标签。
Pod 反亲和硬亲和性
Pod 硬亲和性调度使用
requiredDuringSchedulingIgnoredDuringExecution 属性进行定义,Pod 硬亲和性使用 topologyKey 参数来指定要运行在具备什么样标签 key 的 Node 上,然后再通过 labelSelector 选择你想关联 Pod 的标签,可能有点绕,下面看示例应该就明白了。
Pod 硬亲和性参数解析:
•labelSelector:标签选择器️。
•topologyKey:指定要将当前创建 Pod 运行在具备什么样的 Node 标签上,通常指定 Node 标签的 Key。
•namespaces:指定 labelSelector 应用于哪个名称空间,null 或空列表表示“此 pod 的名称空间”。
•matchExpressions:按照 Pod label 列出节点选择器列表。(与 matchLabels 是两种方式,不过结果是一至)。
•matchLabels:按照节点字段列出节点选择器列表。(与 matchExpressions 是两种方式,不过结果是一至)。
•key:指定要选择节点 label 的 key。
•values:指定要选择节点 label 的 value,值必须为数组 [“value”],如果操作符为 In 或者 Notin,value 则不能为空,如果操作符为 Exists 或者 DoesNotExist,value 则必须为空[], 如果操作符为 Gt 或 Lt,则 value 必须有单个元素,该元素将被解释为整数。
•operator:操作符,指定 key 与 value 的关系。
•In:key 与 value 同时存在,一个 key 多个 value 的情况下,value 之间就成了逻辑或效果。
•NotIn:label 的值不在某个列表中。
•Exists:只判断是否存在 key,不用关心 value 值是什么。
•DoesNotExist:某个 label 不存在。
•Gt:label 的值大于某个值。
•Lt:label 的值小于某个值
1. 为 Node 打上不同地区的标签 node01 标签为 beijing node02 标签为 shanghai node03 标签为 shenzhen
kubectl label node k8s-node01 zone=beijing
kubectl label node k8s-node02 zone=shanghai
kubectl label node k8s-node03 zone=shenzhen
2. 创建资源配置清单 下面清单中,Pod 首先会选择标签 key 为 zone 的 Node,我们上面做了三个 Node 标签 key 都为 zone,匹配之后,开始在标签 key 为 zone 的 Node 上寻找标签 key 为 app,values 为 proxy 或者 web 的 Pod,然后与其运行在那台 Node 之上。
cat podaffinity-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: podaffinity-required-pod
spec:
containers:
- name: nginx-containers
image: nginx:latest
affinity:
podAffinity:
#硬亲和性
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
#选择标签 key 为app,values为 proxy 或者 web 的Pod,然后与其运行在同一个 Node 上
- {key: app, operator: In, values: ["proxy","web"] }
#选择 key 为zone的Node
topologyKey: zone
3. 查看调度结果 如下可以看到,三台 Node 上没有标签 key 为 app,values 为 proxy 或者 web 的 Pod,我们采用的是硬亲和性,所以该 Pod 一直为 Pending 状态。
kubectl apply -f podaffinity-pod.yaml
kubectl get pods -o wide | grep podaffinity
podaffinity-required-pod 0/1 Pending 0 3m43s <none> <none> <none> <none>
4. 创建标签 key 为 app, 值为 proxy 或者 web 的 Pod
cat app_proxy.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-proxy-pod
labels:
app: proxy
spec:
containers:
- name: app-proxy
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
5. 查看调度结果
kubectl apply -f app_proxy.yaml
# 具备标签 key 为 app,values 的 Pod 被调度到了 node03 上
kubectl get pods -owide | grep app-proxy
app-proxy-pod 1/1 Running 0 42s 10.244.5.102 k8s-node03 <none> <none>
# 如下 podAffinity 硬亲和性调度也被调度到此 Node
kubectl get pods -owide | grep podaff
podaffinity-required-pod 1/1 Running 0 2m30s 10.244.5.103 k8s-node03 <none> <none>
Pod 软亲和性
Pod 软亲和性使用
preferredDuringSchedulingIgnoredDuringExecution 属性进行定义,Pod 软亲和性使用 podAffinityTerm 属性来挑选 Pod 标签,当调度的 Pod 对象不再是”必须”,而是“应该”放置于某些特性节点之上,当条件不满足时,它也能够接受编排于其它不符合条件的节点之上,另外,它还为每种倾向性提供了 weight 属性以便用户定义其优先级,取值范围是 1 -100,数字越大优先级越高。
•Pod 软亲和性参数解析:
•podAffinityTerm:Pod 亲和性选择器。
•weight:在 1 -100 范围内,与匹配相应的节点选项相关联的权重。
•labelSelector:标签选择器。
•matchExpressions:按照 Pod label 列出节点选择器列表。(与 matchLabels 是两种方式,不过结果是一至)。
•matchLabels:按照节点字段列出节点选择器列表。(与 matchExpressions 是两种方式,不过结果是一至)。
•key:指定要选择节点 label 的 key。
•values:指定要选择节点 label 的 value,值必须为数组 [“value”],如果操作符为 In 或者 Notin,value 则不能为空,如果操作符为 Exists 或者 DoesNotExist,value 则必须为空[], 如果操作符为 Gt 或 Lt,则 value 必须有单个元素,该元素将被解释为整数。
•operator:操作符,指定 key 与 value 的关系。
•In:key 与 value 同时存在,一个 key 多个 value 的情况下,value 之间就成了逻辑或效果
•NotIn:label 的值不在某个列表中。
•Exists:只判断是否存在 key,不用关心 value 值是什么。
•DoesNotExist:某个 label 不存在。
•Gt:label 的值大于某个值。
•Lt:label 的值小于某个值
1. 创建资源配置清单
下面创建一个 Pod 软亲和性资源配置清单,定义了两组亲和性判断机制,一个是选择 cache Pod 所在节点的 zone 标签,并赋予权重为 80,另一个是选择 db Pod 所在的 zone 标签,权重为 20,调度器首先会将 Pod 调度到具有 zone 标签 key 的 Node 上,然后将多数 Pod 调度到具备标签为 app=cache 的 Pod 同节点,其次调度到具备标签 app=db 的 Pod 同节点。
如果 Node 上的 Pod 都具备 app=cache 和 app=db,那么根据 Pod 软亲和性策略,调度器将退而求其次的将 Pod 调度到其它 Node,如果甚至连 Node 都具备标签 zone 键,那么根据软亲和策略,调度器还是会退而求其次的将 Pod 调度到不存在 zone 键的 Node 上。
cat podaffinity-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: podaffinity-perferred-pod
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["cache"] }
topologyKey: zone
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"] }
topologyKey: zone
containers:
- name: myapp
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
2. 查看调度结果
#Pod调度结果为 node01 两个,node03 一个
kubectl get pods -o wide | grep podaffinity-perferred-pod
podaffinity-perferred-pod-7cddc8c964-5tfr2 1/1 Running 0 12m 10.244.5.106 k8s-node03 <none> <none>
podaffinity-perferred-pod-7cddc8c964-kqsmk 1/1 Running 0 12m 10.244.3.109 k8s-node01 <none> <none>
podaffinity-perferred-pod-7cddc8c964-wpqqw 1/1 Running 0 12m 10.244.3.110 k8s-node01 <none> <none>
#以下三个 Node 都具备标签键为 zone,但是这三个 Node 上没有 Pod 标签为 app=cache 及 app=db,所以上面的调度策略在选择 Pod 标签的时候进行退步才得以将 Pod 调度到 Node01 和Node03
k8sops@k8s-master01:~/manifests/pod$ kubectl get nodes -l zone -L zone
NAME STATUS ROLES AGE VERSION ZONE
k8s-node01 Ready <none> 28d v1.18.2 beijing
k8s-node02 Ready <none> 28d v1.18.2 shanghai
k8s-node03 Ready <none> 29d v1.18.2 shenzhen
Pod 调度之 Pod 反亲和性调度
podAntiAffinity 介绍
Pod 反亲和性 podAntiAffinity 用于定义 Pod 对象的亲和约束,Pod 反亲和性与 Pod 亲和性相反,Pod 亲和性是将有密切关联的 Pod 运行到同一平面、同一个区域或者同一台机器上,而反亲和性是将 Pod 运行在不同区域、不同机器上,Pod 反亲和性调度一般用于分散同一类应用的 Pod 对象等。
podAntiAffinity 也被分为硬亲和性和软亲和性,其原理与 Node 中的硬亲和性及软亲和性一致。
硬亲和性(required):硬亲和性实现的是强制性规则,它是 Pod 调度时必须要满足的规则,而在不存在满足规则的 Node 时,Pod 对象会被置为 Pending 状态。
软亲和性(preferred):软亲和性规则实现的是一种柔性调度限制,它倾向于将 Pod 对象运行于某类特定节点之上,而调度器也将尽量满足此需求,但在无法满足需求时它将退而求其次地选择一个不匹配规则的节点之上。
帮助文档:kubectl explain
pods.spec.affinity.podAntiAffinity
podAntiAffinity 示例
1. 创建资源配置清单
以下创建了 4 个 Pod,自身标签为 app=myapp,使用 Pod 反亲和的硬亲和性,需要运行在具备标签 key 为 zone 的 Node 上,然后不运行在具备标签为 app=myapp 的 Pod 同台 Node 上,我们下面启动了 4 个 Pod,一共有三个 Node,前三个 Pod 都会被分别调度到不同的三台 Node 上(因为采用的是反亲和性,还是硬性,所以相同标签的 Pod 不会调度到同一台 Node),最后一个 Pod 将无家可归,最后无法调度。
cat podAntiAffinity-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: podantiaffinity-perferred-pod
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["myapp"] }
topologyKey: zone
containers:
- name: myapp
image: busybox:latest
command: ["/bin/sh", "-c", "tail -f /etc/passwd" ]
2. 创建 Pod 对象
kubectl apply -f podAntiAffinity-deploy.yaml
3. 查看调度结果
kubectl get pods -L app -l app=myapp -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES APP
podantiaffinity-perferred-pod-6576cf75c8-5bw9f 0/1 Pending 0 80s <none> <none> <none> <none> myapp
podantiaffinity-perferred-pod-6576cf75c8-bxp4k 1/1 Running 0 80s 10.244.2.88 k8s-node02 <none> <none> myapp
podantiaffinity-perferred-pod-6576cf75c8-d2fcm 1/1 Running 0 80s 10.244.5.107 k8s-node03 <none> <none> myapp
podantiaffinity-perferred-pod-6576cf75c8-dghr9 1/1 Running 0 80s 10.244.3.111 k8s-node01 <none> <none> myapp
4. 查看 Node 标签
kubectl get nodes -L zone -l zone
NAME STATUS ROLES AGE VERSION ZONE
k8s-node01 Ready <none> 28d v1.18.2 beijing
k8s-node02 Ready <none> 28d v1.18.2 shanghai
k8s-node03 Ready <none> 29d v1.18.2 shenzhen
文章来源于网络,侵删!