k8s(四)Kubernetes 集群调度
文章目录
- 前言
- 一、Kubernetes 集群调度
- 1.1 Kubernetes 组件协作机制
- 1.1.1 关键组件关系
- 1.2 Pod 创建与工作机制流程
- 1.2.1 典型创建过程(List-Watch 模型)
- 1.2.1.1 三大组件启动监听(List-Watch)
- 1.2.1.2 用户创建 Pod 对象
- 1.2.1.3 API Server 将 Pod 信息写入 etcd
- 1.2.1.4 etcd 通知事件(Create)
- 1.2.1.5 Controller Manager 监听到 Pod 创建事件
- 1.2.1.6 Replication Controller(RC)/ ReplicaSet 保证副本数
- 1.2.1.7 API Server 更新 etcd(记录详细信息)
- 1.2.1.8 etcd 触发 Pod 信息更新事件
- 1.2.1.9 Scheduler 监听到待调度的 Pod
- 1.2.1.10 Scheduler 更新调度结果
- 1.2.1.11 etcd 确认更新 & API Server 同步结果
- 1.2.1.12 kubelet 在 Node 上拉取并运行容器
- 1.2.1.13 API Server 更新 Pod 状态
- 1.3 Scheduler 过程(调度器)
- 1.3.1 核心任务
- 1.3.2 调度目标
- 1.4 调度流程
- 1.4.1 过滤阶段(Predicate)
- 1.4.2 优选阶段(Priorities)
- 1.5 指定调度节点方式
- 1.5.1 nodeName(强制绑定)
- 1.5.2 nodeSelector(基于标签匹配)
- 1.5.2.1 管理 Node 标签命令
- 1.6 节点亲和性与 Pod 亲和性
- 1.6.1 节点亲和性(NodeAffinity)
- 1.6.1.1 案例1 硬策略
- 1.6.1.2 案例2 软策略
- 1.6.1.3 案例3 软硬策略结合
- 1.6.2 Pod 亲和性与反亲和性
- 1.6.2.1 案例1 Pod 亲和性调度
- 1.6.2.2 案例2 Pod 反亲和性调度
- 1.7 污点(Taint) 和 容忍(Tolerations)
- 1.7.1 污点(Taint)
- 1.7.2 容忍 (Tolerations)
- 1.8 节点维护操作
- 1.8.1 cordon & drain
- 1.9 Pod 生命周期(Phase)
- 1.10 故障排查命令
- 1.11 总结
- 1.11.1 核心组件协作关系图
- 1.11.2 核心调度策略层次
- 1.11.3 思维总结
- 总结
- 1. 底层基石:组件协作与 List-Watch 机制
- 2. 调度策略:从“强制绑定”到“柔性偏好”的分层设计
- 3. 运维保障:从状态监控到节点维护
前言
在 Kubernetes(简称 K8s)集群管理中,“调度”是决定 Pod 如何合理分配到节点、保障集群资源高效利用与业务稳定运行的核心机制。无论是小规模测试集群还是大规模生产集群,能否通过调度策略实现“Pod 到节点”的精准匹配,直接影响集群的资源利用率、业务可用性与运维效率。
例如,如何让核心业务 Pod 优先调度到性能更强的节点?如何避免不同业务的 Pod 因资源争抢导致故障?如何在节点维护时安全迁移 Pod 而不中断服务?这些问题的答案,都隐藏在 K8s 调度体系的设计逻辑中。
本文聚焦 K8s 集群调度核心内容,从组件协作的底层机制(List-Watch)切入,逐步拆解 Pod 创建的完整流程、Scheduler 调度算法的过滤与优选逻辑,再到节点绑定、亲和性/反亲和性、污点与容忍等实用策略,最后补充节点维护与故障排查方法。文中包含大量可直接复用的配置示例与命令,旨在帮助读者从“理解原理”到“落地实践”,全面掌握 K8s 调度的核心能力,为集群资源管理与业务部署提供清晰指引。
一、Kubernetes 集群调度
1.1 Kubernetes 组件协作机制
Kubernetes 通过 List-Watch 机制使各个组件协作、数据同步,从而实现解耦与实时一致性。
1.1.1 关键组件关系
组件 | 职责 |
---|---|
kubectl / API 客户端 | 向 APIServer 发起资源创建或管理请求 |
APIServer | 负责 API 调用、权限校验、存储交互,是集群控制的核心入口 |
etcd | 存储集群所有状态信息 |
Controller Manager | 维持副本数、执行自愈逻辑(扩容、重建等) |
Scheduler | 调度器,将未分配节点的 Pod 分配到合适的 Node |
kubelet | 节点代理,负责 Pod 生命周期管理和容器运行状态上报 |
用户通过 kubectl 根据配置文件,向 APIServer 发送命令,在 Node 节点上面建立 Pod 和 Container。APIServer 经过 API 调用,权限控制,调用资源和存储资源的过程,实际上还没有真正开始部署应用。这里需要 Controller Manager、Scheduler 和 kubelet 的协助才能完成整个部署过程。
在 Kubernetes 中,所有部署的信息都会写到 etcd 中保存。实际上 etcd 在存储部署信息的时候,会发送 Create 事件给 APIServer,而 APIServer 会通过监听(Watch)etcd 发过来的事件。其他组件也会监听(Watch)APIServer 发出来的事件。
1.2 Pod 创建与工作机制流程
Pod 创建的整个生命周期由多组件配合完成:
1.2.1 典型创建过程(List-Watch 模型)
1.2.1.1 三大组件启动监听(List-Watch)
- Controller Manager、Scheduler、kubelet 启动后会分别通过 Watch API Server(HTTPS 6443 端口)监听集群资源事件变化。
- Controller Manager:监听副本控制类对象(如 ReplicaSet、Deployment)
- Scheduler:监听未调度的 Pod
- kubelet:监听分配到本节点的 Pod
1.2.1.2 用户创建 Pod 对象
- 用户通过
kubectl
或其他 API 客户端发送创建 Pod 的请求给 API Server。
示例:kubectl apply -f pod.yaml
1.2.1.3 API Server 将 Pod 信息写入 etcd
- API Server 校验请求后,将 Pod 的元数据存入 etcd。
- 写入成功后返回确认给客户端。
1.2.1.4 etcd 通知事件(Create)
- etcd 接受到 Pod 信息后,触发 Create 事件。
- 该事件被发送给 API Server。
1.2.1.5 Controller Manager 监听到 Pod 创建事件
- Controller Manager 通过 Watch 机制收到 API Server 发出的 Pod 创建事件。
1.2.1.6 Replication Controller(RC)/ ReplicaSet 保证副本数
- Controller Manager 在接到 Create 事件以后,如果发现副本数量不足,则由 RC/ReplicaSet 创建所需副本。
- 扩容、缩容操作也都由此机制控制。
1.2.1.7 API Server 更新 etcd(记录详细信息)
- Controller Manager 创建完 Pod 副本后,API Server 会将 Pod 的详细信息更新写入 etcd。
- 包括副本数量、副本模板、容器规格等。
1.2.1.8 etcd 触发 Pod 信息更新事件
- etcd 再次发送更新事件给 API Server。
1.2.1.9 Scheduler 监听到待调度的 Pod
- Scheduler Watch 到新创建的 Pod 处于 “Pending” 状态(尚未分配节点)。
- 通过调度算法(资源、亲和性、污点等)为其选择一个合适的 Node。
1.2.1.10 Scheduler 更新调度结果
- Scheduler 将选定的 Node 信息写回到 API Server。
- API Server 更新 etcd 中该 Pod 的 Node 绑定信息。
1.2.1.11 etcd 确认更新 & API Server 同步结果
- etcd 更新成功后向 API Server 返回确认。
- API Server 同步 Pod 的最新状态(包括 Node 绑定结果)。
1.2.1.12 kubelet 在 Node 上拉取并运行容器
- kubelet 监听到分配给自己的新 Pod。
- 调用容器运行时(Docker/containerd):
- 拉取镜像
- 创建容器
- 启动容器
- 启动成功后将 Pod 状态(Running、Failed 等)上报给 API Server。
1.2.1.13 API Server 更新 Pod 状态
- API Server 将 kubelet 上报的状态写入 etcd。
- etcd 确认写入成功后,集群状态完成同步,Pod 正式进入 Running 状态
kubelet 持续监听 Pod 事件,是为了应对副本数变化、镜像更新等动态操作。
注意:在创建 Pod 的工作就已经完成了后,为什么 kubelet 还要一直监听呢?原因很简单,假设这个时候 kubectl 发命令,要扩充 Pod 副本数量,那么上面的流程又会触发一遍,kubelet 会根据最新的 Pod 的部署情况调整 Node 的资源。又或者 Pod 副本数量没有发生变化,但是其中的镜像文件升级了,kubelet 也会自动获取最新的镜像文件并且加载。
1.3 Scheduler 过程(调度器)
1.3.1 核心任务
将 未绑定 Node 的 Pod(spec.nodeName == ""
) 分配到合适的节点。
1.3.2 调度目标
- 公平性:节点间资源分配均衡
- 高效性:集群所有资源最大化被使用
- 效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
- 灵活性:允许自定义策略(调度策略、插件)
Sheduler 是作为单独的程序运行的,启动之后会一直监听 APIServer,获取 spec.nodeName 为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。
调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为预算策略(predicate);然后对通过的节点按照优先级排序,这个是优选策略(priorities);最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。
1.4 调度流程
1.4.1 过滤阶段(Predicate)
过滤掉不满足条件的节点。
常见过滤算法:
算法名 | 功能描述 |
---|---|
PodFitsResources | 检查节点剩余资源是否满足 Pod 需求 |
PodFitsHost | 检查 NodeName 是否匹配 |
PodFitsHostPorts | 检查端口冲突 |
PodSelectorMatches | label 匹配 |
NoDiskConflict | Volume 挂载冲突检测 |
若无节点满足条件,Pod 进入 Pending 状态,不断重试。
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。 经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序。
1.4.2 优选阶段(Priorities)
对可行节点进行打分排序。
常见算法:
优先级项 | 描述 |
---|---|
LeastRequestedPriority | 资源使用率越低,权重越高 |
BalancedResourceAllocation | CPU 与内存使用率越接近越好(这个一般和上面的一起使用,不单独使用。比如 node01 的 CPU 和 Memory 使用率 20:60,node02 的 CPU 和 Memory 使用率 50:50,虽然 node01 的总使用率比 node02 低,但 node02 的 CPU 和 Memory 使用率更接近,从而调度时会优选 node02。) |
ImageLocalityPriority | 优先节点上已有目标镜像的节点 |
最终选出权重最高的节点进行调度。
1.5 指定调度节点方式
1.5.1 nodeName(强制绑定)
pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配
spec:nodeName: node01
# vim myapp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: myapp
spec:replicas: 3selector:matchLabels:app: myapptemplate:metadata:labels:app: myappspec:nodeName: node01containers:- name: myappimage: soscscs/myapp:v1ports:- containerPort: 80
kubectl apply -f myapp.yaml
kubectl get pods -o wide
# 查看详细事件(发现未经过 scheduler 调度分配)
kubectl describe pod myapp-699655c7fd-9zgsk
直接跳过 Scheduler,由 kubelet 负责启动。
1.5.2 nodeSelector(基于标签匹配)
pod.spec.nodeSelector:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label,然后调度 Pod 到目标节点,该匹配规则属于强制约束
spec:nodeSelector:yjs: a
# 获取标签帮助
kubectl label --help
Usage:kubectl label [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version] [options]# 需要获取 node 上的 NAME 名称
kubectl get node
NAME STATUS ROLES AGE VERSION
master Ready master 30h v1.20.11
node01 Ready <none> 30h v1.20.11
node02 Ready <none> 30h v1.20.11# 给对应的 node 设置标签分别为 yjs=a 和 yjs=b
kubectl label nodes node01 yjs=akubectl label nodes node02 yjs=b# 查看标签
kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready master 30h v1.20.11 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/master=
node01 Ready <none> 30h v1.20.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kgc=a,kubernetes.io/arch=amd64,kubernetes.io/hostname=node01,kubernetes.io/os=linux
node02 Ready <none> 30h v1.20.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kgc=b,kubernetes.io/arch=amd64,kubernetes.io/hostname=node02,kubernetes.io/os=linux
# vim myapp1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: myapp1
spec:replicas: 3selector:matchLabels:app: myapp1template:metadata:labels:app: myapp1spec:nodeSelector:yjs: acontainers:- name: myapp1image: soscscs/myapp:v1ports:- containerPort: 80
kubectl apply -f myapp1.yaml kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp1-58cff4d75-52xm5 1/1 Running 0 24s 10.244.1.29 node01 <none> <none>
myapp1-58cff4d75-f747q 1/1 Running 0 24s 10.244.1.27 node01 <none> <none>
myapp1-58cff4d75-kn8gk 1/1 Running 0 24s 10.244.1.28 node01 <none> <none># 查看详细事件(通过事件可以发现要先经过 scheduler 调度分配)
kubectl describe pod myapp1-58cff4d75-52xm5
Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 57s default-scheduler Successfully assigned default/myapp1-58cff4d75-52xm5 to node01Normal Pulled 57s kubelet, node01 Container image "soscscs/myapp:v1" already present on machineNormal Created 56s kubelet, node01 Created container myapp1Normal Started 56s kubelet, node01 Started container myapp1
Scheduler 会调度匹配此标签的节点。
1.5.2.1 管理 Node 标签命令
kubectl label nodes node01 yjs=a
kubectl get nodes --show-labels# 指定标签查询 node 节点
kubectl get node -l yjs=a # 修改一个 label 的值,需要加上 --overwrite 参数
kubectl label nodes node02 yjs=b --overwrite# 删除一个 label,只需在命令行最后指定 label 的 key 名并与一个减号相连即可:
kubectl label nodes node02 yjs-
1.6 节点亲和性与 Pod 亲和性
参考文档:https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node/
1.6.1 节点亲和性(NodeAffinity)
affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution: # 硬策略preferredDuringSchedulingIgnoredDuringExecution: # 软策略
🧠 类比理解:
你更“想去”做什么(软策略),还是“必须去”做什么(硬策略)。
操作符支持:In
、NotIn
、Exists
、DoesNotExist
、Gt
、Lt
Kubernetes 中 label selector 的键值运算关系:在 Kubernetes 中,很多资源(比如 Pod、Deployment、Service)都会用 label selector(标签选择器) 来筛选对象。运算符决定“选谁”或“不选谁”。
运算符 | 含义 | 通俗理解 | 示例 |
---|---|---|---|
In | label 的值在列表中 | “只要它的值在我列的名单里,就选上它” | env In (dev, test) → 选出 env=dev 或 env=test 的 Pod |
NotIn | label 的值不在列表中 | “黑名单里的不要” | env NotIn (prod) → 除了生产环境都选上 |
Gt | label 的值大于某个数 | “比这个数字大的才要” | version Gt 3 → 选出 version=4,5,6… 的 Pod |
Lt | label 的值小于某个数 | “比这个数字小的才要” | version Lt 3 → 选出 version=1,2 的 Pod |
Exists | label 存在即可 | “只要有这个标签就算数,不管值是啥” | Exists zone → 只要有 zone 这个标签 |
DoesNotExist | label 不存在 | “没有这个标签的才要” | DoesNotExist debug → 没有 debug 标签的 Pod |
简单理解:
In / NotIn 看“名单”,Gt / Lt 看“数值”,Exists / DoesNotExist 看“有没有”。
1.6.1.1 案例1 硬策略
先查看节点标签:
kubectl get nodes --show-labels
# vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:name: affinitylabels:app: node-affinity-pod
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/hostname # 指定node的标签operator: NotIn # 设置Pod安装到kubernetes.io/hostname的标签值不在values列表中的node上values:- node02
kubectl apply -f pod1.yaml
kubectl delete pod --all && kubectl apply -f pod1.yaml && kubectl get pods -o wide# 如果硬策略不满足条件,Pod 状态一直会处于 Pending 状态。
1.6.1.2 案例2 软策略
# preferredDuringSchedulingIgnoredDuringExecution:软策略
# vim pod2.yaml
apiVersion: v1
kind: Pod
metadata:name: affinitylabels:app: node-affinity-pod
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1affinity:nodeAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 1 # 如果有多个软策略选项的话,权重越大,优先级越高preference:matchExpressions:- key: kubernetes.io/hostnameoperator: Invalues:- node03
kubectl apply -f pod2.yamlkubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity 1/1 Running 0 5s 10.244.2.35 node02 <none> <none># 把values:的值改成node01,则会优先在node01上创建Pod
kubectl delete pod --all && kubectl apply -f pod2.yaml && kubectl get pods -o wide
软策略无法满足时,并不会阻碍pod的创建,而是会满足硬策略后,继续创建
1.6.1.3 案例3 软硬策略结合
# 如果把硬策略和软策略合在一起使用,则要先满足硬策略之后才会满足软策略
apiVersion: v1
kind: Pod
metadata:name: affinitylabels:app: node-affinity-pod
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution: # 先满足硬策略,排除有kubernetes.io/hostname=node02标签的节点nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/hostnameoperator: NotInvalues:- node02preferredDuringSchedulingIgnoredDuringExecution: # 再满足软策略,优先选择有yjs=a标签的节点- weight: 1preference:matchExpressions:- key: yjsoperator: Invalues:- akubectl delete pod --all && kubectl apply -f pod3.yaml && kubectl get pods -o wide
软硬策略有冲突时,优先满足硬策略再考虑软策略。
1.6.2 Pod 亲和性与反亲和性
调度策略 | 匹配标签 | 操作符 | 拓扑域支持 | 调度目标 |
---|---|---|---|---|
nodeAffinity | 主机 | In, NotIn, Exists, DoesNotExist, Gt, Lt | ❌ | 指定主机 |
podAffinity | Pod | In, NotIn, Exists, DoesNotExist | ✅ | 与指定 Pod 同域 |
podAntiAffinity | Pod | In, NotIn, Exists, DoesNotExist | ✅ | 与指定 Pod 不同域 |
1.6.2.1 案例1 Pod 亲和性调度
kubectl label nodes node01 yjs=a # pod1 所在节点标签
kubectl label nodes node02 yjs=b # pod2 所在节点标签
# 创建一个标签为 app=myapp01 的 Pod
# vim pod3.yaml
apiVersion: v1
kind: Pod
metadata:name: myapp01labels:app: myapp01
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1
kubectl apply -f pod3.yamlkubectl get pods --show-labels -o wide
# 使用 Pod 亲和性调度,创建多个 Pod 资源
# vim pod4.yaml
apiVersion: v1
kind: Pod
metadata:name: myapp02labels:app: myapp02
spec:containers:- name: myapp02image: soscscs/myapp:v1affinity:podAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues:- myapp01topologyKey: yjs
仅当节点和至少一个已运行且有键为“app”且值为“myapp01”的标签的 Pod 处于同一拓扑域时,才可以将该 Pod 调度到节点上。(更确切的说,如果节点 N 具有带有键 yjs 和某个值 V 的标签,则 Pod 有资格在节点 N 上运行,以便集群中至少有一个具有键 yjs 和值为 V 的节点正在运行具有键“app”和值 “myapp01”的标签的 pod。)
topologyKey 是节点标签的键。如果两个节点使用此键标记并且具有相同的标签值,则调度器会将这两个节点视为处于同一拓扑域中。调度器试图在每个拓扑域中放置数量均衡的 Pod。
如果 yjs 对应的值不一样就是不同的拓扑域。比如 Pod1 在 yjs=a 的 Node 上,Pod2 在 yjs=b 的 Node 上,Pod3 在 yjs=a 的 Node 上,则 Pod2 和 Pod1、Pod3 不在同一个拓扑域,而 Pod1 和 Pod3 在同一个拓扑域。
kubectl apply -f pod4.yamlkubectl get pods --show-labels -o wide
1.6.2.2 案例2 Pod 反亲和性调度
示例1:
# vim pod5.yaml
apiVersion: v1
kind: Pod
metadata:name: myapp10labels:app: myapp10
spec:containers:- name: myapp10image: soscscs/myapp:v1affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues:- myapp01topologyKey: kubernetes.io/hostname #表示了区分拓扑域的依据,以hostname为依据
如果节点处于 Pod 所在的同一拓扑域且具有键“app”和值“myapp01”的标签,则该 pod 不应将其调度到该节点上。(如果 topologyKey 为 kubernetes.io/hostname,则意味着当节点和具有键 “app”和值“myapp01”的 Pod 处于相同的拓扑域,Pod 不能被调度到该节点上。)
kubectl apply -f pod5.yamlkubectl get pods --show-labels -o wide
示例2:
# vim pod6.yaml
apiVersion: v1
kind: Pod
metadata:name: myapp20labels:app: myapp20
spec:containers:- name: myapp20image: soscscs/myapp:v1affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues:- myapp01topologyKey: yjs #识别同一拓扑域的关键为yjs键的数值,如果全部的yjs键的值都是一个,则无法满足这个反亲和的硬策略
# 由于指定 Pod 所在的 node01 节点上具有带有键 yjs 和标签值 a 的标签,node02 也有这个yjs=a的标签,所以 node01 和 node02 是在一个拓扑域中,反亲和要求新 Pod 与指定 Pod 不在同一拓扑域,所以新 Pod 没有可用的 node 节点,即为 Pending 状态。
kubectl get pod --show-labels -owide
kubectl label nodes node02 yjs=b --overwritekubectl get pod --show-labels -o wide
1.7 污点(Taint) 和 容忍(Tolerations)
1.7.1 污点(Taint)
节点亲和性,是 Pod 的一种属性(偏好或硬性要求),它使 Pod 被吸引到一类特定的节点。Taint 则相反,它使节点能够排斥一类特定的 Pod。Taint 和 Toleration 相互配合,可以用来避免 Pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 Pod,是不会被该节点接受的。如果将 toleration 应用于 Pod 上,则表示这些 Pod 可以(但不一定)被调度到具有匹配 taint 的节点上。
使用 kubectl taint 命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去。
污点格式:
key=value:effect
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。
当前 taint effect 支持如下三个选项:
类型 | 描述 |
---|---|
NoSchedule | 不调度到此节点 |
PreferNoSchedule | 尽量避免调度具有该污点的 Node 上 |
NoExecute | 不调度 + 驱逐已存在 Pod |
命令示例:
kubectl get nodes
# master 就是因为有 NoSchedule 污点,k8s 才不会将 Pod 调度到 master 节点上
kubectl describe node master
......
Taints: node-role.kubernetes.io/master:NoSchedule
# 设置污点
kubectl taint node node01 key1=value1:NoSchedule# 节点说明中,查找 Taints 字段
kubectl describe node node-name # 去除污点
kubectl taint node node01 key1:NoSchedule-
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp01 1/1 Running 0 4h28m 10.244.2.3 node02 <none> <none>
myapp02 1/1 Running 0 4h13m 10.244.2.4 node02 <none> <none>
myapp03 1/1 Running 0 3h45m 10.244.1.4 node01 <none> <none>kubectl taint node node02 check=mycheck:NoExecute# 查看 Pod 状态,会发现 node02 上的 Pod 已经被全部驱逐(注:如果是 Deployment 或者 StatefulSet 资源类型,为了维持副本数量则会在别的 Node 上再创建新的 Pod)
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp03 1/1 Running 0 3h48m 10.244.1.4 node01 <none> <none>
1.7.2 容忍 (Tolerations)
设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。但我们可以在 Pod 上设置容忍(Tolerations),意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上。
kubectl taint node node01 check=mycheck:NoExecutevim pod3.yamlkubectl apply -f pod3.yaml# 在两个 Node 上都设置了污点后,此时 Pod 将无法创建成功
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp01 0/1 Pending 0 17s <none> <none> <none> <none>
# vim pod3.yaml
apiVersion: v1
kind: Pod
metadata:name: myapp01labels:app: myapp01
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1tolerations:- key: "check"operator: "Equal" # operator: "Equal" 意味着这个值等于value,如果是Exists,则不需要填写value,只要有这个key就容忍value: "mycheck"effect: "NoExecute"tolerationSeconds: 3600 #当需要被驱逐前在node上停留的时间
当节点添加了带有 NoExecute 效果的污点(例如节点故障、资源不足等)时:
tolerationSeconds: 3600 表示 Pod 会在 3600 秒内保持运行,超过这个时间后才会被驱逐。
但如果节点的污点触发了立即驱逐条件(如节点不可用、健康检查失败),Kubernetes 可能会忽略这个容忍时间,直接驱逐 Pod 以保证集群稳定性。
其中的 key、vaule、effect 都要与 Node 上设置的 taint 保持一致
operator 的值为 Exists 将会忽略 value 值,即存在即可
kubectl apply -f pod3.yaml# 在设置了容忍之后,Pod 创建成功
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp01 1/1 Running 0 10m 10.244.1.5 node01 <none> <none>
# 其它注意事项
(1)当不指定 key 值时,表示容忍所有的污点 keytolerations:- operator: "Exists"(2)当不指定 effect 值时,表示容忍所有的污点作用tolerations:- key: "key"operator: "Exists"(3)有多个 Master 存在时,防止资源浪费,可以如下设置
kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule# 如果某个 Node 更新升级系统组件,为了防止业务长时间中断,可以先在该 Node 设置 NoExecute 污点,把该 Node 上的 Pod 都驱逐出去
kubectl taint node node01 check=mycheck:NoExecute# 此时如果别的 Node 资源不够用,可临时给 Master 设置 PreferNoSchedule 污点,让 Pod 可在 Master 上临时创建
kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule# 待所有 Node 的更新操作都完成后,再去除污点
kubectl taint node node01 check=mycheck:NoExecute-
key、value、effect 必须与 Node 上的污点匹配。
operator: Exists
表示存在即可。
1.8 节点维护操作
1.8.1 cordon & drain
命令 | 功能 |
---|---|
kubectl cordon <node> | 标记为不可调度 |
kubectl drain <node> --ignore-daemonsets --delete-local-data --force | 驱逐 Pod |
kubectl uncordon <node> | 恢复可调度状态 |
drain 等价于 “cordon + 驱逐”。
在 Kubernetes 中,驱逐(Eviction)是指将运行中的 Pod 从一个节点上迁移到另一个节点的过程,同时释放原节点上的资源以供其他使用。驱逐通常发生在以下情况:
- 需要将 Pod 迁移到其他节点以维护节点或进行软硬件升级。
- 某个节点的资源不足以容纳新的 Pod,需要将旧的 Pod 驱逐以释放资源。
Kubernetes 可以使用自动驱逐机制来管理 Pod 的迁移。它可以基于节点负载、Pod 优先级等条件来自动选择需要驱逐的 Pod,并将它们迁移到其他节点上。在执行驱逐时,Kubernetes 会首先将 SIGTERM 信号发送给 Pod,然后等待一段时间,让 Pod 自行终止。如果自我终止未成功,则 Kubernetes 会强制终止 Pod 并进行清理工作。
驱逐是 Kubernetes 集群中必不可少的机制之一,它可以确保系统的高冗余性和高可用性,并优化集群中的资源利用率。
1.9 Pod 生命周期(Phase)
阶段 | 说明 |
---|---|
Pending | 已创建但未调度或拉取镜像中 |
Running | 容器已启动运行中 |
Succeeded | 所有容器成功终止(Job 类) |
Failed | 容器失败退出(非0或异常) |
Unknown | 无法获取状态(通信异常) |
详解 phase 的可能状态有:
- Pending:表示 APIServer 创建了 Pod 资源对象并已经存入了 etcd 中,但是它并未被调度完成(比如还没有调度到某台 node 上),或者仍然处于从仓库下载镜像的过程中。
- Running:Pod 已经被调度到某节点之上,并且 Pod 中所有容器都已经被 kubelet 创建。至少有一个容器正在运行,或者正处于启动或者重启状态(也就是说 Running 状态下的 Pod 不一定能被正常访问)。
- Succeeded:有些 pod 不是长久运行的,比如 job、cronjob,一段时间后 Pod 中的所有容器都被成功终止,并且不会再重启。需要反馈任务执行的结果。
- Failed:Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止,比如 command 写的有问题。
- Unknown:表示无法读取 Pod 状态,通常是 kube-controller-manager 无法与 Pod 通信。
1.10 故障排查命令
操作 | 命令 |
---|---|
查看事件 | kubectl describe pod <POD> |
查看日志 | kubectl logs <POD> [-c 容器名] |
进入容器 | kubectl exec -it <POD> bash |
查看集群状态 | kubectl cluster-info |
查看节点状态 | kubectl get nodes |
查看 kubelet 日志 | journalctl -xefu kubelet |
1.11 总结
1.11.1 核心组件协作关系图
kubectl → APIServer → etcd↓ ↑
Controller Manager ←→ Scheduler ←→ kubelet(Node)
1.11.2 核心调度策略层次
- nodeName(直接绑定)
- nodeSelector(Label 匹配)
- nodeAffinity / podAffinity(亲和性调度)
- Taint & Toleration(污点/容忍机制)
- cordon & drain(节点维护与 Pod 迁移)
1.11.3 思维总结
“调度是吸引,污点是排斥,亲和性是偏好,容忍是例外。”
总结
K8s 集群调度是一个“由组件协作驱动、由策略定义规则”的动态过程,其核心目标是在“资源高效利用”与“业务需求匹配”之间找到最优平衡。通过本文的梳理,可将调度体系的核心逻辑归纳为以下三点:
1. 底层基石:组件协作与 List-Watch 机制
K8s 调度的实现依赖于 APIServer、Controller Manager、Scheduler、kubelet 与 etcd 的紧密协作,而 List-Watch 机制则是组件间“实时同步状态、解耦交互逻辑”的关键。从用户通过 kubectl 创建 Pod,到 APIServer 写入 etcd、Scheduler 完成节点匹配、kubelet 启动容器,整个流程环环相扣,确保 Pod 从“创建请求”到“Running 状态”的稳定过渡。
2. 调度策略:从“强制绑定”到“柔性偏好”的分层设计
K8s 提供了多层次的调度策略,以满足不同场景的需求:
- 基础层:
nodeName
直接绑定节点(跳过调度器,适合特殊场景)、nodeSelector
基于标签匹配(简单强制约束); - 灵活层:节点亲和性(NodeAffinity)与 Pod 亲和性/反亲和性(PodAffinity/PodAntiAffinity),通过“硬策略(必须满足)”与“软策略(优先满足)”实现精细化调度,例如让关联业务 Pod 同节点部署、避免同类 Pod 集中在单一节点;
- 排斥层:污点(Taint)与容忍(Toleration)机制,让节点主动“排斥”不匹配的 Pod,同时允许特定 Pod 以“容忍”突破排斥,适合节点隔离(如 Master 节点禁止普通 Pod 调度)、节点维护等场景。
3. 运维保障:从状态监控到节点维护
理解 Pod 生命周期的 5 种核心状态(Pending、Running、Succeeded、Failed、Unknown),是排查调度故障的基础;而 kubectl describe pod
、journalctl -xefu kubelet
等命令,则是定位“调度失败”“容器启动异常”等问题的关键工具。此外,cordon
(标记不可调度)、drain
(驱逐 Pod)等节点维护命令,能在不中断业务的前提下完成节点升级、故障修复,进一步保障集群稳定性。
简言之,K8s 调度的本质是“用规则定义 Pod 与节点的关系”——调度策略是“吸引”Pod 到合适节点的拉力,污点是“排斥”Pod 的推力,而容忍则是应对特殊需求的“例外条款”。掌握这些机制,不仅能解决当前集群的调度问题,更能为后续大规模集群、复杂业务场景的调度优化打下基础。