Kubernetes中Pod的调度策略
1、Pod调度
在 Kubernetes 平台上,我们很少会直接创建一个 Pod,在大多数情况下会通过 RC、Deployment、
DaemonSet、Job 等控制器完成对一组 Pod 副本的创建、调度及全生命周期的自动控制任务。
在最早的 Kubernetes 版本里是没有这么多 Pod 副本控制器的,只有一个 Pod 副本控制器RC(Replication
Controller),这个控制器是这样设计实现的:RC 独立于所控制的 Pod,并通过 Label 标签这个松耦合关联关系控
制目标 Pod 实例的创建和销毁,随着 Kubernetes 的发展,RC 也出现了新的继任者——Deployment,用于更加
自动地完成 Pod 副本的部署、版本更新、回滚等功能。
严谨地说,RC 的继任者其实并不是 Deployment,而是 ReplicaSet,因为 ReplicaSet 进一步增强了 RC 标签选择
器的灵活性。之前 RC 的标签选择器只能选择一个标签,而 ReplicaSet 拥有集合式的标签选择器,可以选择多个
Pod 标签,如下所示:
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier,operator: In,values: [frontend]}
与 RC 不同,ReplicaSet 被设计成能控制多个不同标签的 Pod 副本。一种常见的应用场景是,应用 MyApp 目前发
布了 v1 与 v2 两个版本,用户希望 MyApp 的 Pod 副本数保持为 3 个,可以同时包含 v1 和 v2 版本的 Pod,就可
以用 ReplicaSet 来实现这种控制,写法如下:
selector:
matchLabels:
version: v2
matchExpressions:
- {key: version,operator: In,values: [v1,v2]}
其实,Kubernetes 的滚动升级就是巧妙运用 ReplicaSet 的这个特性来实现的,同时,Deployment 也是通过
ReplicaSet 来实现 Pod 副本自动控制功能的。我们不应该直接使用底层的 ReplicaSet 来控制 Pod 副本,而应该
使用管理 ReplicaSet 的 Deployment 对象来控制副本,这是来自官方的建议。
在大多数情况下,我们希望 Deployment 创建的 Pod 副本被成功调度到集群中的任何一个可用节点,而不关心具
体会调度到哪个节点。但是,在真实的生产环境中的确也存在一种需求:希望某种 Pod 的副本全部在指定的一个
或者一些节点上运行,比如希望将 MySQL 数据库调度到一个具有 SSD 磁盘的目标节点上,此时 Pod 模板中的
NodeSelector 属性就开始发挥作用了,上述 MySQL 定向调度案例的实现方式可分为以下两步。
(1)把具有 SSD 磁盘的 Node 都打上自定义标签 disk=ssd。
(2)在 Pod 模板中设定 NodeSelector 的值为 disk: ssd。
如此一来,Kubernetes 在调度 Pod 副本的时候,就会先按照 Node 的标签过滤出合适的目标节点,然后选择一
个最佳节点进行调度。
上述逻辑看起来既简单又完美,但在真实的生产环境中可能面临以下令人尴尬的问题。
(1)如果 NodeSelector 选择的 Label 不存在或者不符合条件,比如这些目标节点此时宕机或者资源不足,该怎
么办?
(2)如果要选择多种合适的目标节点,比如 SSD 磁盘的节点或者超高速硬盘的节点,该怎么办? Kubernetes 引
入了 NodeAffinity(节点亲和性设置)来解决该需求。
在真实的生产环境中还存在如下所述的特殊需求。
(1)不同 Pod 之间的亲和性(Affinity)。比如 MySQL 数据库与 Redis 中间件不能被调度到同一个目标节点上,或
者两种不同的 Pod 必须被调度到同一个 Node 上,以实现本地文件共享或本地网络通信等特殊需求,这就是
PodAffinity 要解决的问题。
(2)有状态集群的调度。对于ZooKeeper、ElasticSearch、MongoDB、Kafka 等有状态集群,虽然集群中的每
个 Worker 节点看起来都是相同的,但每个 Worker 节点都必须有明确的、不变的唯一ID(主机名或IP地址),这些
节点的启动和停止次序通常有严格的顺序。此外,由于集群需要持久化保存状态数据,所以集群中的 Worker 节点
对应的 Pod 不管在哪个 Node 上恢复,都需要挂载原来的 Volume,因此这些 Pod 还需要捆绑具体的 PV。针对这
种复杂的需求,Kubernetes 提供了 StatefulSet 这种特殊的副本控制器来解决问题,在 Kubernetes 1.9 版本发布
后,StatefulSet 才可用于正式生产环境中。
(3)在每个 Node 上调度并且仅仅创建一个 Pod 副本。这种调度通常用于系统监控相关的 Pod,比如主机上的
日志采集、主机性能采集等进程需要被部署到集群中的每个节点,并且只能部署一个副本,这就是 DaemonSet 这
种特殊 Pod 副本控制器所解决的问题。
(4)对于批处理作业,需要创建多个 Pod 副本来协同工作,当这些 Pod 副本都完成自己的任务时,整个批处理
作业就结束了。这种 Pod 运行且仅运行一次的特殊调度,用常规的 RC 或者 Deployment 都无法解决,所以
Kubernetes 引入了新的 Pod 调度控制器 Job 来解决问题,并继续延伸了定时作业的调度控制器 CronJob。
与单独的 Pod 实例不同,由 RC、ReplicaSet、Deployment、DaemonSet 等控制器创建的 Pod 副本实例都是归
属于这些控制器的,这就产生了一个问题:控制器被删除后,归属于控制器的Pod副本该何去何从?在 Kubernetes
1.9 之前,在 RC 等对象被删除后,它们所创建的 Pod 副本都不会被删除;在 Kubernetes 1.9 以后,这些 Pod 副
本会被一并删除。如果不希望这样做,则可以通过 kubectl 命令的 --cascade=false 参数来取消这一默认特性:
$ kubectl delete replicaset my-repset --cascade=false
接下来深入理解和实践这些 Pod 调度控制器的各种功能和特性。
2、ReplicationController
RC 是 kubernetes 系统中的核心概念之一,简单来说,它其实定义了一个期望的场景,即声明某种 Pod 的副本数
量在任意时刻都符合某个预期值,所以 RC 的定义包括如下几个部分。
-
Pod 期待的副本数量。
-
用于筛选目标 Pod 的 Label Selector。
-
当 Pod 的副本数量小于预期数量时,用于创建新 Pod 的 Pod 模板(template)。
下面是一个完整的 RC 定义的例子,即确保拥有 tier=frontend 标签的这个 Pod(运行 Tomcat 容器)在整个
kubernetes 集群中始终只有一个副本。
配置文件 017-replication-controller-demo.yaml
的内容为:
apiVersion: v1
kind: ReplicationController
metadata:
name: frontend
spec:
replicas: 1
selector:
tier: frontend
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
[root@master cha3]# kubectl create -f 017-replication-controller-demo.yaml
replicationcontroller/frontend created
[root@master cha3]# kubectl get rc
NAME DESIRED CURRENT READY AGE
frontend 1 1 1 14s
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend-2th9d 1/1 Running 0 30s
在我们定义了一个 RC 并将其提交到 kubernetes 集群中后,Master 上的 Controller Manager 组件就得到通知,
定期巡检系统中当前存活的目标 Pod,并确保目标 Pod 实例的数量刚好等于此 RC 的期望值,如果有过多的 Pod
副本在运行,系统就会停掉一些 Pod,否则系统会再自动创建一些 Pod。可以说,通过 RC,kubernetes 实现了
用户应用集群的高可用性,并且大大减少了系统管理员在传统IT环境中需要完成的许多手工运维工作(如主机监控
脚本、应用监控脚本、故障恢复脚本等)。
下面以有 3 个 Node 的集群为例,说明 kubernetes 如何通过 RC 来实现 Pod 副本数量自动控制的机制。假如在
我们的 RC 里定义 redis-slave 这个 Pod 需要保持两个副本,系统将可能在其中的两个 Node 上创建 Pod。在
Node1 和 Node2 上各创建一个 Pod。假设 Node 2 上的 Pod 2 意外终止,则根据 RC 定义的 replicas 数量 2,
kubernetes 将会自动创建并启动一个新的 Pod,以保证在整个集群中始终有两个 redis-slave Pod 运行。系统可
能选择 Node 3 或者 Node 1 来创建一个新 的 Pod。
此外,在运行时,我们可以通过修改 RC 的副本数量,来实现 Pod 的动态缩放(Scaling),这可以通过执行
kubectl scale 命令来一键完成:
[root@master cha3]# kubectl scale rc frontend --replicas=3
replicationcontroller/frontend scaled
[root@master cha3]# kubectl get rc
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 7m58s
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend-2th9d 1/1 Running 0 8m3s
frontend-bd6k8 1/1 Running 0 20s
frontend-zjpch 1/1 Running 0 19s
kubectl 提供了 delete 命令来一次性删除 RC 和 RC 控制的全部 Pod。需要注意的是,删除 RC 会删除通过该 RC
已创建好的 Pod。为了删除所有 Pod,也可以设置 replicas 的值为 0,然后更新该 RC。
# kubectl delete
[root@master cha3]# kubectl delete rc frontend
replicationcontroller "frontend" deleted
[root@master cha3]# kubectl get rc
No resources found in default namespace.
[root@master cha3]# kubectl get pod
No resources found in default namespace.
# 重新创建rc并且扩容为3,然后修改replicas为0
[root@master cha3]# kubectl replace -f 017-replication-controller-demo.yaml
replicationcontroller/frontend replaced
[root@master cha3]# kubectl get pod
No resources found in default namespace.
[root@master cha3]# kubectl get rc
NAME DESIRED CURRENT READY AGE
frontend 0 0 0 2m10s
应用升级时,通常会使用一个新的容器镜像版本替代旧版本。我们希望系统平滑升级,比如在当前系统中有 10 个
对应的旧版本的 Pod,则最佳的系统升级方式是旧版本的 Pod 每停止一个,就同时创建一个新版本的 Pod,在整
个升级过程中此消彼长,而运行中的 Pod 数量始终是 10 个,几分钟以后,当所有的 Pod 都已经是新版本时,系
统升级完成。通过 RC 机制,kubernetes 很容易就实现了这种高级实用的特性,被称为滚动升级(Rolling
Update)。
3、ReplicaSet
Replication Controller 由于与 kubernetes 代码中的模块 Replication Controller 同名,同时Replication
Controller 无法准确表达它的本意,所以在 kubernetes 1.2 中,升级为另外一个新概念—Replica Set,官方解
释其为下一代的 RC。Replica Set 与 RC 当前的唯一区别是,Replica Set 支持基于集合的 Label selector(Set-
based selector),而 RC 只支持基于等式的 Label Selector(equality-based selector),这使得 Replica Set 的功
能更强。下面是等价于之前 RC 例子的 Replica Set 的定义。
配置文件 018-replica-set-demo.yaml
文件的内容:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
spec:
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
replicas: 3
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
[root@master cha3]# kubectl create -f 018-replica-set-demo.yaml
replicaset.apps/frontend created
[root@master cha3]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
frontend 3 3 3 59s tomcat-demo tomcat tier=frontend,tier in (frontend)
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend-lkwt8 1/1 Running 0 29s
frontend-n6tw7 1/1 Running 0 29s
frontend-sqfwf 1/1 Running 0 29s
kubectl 命令行工具适用于 RC 的绝大部分命令同样适用于 Replica Set。此外,我们当前很少单独使用 Replica
Set,它主要被 Deployment 这个更高层的资源对象所使用,从而形成一整套 Pod 创建、删除、更新的编排机
制。我们在使用 Deployment 时,无须关心它是如何创建和维护 Replica Set 的,这一切都是自动发生的。
Replica Set 与 Deployment 这两个重要的资源对象逐步替代了之前 RC 的作用,是 kubernetes 1.3 里 Pod 自动
扩容(伸缩)这个告警功能实现的基础,也将继续在 kubernetes 未来的版本中发挥重要的作用。
最后总结一下RC(Replica Set)的一些特性与作用。
-
在大多数情况下,我们通过定义一个 RC 实现 Pod 的创建及副本数量的自动控制。
-
在 RC 里包括完整的 Pod 定义模板。
-
RC 通过 Label Selector 机制实现对 Pod 副本的自动控制。
-
通过改变 RC 里的 Pod 副本数量,可以实现 Pod 的扩容或缩容。
-
通过改变 RC 里 Pod 模板中的镜像版本,可以实现 Pod 的滚动升级。
4、Deployment
Deployment 是 kubernetes 在 1.2 版本中引入的新概念,用于更好地解决 Pod 的编排问题。为此,Deployment
在内部使用了 Replica Set 来实现目的,无论从 Deployment 的作用与目的、YAML 定义,还是从它的具体命令行
操作来看,我们都可以把它看作 RC 的一次升级,两者的相似度超过 90%。
Deployment 相对于 RC 的一个最大升级是我们可以随时知道当前 Pod 部署的进度。实际上由于一个 Pod 的创
建、调度、绑定节点及在目标 Node 上启动对应的容器这一完整过程需要一定的时间,所以我们期待系统启动 N
个 Pod 副本的目标状态,实际上是一个连续变化的部署过程导致的最终状态。
Deployment 的典型使用场景有以下几个。
-
创建一个 Deployment 对象来生成对应的 Replica Set 并完成 Pod 副本的创建。
-
检查 Deployment 的状态来看部署动作是否完成( Pod 副本数量是否达到预期的值)。
-
更新 Deployment 以创建新的 Pod (比如镜像升级)。
-
如果当前 Deployment 不稳定,则回滚到一个早先的 Deployment 版本。
-
暂停 Deployment 以便于一次性修改多个 PodTemplateSpec 的配置项,之后再恢复 Deployment,进行新的
发布。
-
扩展 Deployment 以应对高负载。
-
查看 Deployment 的状态,以此作为发布是否成功的指标。
-
清理不再需要的旧版本 ReplicaSets。
除了 API 声明与 Kind 类型等有所区别,Deployment 的定义与 Replica Set 的定义很类似:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-repset
下面通过运行一些例子来直观地感受Deployment的概念。创建一个名为 019-tomcat-deployment.yaml
的
Deployment 描述文件,内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier,operator: In,values: [frontend]}
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
运行下述命令创建 Deployment:
[root@master cha3]# kubectl create -f 019-tomcat-deployment.yaml
deployment.apps/frontend created
运行下述命令查看 Deployment 的信息:
[root@master cha3]# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
frontend 3/3 3 3 20s
对上述输出中涉及的数量解释如下。
-
READY 包含CURRENT/DESIRED。
-
DESIRED:Pod 副本数量的期望值,即在 Deployment 里定义的 Replica。
-
CURRENT:当前 Replica 的值,实际上是 Deployment 创建的 Replica Set 里的 Replica 值,这个值不断增
加,直到达到 DESIRED 为止,表明整个部署过程完成。
-
UP-TO-DATE:最新版本的 Pod 的副本数量,用于指示在滚动升级的过程中,有多少个 Pod 副本已经成功升
级。
-
AVAILABLE:当前集群中可用的 Pod 副本数量,即集群中当前存活的 Pod 数量。
运行下述命令查看对应的 Replica Set,我们看到它的命名与 Deployment 的名称有关系:
[root@master cha3]# kubectl get rs
NAME DESIRED CURRENT READY AGE
frontend-7d7c57fc94 3 3 3 31s
运行下述命令查看创建的 Pod,我们发现 Pod 的命名以 Deployment 对应的 Replica Set 的名称为前缀,这种命
名很清晰地表明了一个 Replica Set 创建了哪些 Pod,对于 Pod 滚动升级这种复杂的过程来说,很容易排查错误:
[root@master cha3]# kubectl get pods
NAME READY STATUS RESTARTS AGE
frontend-7d7c57fc94-2sfbh 1/1 Running 0 59s
frontend-7d7c57fc94-fl492 1/1 Running 0 59s
frontend-7d7c57fc94-vcr9w 1/1 Running 0 59s
运行 kubectl describe deployments,可以清楚地看到 Deployment 控制的 Pod 的水平扩展过程。
[root@master cha3]# kubectl describe deployments frontend
Name: frontend
Namespace: default
CreationTimestamp: Tue, 04 Jul 2023 21:31:20 +0800
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 1
Selector: tier=frontend,tier in (frontend)
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=app-demo
tier=frontend
Containers:
tomcat-demo:
Image: tomcat
Port: 8080/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: frontend-7d7c57fc94 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 84s deployment-controller Scaled up replica set frontend-7d7c57fc94 to 3
Pod 的管理对象,除了 RC 和 Deployment,还包括 ReplicaSet、DaemonSet、StatefulSet、Job 等,分别用
于不同的应用场景中,将在后面进行详细介绍。
Deployment 或 RC 的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内
始终维持用户指定的副本数量。
下面是一个 Deployment 配置的例子,使用这个配置文件可以创建一个 ReplicaSet,这个 ReplicaSet 会创建 3 个
Nginx 应用的 Pod。
配置文件 020-nginx-deployment.yaml
的内容为:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
运行 kubectl create 命令创建这个 Deployment:
[root@master cha3]# kubectl create -f 020-nginx-deployment.yaml
deployment.apps/nginx-deployment created
查看 Deployment 的状态:
[root@master cha3]# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 20s
该状态说明 Deployment 已创建好所有 3 个副本,并且所有副本都是最新的可用的。
通过运行 kubectl get rs 和 kubectl get pods 可以查看已创建的ReplicaSet(RS) 和 Pod 的信息。
[root@master cha3]# kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-5d59d67564 3 3 3 60s
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-5d59d67564-4g297 1/1 Running 0 62s
nginx-deployment-5d59d67564-hxz8s 1/1 Running 0 62s
nginx-deployment-5d59d67564-pfxxx 1/1 Running 0 62s
[root@master cha3]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-5d59d67564-4g297 1/1 Running 0 2m23s 10.244.140.70 slave2 <none> <none>
nginx-deployment-5d59d67564-hxz8s 1/1 Running 0 2m23s 10.244.140.204 slave1 <none> <none>
nginx-deployment-5d59d67564-pfxxx 1/1 Running 0 2m23s 10.244.140.205 slave1 <none> <none>
从调度策略上来说,这 3 个 Nginx Pod 由系统全自动完成调度。它们各自最终运行在哪个节点上,完全由 Master
的 Scheduler 经过一系列算法计算得出,用户无法干预调度过程和结果。
除了使用系统自动调度算法完成一组 Pod 的部署,Kubernetes 也提供了多种丰富的调度策略,用户只需在 Pod
的定义中使用 NodeSelector
、NodeAffinity
、PodAffinity
、Pod
驱逐等更加细粒度的调度策略设置,就能
完成对 Pod 的精准调度,下面对这些策略进行说明。
5、NodeSelector-Node定向调度
Kubernetes Master 上的 Scheduler 服务( kube-scheduler 进程)负责实现 Pod 的调度,整个调度过程通过执行一
系列复杂的算法,最终为每个 Pod 都计算出一个最佳的目标节点,这一过程是自动完成的,通常我们无法知道
Pod 最终会被调度到哪个节点上。在实际情况下,也可能需要将 Pod 调度到指定的一些 Node 上,可以通过
Node 的标签(Label)和 Pod 的 nodeSelector 属性相匹配,来达到上述目的。
(1)首先通过 kubectl label 命令给目标 Node 打上一些标签:
$ kubectl label nodes <node-name> <label-key>=<label-value>
这里,我们为 slave1 节点打上一个 zone=north
标签,表明它是北方的一个节点:
[root@master cha3]# kubectl label nodes slave1 zone=north
node/slave1 labeled
[root@master cha3]# kubectl get nodes --show-labels | grep slave1
slave1 Ready <none> 99m v1.21.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=slave1,kubernetes.io/os=linux,zone=north
上述命令行操作也可以通过修改资源定义文件的方式,并执行 kubectl replace -f xxx.yaml 命令来完成。
(2)然后,在 Pod 的定义中加上 nodeSelector
的设置,以 021-redis-replicationcontroller.yaml
为
例:
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master
labels:
name: redis-master
spec:
replicas: 3
selector:
name: redis-master
template:
metadata:
labels:
name: redis-master
spec:
containers:
- name: master
image: kubeguide/redis-master
ports:
- containerPort: 6379
nodeSelector:
zone: north
运行 kubectl create -f 命令创建 Pod,scheduler 就会将该 Pod 调度到拥有 zone=north
标签的 Node 上。
[root@master cha3]# kubectl create -f 021-redis-replicationcontroller.yaml
replicationcontroller/redis-master created
使用 kubectl get pods -o wide 命令可以验证Pod所在的Node:
[root@master cha3]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-master-g9d6r 1/1 Running 0 25s 10.244.140.211 slave1 <none> <none>
redis-master-hkxxj 1/1 Running 0 25s 10.244.140.209 slave1 <none> <none>
redis-master-hnpk2 1/1 Running 0 25s 10.244.140.210 slave1 <none> <none>
如果我们给多个 Node 都定义了相同的标签(例如 zone=north
),则 scheduler 会根据调度算法从这组 Node 中
挑选一个可用的 Node 进行 Pod 调度。
通过基于 Node 标签的调度方式,我们可以把集群中具有不同特点的 Node 都贴上不同的标签,例如
role=frontend
role=backend
role=database
等标签,在部署应用时就可以根据应用的需求设置
NodeSelector 来进行指定 Node 范围的调度。
需要注意的是,如果我们指定了 Pod 的 nodeSelector 条件,且在集群中不存在包含相应标签的 Node,则即使在
集群中还有其他可供使用的 Node,这个 Pod 也无法被成功调度。
除了用户可以自行给 Node 添加标签,Kubernetes 也会给 Node 预定义一些标签,包括:
-
kubernetes.io/hostname
-
beta.kubernetes.io/os
(从1.14版本开始更新为稳定版,到1.18版本删除) -
beta.kubernetes.io/arch
(从1.14版本开始更新为稳定版,到1.18版本删除) -
kubernetes.io/os
(从1.14版本开始启用) -
kubernetes.io/arch
(从1.14版本开始启用)
[root@master cha3]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready control-plane,master 107m v1.21.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
master2 Ready control-plane,master 104m v1.21.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master2,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
slave1 Ready <none> 105m v1.21.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=slave1,kubernetes.io/os=linux,zone=north
slave2 Ready <none> 105m v1.21.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=slave2,kubernetes.io/os=linux
用户也可以使用这些系统标签进行 Pod 的定向调度。
NodeSelector 通过标签的方式,简单实现了限制 Pod 所在节点的方法。亲和性调度机制则极大扩展了 Pod 的调
度能力,主要的增强功能如下。
-
更具表达力(不仅仅是符合全部的简单情况)。
-
可以使用软限制、优先采用等限制方式,代替之前的硬限制,这样调度器在无法满足优先需求的情况下,会退
而求其次,继续运行该 Pod。
-
可以依据节点上正在运行的其他 Pod 的标签来进行限制,而非节点本身的标签。这样就可以定义一种规则来
描述 Pod 之间的亲和或互斥关系。
亲和性调度功能包括节点亲和性(NodeAffinity)和 Pod 亲和性(PodAffinity)两个维度的设置。节点亲和性与
NodeSelector 类似,增强了上述前两点优势;Pod 的亲和与互斥限制则通过 Pod 标签而不是节点标签来实现。
NodeSelector 将会继续使用,随着节点亲和性越来越能够表达 nodeSelector 的功能,最终 NodeSelector 会被
废弃。
6、NodeAffinity-Node亲和性调度
NodeAffinity 意为 Node 亲和性的调度策略,是用于替换 NodeSelector 的全新调度策略。目前有两种节点亲和
性表达。
-
RequiredDuringSchedulingIgnoredDuringExecution
:必须满足指定的规则才可以调度 Pod 到 Node 上(功能与 nodeSelector 很像,但是使用的是不同的语法),相当于硬限制。
-
PreferredDuringSchedulingIgnoredDuringExecution
:强调优先满足指定规则,调度器会尝试调度 Pod到 Node 上,但并不强求,相当于软限制。多个优先级规则还可以设置权重(weight)值,以定义执行的先后顺
序。
IgnoredDuringExecution
的意思是:如果一个 Pod 所在的节点在 Pod 运行期间标签发生了变更,不再符合该
Pod 的节点亲和性需求,则系统将忽略 Node 上 Label 的变化,该 Pod 能继续在该节点运行。
下面的例子设置了 NodeAffinity 调度的如下规则。
-
requiredDuringSchedulingIgnoredDuringExecution
要求只运行在 amd64 的节点上(beta.kubernetes.io/arch In amd64)。
-
preferredDuringSchedulingIgnoredDuringExecution
的要求是尽量运行在磁盘类型为ssd(disk-type Inssd)的节点上。
配置文件 022-with-node-affinity.yaml
的内容为:
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
containers:
- name: with-node-affinity
image: google/pause
[root@master cha3]# kubectl create -f 022-with-node-affinity.yaml
pod/with-node-affinity created
[root@master cha3]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
with-node-affinity 1/1 Running 0 43s 10.244.140.214 slave1 <none> <none>
从上面的配置中可以看到In操作符,NodeAffinity 语法支持的操作符包括 In
、NotIn
、Exists
、
DoesNotExist
、Gt
、Lt
。虽然没有节点排斥功能,但是用 NotIn 和 DoesNotExist 就可以实现排斥的功能了。
NodeAffinity 规则设置的注意事项如下:
-
如果同时定义了 nodeSelector 和 nodeAffinity,那么必须两个条件都得到满足,Pod 才能最终运行在指定的
Node 上。
-
如果 nodeAffinity 指定了多个 nodeSelectorTerms,那么其中一个能够匹配成功即可。
-
如果在 nodeSelectorTerms 中有多个 matchExpressions,则一个节点必须满足所有 matchExpressions 才
能运行该 Pod。
7、PodAffinity-Pod亲和与互斥调度策略
Pod 间的亲和与互斥从 Kubernetes 1.4 版本开始引入。这一功能让用户从另一个角度来限制 Pod 所能运行的节
点:根据在节点上正在运行的 Pod 的标签而不是节点的标签进行判断和调度,要求对节点和 Pod 两个条件进行匹
配。这种规则可以描述为:如果在具有标签X的 Node 上运行了一个或者多个符合条件Y的 Pod,那么 Pod 应该
(如果是互斥的情况,那么就变成拒绝)运行在这个 Node 上。
这里X指的是一个集群中的节点、机架、区域等概念,通过 Kubernetes 内置节点标签中的 key 来进行声明。这个
key 的名字为 topologyKey,意为表达节点所属的 topology 范围。
-
kubernetes.io/hostname
-
failure-domain.beta.kubernetes.io/zone
-
failure-domain.beta.kubernetes.io/region
与节点不同的是,Pod 是属于某个命名空间的,所以条件Y表达的是一个或者全部命名空间中的一个 Label
Selector。
和节点亲和相同,Pod 亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution
和
preferredDuringSchedulingIgnoredDuringExecution
。Pod 的亲和性被定义于 PodSpec 的 affinity 字段下
的 podAffinity 子字段中。Pod 间的互斥性则被定义于同一层次的 podAntiAffinity 子字段中。
下面通过实例来说明 Pod 间的亲和性和互斥性策略设置。
7.1 参照目标Pod
首先,创建一个名为 pod-flag 的 Pod,带有标签 security=S1
和 app=nginx
,后面的例子将使用 pod-flag 作
为 Pod 亲和与互斥的目标 Pod。
配置文件 023-affinity.yaml
的内容为:
# affinity
apiVersion: v1
kind: Pod
metadata:
name: pod-flag
labels:
security: "S1"
app: "nginx"
spec:
containers:
- name: nginx
image: nginx
[root@master cha3]# kubectl create -f 023-pod-flag.yaml
pod/pod-flag created
[root@master cha3]# kubectl get pod -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-flag 1/1 Running 0 53s 10.244.140.65 slave2 <none> <none> app=nginx,security=S1
7.2 Pod的亲和性调度podAffinity
下面创建第2个 Pod 来说明 Pod 的亲和性调度,这里定义的亲和标签是 security=S1
,对应上面的
Pod "pod-flag"
,topologyKey 的值被设置为kubernetes.io/hostname
。
配置文件 024-pod-affinity.yaml
的内容为:
# affinity
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: google/pause
[root@master cha3]# kubectl create -f 024-pod-affinity.yaml
pod/pod-affinity created
[root@master cha3]# kubectl get pod -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-affinity 1/1 Running 0 27s 10.244.140.66 slave2 <none> <none> <none>
pod-flag 1/1 Running 0 36m 10.244.140.65 slave2 <none> <none> app=nginx,security=S1
创建 Pod 之后,使用 kubectl get pods -o wide 命令可以看到,这两个 Pod 在同一个 Node 上运行。
有兴趣的读者还可以测试一下,在创建这个 Pod 之前,删掉这个节点的 kubernetes.io/hostname 标签,重复上
面的创建步骤,将会发现 Pod 一直处于 Pending 状态,这是因为找不到满足条件的 Node 了。
7.3 Pod的互斥性调度
[root@master cha3]# kubectl label nodes slave1 zone=north
node/slave1 labeled
新建参考 pod,配置文件 025-pod-flag.yaml
的内容为:
apiVersion: v1
kind: Pod
metadata:
name: pod-flag2
labels:
security: "S1"
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
zone: north
[root@master cha3]# kubectl create -f 025-pod-flag.yaml
pod/pod-flag2 created
[root@master cha3]# kubectl get pod -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-affinity 1/1 Running 0 35m 10.244.140.66 slave2 <none> <none> <none>
pod-flag 1/1 Running 0 71m 10.244.140.65 slave2 <none> <none> app=nginx,security=S1
pod-flag2 1/1 Running 0 31s 10.244.140.217 slave1 <none> <none> security=S1
创建第3个 Pod,我们希望它不与目标 Pod 运行在同一个 Node 上。
配置文件 026-anti-affinity.yaml
的内容为:
# anti-affinity
apiVersion: v1
kind: Pod
metadata:
name: anti-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- name: anti-affinity
image: google/pause
[root@master cha3]# kubectl create -f 026-anti-affinity.yaml
pod/anti-affinity created
[root@master cha3]# kubectl get pod -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
anti-affinity 1/1 Running 0 5s 10.244.140.218 slave1 <none> <none> <none>
pod-affinity 1/1 Running 0 35m 10.244.140.66 slave2 <none> <none> <none>
pod-flag 1/1 Running 0 72m 10.244.140.65 slave2 <none> <none> app=nginx,security=S1
pod-flag2 1/1 Running 0 86s 10.244.140.217 slave1 <none> <none> security=S1
这里要求这个新 Pod 与 security=S1
的 Pod 为同一个 Node,但是不与 app=nginx
的 Pod 为同一个 Node。
创建 Pod 之后,同样用 kubectl get pods -o wide 来查看,会看到新的 Pod 被调度到了 slave1 上。
与节点亲和性类似,Pod 亲和性的操作符也包括 In、NotIn、Exists、DoesNotExist、Gt、Lt。
原则上,topologyKey 可以使用任何合法的标签 Key 赋值,但是出于性能和安全方面的考虑,对 topologyKey 有
如下限制。
-
在 Pod 亲和性和 RequiredDuringScheduling 的 Pod 互斥性的定义中,不允许使用空的 topologyKey。
-
如果 Admission controller 包含了 LimitPodHardAntiAffinityTopology,那么针对 Required
DuringScheduling 的 Pod 互斥性定义就被限制为 kubernetes.io/hostname,要使用自定义的
topologyKey,就要改写或禁用该控制器。
-
在 PreferredDuringScheduling 类型的 Pod 互斥性定义中,空的 topologyKey 会被解释为
kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone 及
failuredomain.beta.kubernetes.io/region 的组合。
-
如果不是上述情况,就可以采用任意合法的 topologyKey 了。
PodAffinity 规则设置的注意事项如下。
-
除了设置 Label Selector 和 topologyKey,用户还可以指定 Namespace 列表来进行限制,同样,使用 Label
Selector 对 Namespace 进行选择。Namespace 的定义和 Label Selector 及 topologyKey 同级。省略
Namespace 的设置,表示使用定义了 affinity/anti-affinity 的 Pod 所在的 Namespace。如果 Namespace
被设置为空值(“”),则表示所有 Namespace。
-
在所有关联 requiredDuringSchedulingIgnoredDuringExecution 的 matchExpressions 全都满足之后,系统
才能将 Pod 调度到某个 Node 上。
关于Pod亲和性和互斥性调度的更多信息可以参考其设计文档,网址为:
https://github.com/kubernetes/kubernetes/blob/master/docs/design/podaffinity.md
8、Taints和Tolerations(污点和容忍)
前面介绍的 NodeAffinity 节点亲和性,是在 Pod 上定义的一种属性,使得 Pod 能够被调度到某些 Node 上运行
(优先选择或强制要求)。Taint 则正好相反,它让 Node 拒绝 Pod 的运行。
Taint 需要和 Toleration 配合使用,让 Pod 避开那些不合适的 Node。在 Node 上设置一个或多个 Taint 之后,除
非 Pod 明确声明能够容忍这些污点,否则无法在这些 Node 上运行。Toleration 是 Pod 的属性,让 Pod 能够(注
意,只是能够,而非必须)运行在标注了 Taint 的 Node 上。
可以用 kubectl taint 命令为 Node 设置 Taint 信息:
$ kubectl taint nodes node1 key=value:NoSchedule
这个设置为 node1 加上了一个 Taint,该 Taint 的键为 key,值为 value,Taint 的效果是 NoSchedule。这意味着
除非 Pod 明确声明可以容忍这个 Taint,否则就不会被调度到 node1 上。
然后,需要在 Pod 上声明 Toleration。下面的两个 Toleration 都被设置为可以容忍(Tolerate)具有该 Taint 的
Node,使得 Pod 能够被调度到 node1 上。
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
或:
tolerations:
- key: "key"
operator: "Exists"
effect: "NoSchedule"
Pod的 Toleration 声明中的 key 和 effect 需要与 Taint 的设置保持一致,并且满足以下条件之一。
-
operator
的值是Exists
(无须指定value)。 -
operator
的值是Equal
并且 value 相等。
如果不指定 operator,则默认值为 Equal。
另外,有如下两个特例。
-
空的 key 配合 Exists 操作符能够匹配所有的键和值。
-
空的 effect 匹配所有的 effect。
在上面的例子中,effect 的取值为 NoSchedule,还可以取值为 PreferNoSchedule,这个值的意思是优先,也可
以算作 NoSchedule 的软限制版本—一个 Pod 如果没有声明容忍这个 Taint,则系统会尽量避免把这个 Pod 调度
到这一节点上,但不是强制的。后面还会介绍另一个 effect NoExecute。
系统允许在同一个 Node 上设置多个 Taint,也可以在 Pod 上设置多个 Toleration。Kubernetes 调度器处理多个
Taint 和 Toleration 的逻辑顺序为:首先列出节点中所有的 Taint,然后忽略 Pod 的 Toleration 能够匹配的部分,
剩下的没有忽略的 Taint 就是对 Pod 的效果了。下面是几种特殊情况。
-
如果在剩余的 Taint 中存在 effect=NoSchedule,则调度器不会把该 Pod 调度到这一节点上。
-
如果在剩余的 Taint 中没有 NoSchedule 效果,但是有 PreferNoSchedule 效果,则调度器会尝试不把这个
Pod 指派给这个节点。
-
如果在剩余的 Taint 中有 NoExecute 效果,并且这个 Pod 已经在该节点上运行,则会被驱逐;如果没有在该
节点上运行,则也不会再被调度到该节点上。
例如,我们这样对一个节点进行 Taint 设置:
$ kubectl taint nodes slave1 key1=value1:NoSchedule
$ kubectl taint nodes slave1 key1=value1:NoExecute
$ kubectl taint nodes slave1 key2=value2:NoSchedule
然后在 Pod 上设置两个 Toleration:
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
这样的结果是该 Pod 无法被调度到 slave1 上,这是因为第3个 Taint 没有匹配的 Toleration。但是如果该 Pod 已
经在 node1 上运行了,那么在运行时设置第3个 Taint,它还能继续在 node1 上运行,这是因为 Pod 可以容忍前
两个 Taint。
一般来说,如果给 Node 加上 effect=NoExecute 的 Taint,那么在该 Node 上正在运行的所有无对应 Toleration
的 Pod 都会被立刻驱逐,而具有相应 Toleration 的 Pod 永远不会被驱逐。不过,系统允许给具有 NoExecute 效
果的 Toleration 加入一个可选的 tolerationSeconds 字段,这个设置表明 Pod 可以在 Taint 添加到 Node 之后还
能在这个 Node 上运行多久(单位为s):
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
上述定义的意思是,如果 Pod 正在运行,所在节点都被加入一个匹配的 Taint,则这个 Pod 会持续在这个节点上
存活 3600s 后被逐出。如果在这个宽限期内 Taint 被移除,则不会触发驱逐事件。
Taint 和 Toleration 是一种处理节点并且让 Pod 进行规避或者驱逐 Pod 的弹性处理方式,下面列举一些常见的用
例。
8.1 独占节点
如果想要拿出一部分节点专门给一些特定应用使用,则可以为节点添加这样的 Taint:
$ kubectl taint nodes nodename dedicated=groupName:NoSchedule
然后给这些应用的 Pod 加入对应的 Toleration。这样,带有合适 Toleration 的 Pod 就会被允许同使用其他节点一
样使用有 Taint 的节点。
通过自定义 Admission Controller 也可以实现这一目标。如果希望让这些应用独占一批节点,并且确保它们只能
使用这些节点,则还可以给这些 Taint 节点加入类似的标签 dedicated=groupName,然后 Admission Controller
需要加入节点亲和性设置,要求 Pod 只会被调度到具有这一标签的节点上。
8.2 具有特殊硬件设备的节点
在集群里可能有一小部分节点安装了特殊的硬件设备(如GPU芯片),用户自然会希望把不需要占用这类硬件的 Pod
排除在外,以确保对这类硬件有需求的 Pod 能够被顺利调度到这些节点。
可以用下面的命令为节点设置 Taint:
kubectl taint nodes nodename special=true:NoSchedule
kubectl taint nodes nodename special=true:PreferNoSchedule
然后在 Pod 中利用对应的 Toleration 来保障特定的 Pod 能够使用特定的硬件。
和上面的独占节点的示例类似,使用 Admission Controller 来完成这一任务会更方便。例如,Admission
Controller 使用 Pod 的一些特征来判断这些 Pod,如果可以使用这些硬件,就添加 Toleration 来完成这一工作。
要保障需要使用特殊硬件的 Pod 只被调度到安装这些硬件的节点上,则还需要一些额外的工作,比如将这些特殊
资源使用 opaque-int-resource 的方式对自定义资源进行量化,然后在 PodSpec 中进行请求;也可以使用标签的
方式来标注这些安装有特别硬件的节点,然后在 Pod 中定义节点亲和性来实现这个目标。
8.3 定义Pod驱逐行为,以应对节点故障(为Alpha版本的功能)
前面提到的 NoExecute 这个 Taint 效果对节点上正在运行的 Pod 有以下影响。
-
没有设置 Toleration 的 Pod 会被立刻驱逐。
-
配置了对应 Toleration 的 Pod,如果没有为 tolerationSeconds 赋值,则会一直留在这一节点中。
-
配置了对应 Toleration 的 Pod 且指定了 tolerationSeconds 值,则会在指定时间后驱逐。
-
Kubernetes从1.6 版本开始引入一个 Alpha 版本的功能,即把节点故障标记为 Taint(目前只针对 node
unreachable 及 node not ready,相应的 NodeCondition
TaintBasedEvictions 功能后(在 --feature-gates 参数中加入 TaintBasedEvictions=true),NodeController 会
自动为 Node 设置 Taint,而在状态为 Ready 的 Node 上,之前设置过的普通驱逐逻辑将会被禁用。注意,在
节点故障的情况下,为了保持现存的 Pod 驱逐的限速(rate-limiting)设置,系统将会以限速的模式逐步给
Node 设置 Taint,这就能避免在一些特定情况下(比如 Master 暂时失联)大量的 Pod 被驱逐。这一功能兼容于
tolerationSeconds,允许 Pod 定义节点故障时持续多久才被逐出。
例如,一个包含很多本地状态的应用可能需要在网络发生故障时,还能持续在节点上运行,期望网络能够快速恢
复,从而避免被从这个节点上驱逐。Pod 的 Toleration 可以这样定义:
tolerations:
- key: "node.alpha.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 6000
对于 Node 未就绪状态,可以把 Key 设置为 node.alpha.kubernetes.io/notReady
。
如果没有为 Pod 指定 node.alpha.kubernetes.io/notReady
的 Toleration,那么 Kubernetes 会自动为 Pod
加入 tolerationSeconds=300
的 node.alpha.kubernetes.io/notReady
类型的 Toleration。
同样,如果 Pod 没有定义 node.alpha.kubernetes.io/unreachable
的 Toleration,那么系统会自动为其加入
tolerationSeconds=300
的 node.alpha.kubernetes.io/unreachable
类型的 Toleration。
这些系统自动设置的 toleration 在 Node 发现问题时,能够为 Pod 确保驱逐前再运行 5min。这两个默认的
Toleration 由 Admission Controller DefaultTolerationSeconds 自动加入。
# 设置
[root@master cha3]# kubectl taint nodes slave1 key=value:NoSchedule
node/slave1 tainted
[root@master cha3]# kubectl taint nodes slave2 key=value:NoSchedule
node/slave2 tainted
# 查看
[root@master cha3]# kubectl describe node slave1 | grep Taints
Taints: key=value:NoSchedule
[root@master cha3]# kubectl describe node slave2 | grep Taints
Taints: key=value:NoSchedule
配置文件 027-pod-toleration.yaml
的内容为:
apiVersion: v1
kind: Pod
metadata:
name: pod-toleration
spec:
containers:
- name: pod-toleration
image: google/pause
[root@master cha3]# kubectl create -f 027-pod-toleration.yaml
pod/pod-toleration created
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-toleration 0/1 Pending 0 4s
[root@master cha3]# kubectl describe pod pod-toleration
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 33s default-scheduler 0/4 nodes are available: 2 node(s) had taint {key: value}, that the pod didn't tolerate, 2 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
Warning FailedScheduling 31s default-scheduler 0/4 nodes are available: 2 node(s) had taint {key: value}, that the pod didn't tolerate, 2 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
由于设置了 key=value:NoSchedulev
,调度会失败。
下面我们配置 tolerations
再次尝试。
配置文件 028-pod-toleration.yaml
的内容为:
apiVersion: v1
kind: Pod
metadata:
name: pod-toleration2
spec:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
containers:
- name: pod-toleration
image: google/pause
[root@master cha3]# kubectl create -f 028-pod-toleration.yaml
pod/pod-toleration2 created
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-toleration 0/1 Pending 0 2m16s
pod-toleration2 1/1 Running 0 11s
删除 key=value:NoSchedulev
,pod-toleration
会正常运行:
[root@master cha3]# kubectl taint nodes slave1 key-
node/slave1 untainted
[root@master cha3]# kubectl taint nodes slave2 key-
node/slave2 untainted
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-toleration 1/1 Running 0 4m47s
pod-toleration2 1/1 Running 0 2m42s
下面对 taint 的一些内容进行补充:
删除taint:
# 这里的key可以不用指定value
kubectl taint node slave1 key1:NoSchedule-
kubectl taint node slave1 key1:NoExecute-
# 删除指定key所有的effect
kubectl taint node slave1 key1-
master 节点设置 taint:
使用 kubeadm 初始化的 kubernetes,出于安全考虑 Pod 不会被调度到 Master Node 上,也就是说 Master
Node 不参与工作负载。
# 1、查看master节点调度情况
# master:NoSchedule表示当前节点为不可调度节点
[root@master cha3]# kubectl describe node master | grep Taints
Taints: node-role.kubernetes.io/master:NoSchedule
# 2、将master节点设置为可调度节点
[root@master cha3]# kubectl taint node master node-role.kubernetes.io/master-
node/master untainted
[root@master cha3]# kubectl describe node master | grep Taints
Taints: <none>
# 3、将master节点设置为不可调度节点
# node-role.kubernetes.io/master为key,value为空,effect为NoSchedule
[root@master cha3]# kubectl taint node master node-role.kubernetes.io/master="":NoSchedule
node/master tainted
[root@master cha3]# kubectl describe node master | grep Taints
Taints: node-role.kubernetes.io/master:NoSchedule
容忍 tolerations master节点 的 taints:
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Equal"
value: ""
effect: "NoSchedule"
查看 node 的 taint:
[root@master cha3]# kubectl get node -o yaml | grep taint -A 5
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
status:
addresses:
- address: 192.168.226.200
--
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
status:
addresses:
- address: 192.168.226.203
去除污点,允许 master 节点部署 pod:
[root@master cha3]# kubectl taint nodes --all node-role.kubernetes.io/master-
node/master untainted
node/master2 untainted
taint "node-role.kubernetes.io/master" not found
taint "node-role.kubernetes.io/master" not found
[root@master cha3]# kubectl get node -o yaml | grep taint -A 5
9、Pod Priority Preemption-Pod优先级调度
对于运行各种负载(如 Service、Job )的中等规模或者大规模的集群来说,出于各种原因,我们需要尽可能提高集群
的资源利用率。而提高资源利用率的常规做法是采用优先级方案,即不同类型的负载对应不同的优先级,同时允许
集群中的所有负载所需的资源总量超过集群可提供的资源,在这种情况下,当发生资源不足的情况时,系统可以选
择释放一些不重要的负载(优先级最低的),保障最重要的负载能够获取足够的资源稳定运行。
在 Kubernetes 1.8 版本之前,当集群的可用资源不足时,在用户提交新的 Pod 创建请求后,该 Pod 会一直处于
Pending 状态,即使这个 Pod 是一个很重要(很有身份)的 Pod,也只能被动等待其他 Pod 被删除并释放资源,才
能有机会被调度成功。Kubernetes 1.8 版本引入了基于 Pod 优先级抢占(Pod Priority Preemption)的调度策略,
此时 Kubernetes 会尝试释放目标节点上低优先级的 Pod,以腾出空间(资源)安置高优先级的 Pod,这种调度方式
被称为抢占式调度。在 Kubernetes 1.11 版本中,该特性升级为 Beta 版本,默认开启,在后继的 Kubernetes
1.14 版本中正式 Release。如何声明一个负载相对其他负载更重要? 我们可以通过以下几个维度来定义:
- Priority,优先级;
- QoS,服务质量等级;
- 系统定义的其他度量指标;
优先级抢占调度策略的核心行为分别是驱逐(Eviction)与抢占(Preemption),这两种行为的使用场景不同,效果相
同。Eviction 是 kubelet 进程的行为,即当一个 Node 发生资源不足(under resource pressure)的情况时,该节点
上的 kubelet 进程会执行驱逐动作,此时 Kubelet 会综合考虑 Pod 的优先级、资源申请量与实际使用量等信息来
计算哪些 Pod 需要被驱逐;当同样优先级的 Pod 需要被驱逐时,实际使用的资源量超过申请量最大倍数的高耗能
Pod 会被首先驱逐。对于 QoS 等级为 Best Effort 的 Pod 来说,由于没有定义资源申请(CPU/Memory
Request),所以它们实际使用的资源可能非常大。Preemption 则是 Scheduler 执行的行为,当一个新的 Pod 因
为资源无法满足而不能被调度时,Scheduler 可能(有权决定)选择驱逐部分低优先级的 Pod 实例来满足此 Pod 的
调度目标,这就是 Preemption 机制。
需要注意的是,Scheduler 可能会驱逐 Node A 上的一个 Pod 以满足 Node B 上的一个新 Pod 的调度任务。比如
下面的这个例子:
一个低优先级的 Pod A 在Node A(属于机架R)上运行,此时有一个高优先级的 Pod B 等待调度,目标节点是同属
机架 R 的 Node B,他们中的一个或全部都定义了 anti-affinity 规则,不允许在同一个机架上运行,此时
Scheduler 只好丢车保帅,驱逐低优先级的 Pod A 以满足高优先级的 Pod B 的调度。
Pod优先级调度示例如下。
首先,由集群管理员创建 PriorityClasses,PriorityClass 不属于任何命名空间。
配置文件 029-priorityclass.yaml
的内容为:
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
[root@master cha3]# kubectl create -f 029-priorityclass.yaml
Warning: scheduling.k8s.io/v1beta1 PriorityClass is deprecated in v1.14+, unavailable in v1.22+; use scheduling.k8s.io/v1 PriorityClass
priorityclass.scheduling.k8s.io/high-priority created
[root@master cha3]# kubectl get PriorityClass
NAME VALUE GLOBAL-DEFAULT AGE
high-priority 1000000 false 49s
system-cluster-critical 2000000000 false 52m
system-node-critical 2000001000 false 52m
上述 YAML 文件定义了一个名为 high-priority 的优先级类别,优先级为 100000,数字越大,优先级越高,超过
一亿的数字被系统保留,用于指派给系统组件。
我们可以在任意 Pod 中引用上述 Pod 优先级类别。
配置文件 030-high-priority.yaml
的内容为:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
[root@master cha3]# kubectl create -f 030-high-priority.yaml
pod/nginx created
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 16s
如果发生了需要抢占的调度,高优先级 Pod 就可能抢占节点 N,并将其低优先级 Pod 驱逐出节点 N,高优先级
Pod 的 status 信息中的 nominatedNodeName 字段会记录目标节点N的名称。需要注意,高优先级 Pod 仍然无
法保证最终被调度到节点N上,在节点N上低优先级 Pod 被驱逐的过程中,如果有新的节点满足高优先级 Pod 的
需求,就会把它调度到新的 Node 上。而如果在等待低优先级的 Pod 退出的过程中,又出现了优先级更高的
Pod,调度器将会调度这个更高优先级的 Pod 到节点N上,并重新调度之前等待的高优先级 Pod。
优先级抢占的调度方式可能会导致调度陷入死循环状态。当 Kubernetes 集群配置了多个调度器(Scheduler)时,
这一行为可能就会发生,比如下面这个例子:
Scheduler A为了调度一个(批) Pod,特地驱逐了一些 Pod,因此在集群中有了空余的空间可以用来调度,此时
Scheduler B 恰好抢在 Scheduler A 之前调度了一个新的 Pod,消耗了相应的资源,因此,当 Scheduler A 清理
完资源后正式发起 Pod 的调度时,却发现资源不足,被目标节点的 kubelet 进程拒绝了调度请求!这种情况的确
无解,因此最好的做法是让多个 Scheduler 相互协作来共同实现一个目标。
最后要指出一点:使用优先级抢占的调度策略可能会导致某些 Pod 永远无法被成功调度。因此优先级调度不但增
加了系统的复杂性,还可能带来额外不稳定的因素。因此,一旦发生资源紧张的局面,首先要考虑的是集群扩容,
如果无法扩容,则再考虑有监管的优先级调度特性,比如结合基于 Namespace 的资源配额限制来约束任意优先
级抢占行为。
10、DaemonSet在每个Node上都调度一个Pod
DaemonSet 是 Kubernetes 1.2 版本新增的一种资源对象,用于管理在集群中每个 Node 上仅运行一份 Pod 的副
本实例。
这种用法适合有这种需求的应用。
-
在每个 Node 上都运行一个 GlusterFS 存储或者 Ceph 存储的 Daemon 进程。
-
在每个 Node 上都运行一个日志采集程序,例如 Fluentd 或者 Logstach。
-
在每个 Node 上都运行一个性能监控程序,采集该 Node 的运行性能数据,例如 Prometheus Node
Exporter、collectd、New Relic agent 或者 Ganglia gmond 等。
DaemonSet 的 Pod 调度策略与 RC 类似,除了使用系统内置的算法在每个 Node 上进行调度,也可以在 Pod 的
定义中使用 NodeSelector 或 NodeAffinity 来指定满足条件的 Node 范围进行调度。
下面的例子定义为在每个 Node 上都启动一个 fluentd 容器,配置文件 031-fluentd-ds.yaml
的内容如下,其
中挂载了物理机的两个目录 /var/log
和 /var/lib/docker/containers
。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-cloud-logging
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
selector:
matchLabels:
k8s-app: fluentd-cloud-logging
template:
metadata:
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
containers:
- name: fluentd-cloud-logging
image: xuchaoi/dockerfile-fluentd-elasticsearch
resources:
limits:
cpu: 100m
memory: 200Mi
env:
- name: FLUENTD_ARGS
value: -q
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: false
- name: containers
mountPath: /var/lib/docker/containers
readOnly: false
volumes:
- name: containers
hostPath:
path: /var/lib/docker/containers
- name: varlog
hostPath:
path: /var/log
使用 kubectl create 命令创建该 DaemonSet:
[root@master cha3]# kubectl create -f 031-fluentd-ds.yaml
daemonset.apps/fluentd-cloud-logging created
查看创建好的 DaemonSet 和 Pod,可以看到在每个 Node 上都创建了一个 Pod:
[root@master cha3]# kubectl get daemonset fluentd-cloud-logging --namespace=kube-system -o wide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
fluentd-cloud-logging 2 2 2 2 2 <none> 76s fluentd-cloud-logging xuchaoi/dockerfile-fluentd-elasticsearch k8s-app=fluentd-cloud-logging
[root@master cha3]# kubectl get pod --namespace=kube-system -o wide | grep fluentd-cloud-logging
fluentd-cloud-logging-9tj8s 1/1 Running 0 2m3s 10.244.140.65 slave2 <none> <none>
fluentd-cloud-logging-hkqvw 1/1 Running 0 2m3s 10.244.140.198 slave1 <none> <none>
在 Kubernetes 1.6 以后的版本中,DaemonSet 也能执行滚动升级了,即在更新一个 DaemonSet 模板的时候,
旧的 Pod 副本会被自动删除,同时新的 Pod 副本会被自动创建,此时 DaemonSet 的更新策略(updateStrategy)
为 RollingUpdate,如下所示:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: goldpinger
spec:
updateStrategy:
type: RollingUpdate
updateStrategy 的另外一个值是 OnDelete,即只有手工删除了 DaemonSet 创建的 Pod 副本,新的 Pod 副本才
会被创建出来。如果不设置 updateStrategy 的值,则在 Kubernetes 1.6 之后的版本中会被默认设置为
RollingUpdate。
11、Job批处理调度
Kubernetes 从 1.2 版本开始支持批处理类型的应用,我们可以通过 Kubernetes Job 资源对象来定义并启动一个
批处理任务。批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项(work item),处理完成后,整
个批处理任务结束。按照批处理任务实现方式的不同,批处理任务可以分为如下的几种模式。
-
Job Template Expansion
模式:一个 Job 对象对应一个待处理的 Work item,有几个 Work item 就产生几个独立的 Job,通常适合 Work item 数量少、每个 Work item 要处理的数据量比较大的场景,比如有一个
100GB 的文件作为一个 Work item,总共有 10 个文件需要处理。
-
Queue with Pod Per Work Item
模式:采用一个任务队列存放 Work item,一个 Job 对象作为消费者去完成这些 Work item,在这种模式下,Job 会启动N个 Pod,每个 Pod 都对应一个 Work item。
-
Queue with Variable Pod Count
模式:也是采用一个任务队列存放 Work item,一个 Job 对象作为消费者去完成这些 Work item,但与上面的模式不同,Job 启动的 Pod 数量是可变的。
还有一种被称为 Single Job with Static Work Assignment 的模式,也是一个 Job 产生多个 Pod,但它采用程序静
态方式分配任务项,而不是采用队列模式进行动态分配,如下表所示是这几种模式的一个对比。
考虑到批处理的并行问题,Kubernetes 将 Job 分以下三种类型。
1、Non-parallel Jobs
通常一个 Job 只启动一个 Pod,除非 Pod 异常,才会重启该 Pod,一旦此 Pod 正常结束,Job 将结束。
2、Parallel Jobs with a fixed completion count
并行 Job 会启动多个 Pod,此时需要设定 Job 的 .spec.completions 参数为一个正数,当正常结束的 Pod 数量达
至此参数设定的值后,Job 结束。此外,Job 的 .spec.parallelism 参数用来控制并行度,即同时启动几个 Job 来处
理 Work Item。
3、Parallel Jobs with a work queue
任务队列方式的并行 Job 需要一个独立的 Queue,Work item 都在一个 Queue 中存放,不能设置 Job的
.spec.completions 参数,此时 Job 有以下特性。
-
每个Pod都能独立判断和决定是否还有任务项需要处理。
-
如果某个Pod正常结束,则Job不会再启动新的Pod。
-
如果一个Pod成功结束,则此时应该不存在其他Pod还在工作的情况,它们应该都处于即将结束、退出的状
态。
-
如果所有Pod都结束了,且至少有一个Pod成功结束,则整个Job成功结束。
下面分别讲解常见的三种批处理模型在 Kubernetes 中的应用例子。
首先是 Job Template Expansion 模式,由于在这种模式下每个 Work item 对应一个 Job 实例,所以这种模式首先
定义一个 Job 模板,模板里的主要参数是 Work item 的标识,因为每个 Job 都处理不同的 Work item。如下所示
的 Job 模板(文件名为 job.yaml
)中的 $ITEM
可以作为任务项的标识:
apiVersion: batch/v1
kind: Job
metadata:
name: process-item-$ITEM
labels:
jobgroup: jobexample
spec:
template:
metadata:
name: jobexample
labels:
jobgroup: jobexample
spec:
containers:
- name: c
image: busybox
command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"]
restartPolicy: Never
通过下面的操作,生成了 3 个对应的 Job 定义文件并创建 Job:
[root@master cha3]# mkdir jobs
# job-execute.sh
#!/bin/bash
for i in apple banana cherry
do
cat job.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
done
[root@master cha3]# ./job-execute.sh
[root@master cha3]# ls jobs/
job-apple.yaml job-banana.yaml job-cherry.yaml
[root@master cha3]# kubectl create -f jobs
job.batch/process-item-apple created
job.batch/process-item-banana created
job.batch/process-item-cherry created
首先,观察Job
的运行情况:
[root@master cha3]# kubectl get jobs -l jobgroup=jobexample
NAME COMPLETIONS DURATION AGE
process-item-apple 1/1 35s 46s
process-item-banana 1/1 8s 46s
process-item-cherry 1/1 38s 46s
[root@master cha3]# kubectl get pods
NAME READY STATUS RESTARTS AGE
process-item-apple-v4v9l 0/1 Completed 0 103s
process-item-banana-2bdfm 0/1 Completed 0 103s
process-item-cherry-z8ctp 0/1 Completed 0 103s
其次,我们看看Queue with Pod Per Work Item模式,在这种模式下需要一个任务队列存放Work item,比如
RabbitMQ,客户端程序先把要处理的任务变成Work item放入任务队列,然后编写Worker程序、打包镜像并定义
成为Job中的Work Pod。Worker程序的实现逻辑是从任务队列中拉取一个Work item并处理,在处理完成后即结
束进程。并行度为2的Demo示意图如图所示。
最后,我们看看Queue with Variable Pod Count模式,如图3.6所示。由于这种模式下,Worker程序需要知道队
列中是否还有等待处理的Work item,如果有就取出来处理,否则就认为所有工作完成并结束进程,所以任务队列
通常要采用Redis或者数据库来实现。
12、Cronjob:定时任务
Kubernetes 从 1.5 版本开始增加了一种新类型的 Job,即类似 Linux Cron 的定时任务 Cron Job,下面看看如何
定义和使用这种类型的 Job。
首先,确保 Kubernetes 的版本为 1.8 及以上。
其次,需要掌握 Cron Job 的定时表达式,它基本上照搬了 Linux Cron 的表达式,区别是第1位是分钟而不是秒,
格式如下:
Minutes Hours DayofMonth Month DayofWeek Year
其中每个域都可出现的字符如下。
-
Minutes
:可出现,
、-
、*
、/
这4个字符,有效范围为0~59
的整数。 -
Hours
:可出现,
、-
、*
、/
这4个字符,有效范围为0~23
的整数。 -
DayofMonth
:可出现,
、-
、*
、/
、?
、L
、W
、C
这8个字符,有效范围为0~31
的整数。 -
Month
:可出现,
、-
、*
、/
这4个字符,有效范围为1~12
的整数或JAN~DEC
。 -
DayofWeek
:可出现,
、-
、*
、/
、?
、L
、C
、#
这8个字符,有效范围为1~7
的整数或SUN~SAT
。1表示星期天,2表示星期一,以此类推。
表达式中的特殊字符*
与/
的含义如下。
-
*
:表示匹配该域的任意值,假如在Minutes域使用*
,则表示每分钟都会触发事件。 -
/
:表示从起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域设置为5/20,则意味着第1次触发在第5min时,接下来每20min触发一次,将在第25min、第45min等时刻分别触发。
比如,我们要每隔1min执行一次任务,则Cron表达式如下:
*/1 * * * *
掌握这些基本知识后,就可以编写一个 Cron Job 的配置文件了。
配置文件 032-cron.yaml
的内容为:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
该例子定义了一个名为 hello 的 Cron Job,任务每隔 1min 执行一次,运行的镜像是 busybox,执行的命令是
Shell 脚本,脚本执行时会在控制台输出当前时间和字符串 Hello from the Kubernetes cluster。
接下来执行 kubectl create 命令完成创建:
[root@master cha3]# kubectl create -f 032-cron.yaml
cronjob.batch/hello created
然后每隔 1min 执行 kubectl get cronjob hello 查看任务状态,发现的确每分钟调度了一次:
[root@master cha3]# kubectl get cronjob hello
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 0 <none> 21s
[root@master cha3]# kubectl get cronjob hello
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 1 3s 43s
[root@master cha3]# kubectl get cronjob hello
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 1 0s 10m
[root@master cha3]# kubectl get cronjob hello
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 1 1s 10m
[root@master cha3]# kubectl get cronjob hello
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 0 5s 10m
还可以通过查找 Cron Job 对应的容器,验证每隔 1min 产生一个容器的事实,如下所示:
[root@slave1 ~]# docker ps -a | grep busybox
c626a9198e35 busybox "/bin/sh -c 'date; e…" 15 seconds ago Exited (0) 15 seconds ago k8s_hello_hello-28144181-hdg2v_default_4924cb7b-2f28-4599-8b2e-27db1c6c0994_0
a6b052da68e0 busybox "/bin/sh -c 'date; e…" About a minute ago Exited (0) About a minute ago k8s_hello_hello-28144180-dkxh2_default_0e64701b-423d-4cb6-a37b-600dc6a79d7d_0
57f8671647b8 busybox "/bin/sh -c 'date; e…" 2 minutes ago Exited (0) 2 minutes ago k8s_hello_hello-28144179-g8wgv_default_1baae0fb-c25e-41a3-85e9-fc23d8cbfd42_0
607f8a45a290 busybox "/bin/sh -c 'date; e…" 3 minutes ago Exited (0) 3 minutes ago k8s_hello_hello-28144178-lqn5t_default_16475aa4-65d8-4954-bc6e-87072d6a4691_0
查看任意一个容器的日志,结果如下:
[root@slave1 ~]# docker logs c626a9198e35
Thu Jul 6 13:41:03 UTC 2023
Hello from the Kubernetes cluster
运行下面的命令,可以更直观地了解 Cron Job 定期触发任务执行的历史和现状:
[root@master cha3]# kubectl get jobs --watch
NAME COMPLETIONS DURATION AGE
hello-28144182 1/1 3s 2m59s
hello-28144183 1/1 5s 119s
hello-28144184 1/1 4s 59s
hello-28144185 0/1 0s
hello-28144185 0/1 0s 0s
hello-28144185 1/1 6s 6s
hello-28144182 1/1 3s 3m6s
hello-28144186 0/1 0s
hello-28144186 0/1 0s 0s
hello-28144186 1/1 3s 3s
hello-28144183 1/1 5s 3m3s
hello-28144187 0/1 0s
hello-28144187 0/1 0s 0s
hello-28144187 1/1 4s 4s
hello-28144184 1/1 4s 3m4s
hello-28144188 0/1 0s
hello-28144188 0/1 0s 0s
hello-28144188 1/1 4s 4s
hello-28144185 1/1 6s 3m4s
hello-28144189 0/1 0s
hello-28144189 0/1 0s 0s
其中 COMPLETIONS 列为1的每一行都是一个调度成功的 Job,以第1行的 hello-28144189 的 Job为例,它对应
的 Pod 可以通过下面的方式得到:
[root@master cha3]# kubectl get pods | grep hello-28144189
hello-28144189-h5d8v 0/1 Completed 0 12s
查看该 Pod 的日志:
[root@master cha3]# kubectl logs hello-28144189-h5d8v
Thu Jul 6 13:42:03 UTC 2023
Hello from the Kubernetes cluster
最后,当不需要某个 Cron Job 时,可以通过下面的命令删除它:
[root@master cha3]# kubectl delete cronjob hello
cronjob.batch "hello" deleted
在 Kubernetes 1.9 版本后,kubectrl 命令增加了别名 cj 来表示 cronjob,同时 kubectl set image/env 命令也可
以作用在 CronJob 对象上了。
13、自定义调度器
如果 Kubernetes 调度器的众多特性还无法满足我们的独特调度需求,则还可以用自己开发的调度器进行调度。从
1.6 版本开始,Kubernetes 的多调度器特性也进入了快速发展阶段。
一般情况下,每个新 Pod 都会由默认的调度器进行调度。但是如果在 Pod 中提供了自定义的调度器名称,那么默
认的调度器会忽略该 Pod,转由指定的调度器完成 Pod 的调度。
在下面的例子中为 Pod 指定了一个名为 my-scheduler 的自定义调度器。
配置文件 my-scheduler.yaml
的内容为:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
schedulerName: my-scheduler
containers:
- name: nginx
image: nginx
[root@master cha3]# kubectl create -f my-scheduler.yaml
pod/nginx created
[root@master cha3]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 0/1 Pending 0 59s
如果自定义的调度器还未在系统中部署,则默认的调度器会忽略这个 Pod,这个Pod 将会永远处于 Pending 状
态。
下面看看如何创建一个自定义的调度器。
可以用任何语言来实现简单或复杂的自定义调度器。下面的简单例子使用Bash脚本进行实现,调度策略为随机选
择一个Node(注意,这个调度器需要通过kubectl proxy来运行):
# my-scheduler.sh
#!/bin/bash
SERVER='localhost:8001'
while true;
do
for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"');
do
NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))
NUMNODES=${#NODES[@]}
CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind": "Node", "name":"'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
echo "Assigned $PODNAME to $CHOSEN"
done
sleep 1
done
[root@master ~]# kubectl proxy
Starting to serve on 127.0.0.1:8001
[root@master cha3]# ./my-scheduler.sh
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Success",
"code": 201
}Assigned nginx to slave2
[root@master cha3]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 44s
一旦这个自定义调度器成功启动,前面的 Pod 就会被正确调度到某个 Node 上。