Kubernetes Pod 调度与管理完全指南
第一部分:Pod 亲和性与反亲和性
Pod 调度是 Kubernetes 的核心功能之一。默认情况下,调度器(kube-scheduler)会根据资源请求(Request)进行打分筛选,但生产环境中我们往往需要更精细的控制:哪些 Pod 应该部署在一起(低延迟、高吞吐),哪些 Pod 应该物理隔离(高可用、容灾)。
亲和性(Affinity)与反亲和性(Anti-Affinity)提供了比 nodeSelector 更强大的表达能力。
1.1 核心概念演进
-
NodeSelector(节点选择器):最原始的方式。只能将 Pod 调度到包含特定标签的节点上,逻辑是“硬性必须”,且无法表达“优先”或“互斥”。
-
Node Affinity(节点亲和性):NodeSelector 的升级版。支持
required(硬亲和)和preferred(软亲和)两种策略。 -
Pod Affinity/Anti-Affinity(Pod 亲和/反亲和):最强大的调度能力。不再基于节点标签,而是根据 已经在节点上运行的 Pod 的标签 来决定调度。解决了“希望两个服务跑在同一台机器上”或“不希望两个副本跑在同一可用区”的问题。
1.2 Node Affinity(节点亲和性)
Node Affinity 允许你告诉调度器:这个 Pod 应该(或必须)被调度到具有特定标签的节点上。
1.2.1 配置语法
在 Pod 的 spec 中,通过 affinity.nodeAffinity 字段定义。
-
requiredDuringSchedulingIgnoredDuringExecution:硬亲和。如果节点不满足条件,Pod 不会被调度。相当于增强版的nodeSelector。 -
preferredDuringSchedulingIgnoredDuringExecution:软亲和。调度器会尽量满足,但如果找不到,Pod 也会被调度到不满足条件的节点上。
示例:将 Pod 调度到有 GPU 且为 SSD 磁盘的节点
yaml
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
affinity:
nodeAffinity:
# 硬性要求:必须包含 gpu=true 标签
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- "true"
# 软性偏好:最好是 disk-type=ssd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
containers:
- name: cuda-container
image: nvidia/cuda:latest
Operator 操作符详解:
-
In:标签值在列表中。 -
NotIn:标签值不在列表中。 -
Exists:只要节点存在该标签(无论值是什么)。 -
DoesNotExist:节点不存在该标签。 -
Gt:值大于指定数值(字符串转数值比较)。 -
Lt:值小于指定数值。
1.2.2 调度阶段(DuringScheduling vs DuringExecution)
IgnoredDuringExecution 后缀意味着:如果 Pod 已经成功运行,即使节点上的标签后来发生了变化(不再满足亲和性),Pod 也不会被驱逐。Kubernetes 仅在调度时刻检查此条件。
1.3 Pod Affinity(Pod 亲和性)
Pod 亲和性决定一个 Pod 是否应该与某个 特定的 Pod 集合 运行在同一个“拓扑域”中。这里的“拓扑域”通常指 Node,但也可以是机架、可用区(Zone)等。
1.3.1 核心参数
-
labelSelector:通过标签选择器找到一组参考 Pod。 -
topologyKey:指定拓扑域的键(如kubernetes.io/hostname表示节点,topology.kubernetes.io/zone表示可用区)。 -
namespaceSelector:默认只在当前 namespace 查找,可通过此字段跨命名空间匹配。
场景:将 Web 服务与 Redis 缓存部署在同一台物理机上,以降低网络延迟
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
# 拓扑域:同一节点
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
在这个例子中,web-server 的 Pod 只会被调度到 已经运行了带有 app=redis 标签的 Pod 的节点 上。
1.4 Pod Anti-Affinity(Pod 反亲和性)
反亲和性是亲和性的孪生兄弟,用于实现 互斥。它将 Pod 尽量(或必须)避开拥有特定标签的 Pod。
场景:将同一个 Deployment 的副本打散到不同节点,防止单点故障(高可用)
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ha
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
# 硬性反亲和:同一个 Deployment 的副本绝对不能调度到同一节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
如果集群只有 2 个节点,而 replicas 设为 3,那么第三个 Pod 将处于 Pending 状态,因为找不到没有运行 app=nginx Pod 的节点了(除非节点数足够多)。
1.5 高级用法与性能考量
1.5.1 topologySpreadConstraints(拓扑分布约束)
在 Kubernetes v1.19+ 中,topologySpreadConstraints 是反亲和性的更现代、更精细的替代方案。它允许你定义“在指定拓扑域中,最多允许有多少个同类型的 Pod”。
示例:确保服务在可用区间均匀分布
yaml
spec:
topologySpreadConstraints:
- maxSkew: 1 # 最大偏差,即最多允许一个 Zone 比另一个 Zone 多 1 个 Pod
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # 如果无法满足,则不调度
labelSelector:
matchLabels:
app: my-app
-
maxSkew: 1意味着所有可用区之间的 Pod 数量差异不能超过 1。 -
whenUnsatisfiable可以设为ScheduleAnyway(软约束)或DoNotSchedule(硬约束)。
1.5.2 亲和性/反亲和性的开销
-
计算复杂度:Pod 反亲和性(尤其是跨 Namespace 的)会大幅增加调度器的计算开销。对于大规模集群(数千节点),大量的反亲和性规则可能导致调度延迟。
-
可用区打散:推荐优先使用
topologySpreadConstraints,它在性能上优于传统的podAntiAffinity,且逻辑更清晰。
第二部分:污点(Taints)与容忍度(Tolerations)
如果说亲和性是“Pod 选择节点”,那么污点与容忍度则是 “节点拒绝 Pod” 的机制。它允许节点排斥一类特定的 Pod。
2.1 核心概念
-
Taint(污点):应用在 节点 上。如果一个节点被标记了污点,默认情况下,没有任何 容忍度(Toleration) 的 Pod 是不会被调度到该节点上的。
-
Toleration(容忍度):应用在 Pod 上。允许 Pod 调度到具有匹配污点的节点上。
2.2 污点的组成
每个污点由 key、value 和 effect 组成。格式:key=value:effect
Effect(效果)有三种:
-
NoSchedule:核心影响。如果 Pod 不容忍此污点,则 Pod 不会被调度到此节点。已有的 Pod 不受影响。 -
PreferNoSchedule:软版本。系统会尽量不让 Pod 调度到此节点,但如果资源紧张,还是有可能调度上来。 -
NoExecute:最严厉。如果 Pod 不容忍此污点:-
不会被调度到此节点。
-
该节点上已有的、不容忍的 Pod 将被驱逐。
-
2.2.1 管理污点命令
bash
# 添加污点 kubectl taint nodes node1 key1=value1:NoSchedule # 移除污点(末尾加 -) kubectl taint nodes node1 key1=value1:NoSchedule- # 查看污点 kubectl describe node node1 | grep Taints
2.3 容忍度的配置
在 Pod 的 spec.tolerations 中定义。
2.3.1 匹配规则
-
完全匹配:
key、value、effect三个完全一致。 -
存在性匹配:
operator: Exists,只要key和effect相同,忽略value是否匹配。 -
通配 Effect:如果不指定
effect,表示容忍所有 effects。
示例:
yaml
tolerations: # 1. 完全匹配容忍 - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" # 2. 只要存在 key1,无论 value 是什么,都容忍 NoSchedule - key: "key1" operator: "Exists" effect: "NoSchedule" # 3. 容忍所有的污点(谨慎使用) - operator: "Exists"
2.4 典型应用场景
场景一:专用节点
有时候我们希望某些节点只运行特定类型的服务(例如只运行监控组件),不希望其他业务 Pod 占用资源。
bash
# 给节点打污点 kubectl taint nodes node-monitoring dedicated=monitoring:NoSchedule
然后,只有具有以下容忍度的 Pod 才能被调度上去:
yaml
tolerations: - key: "dedicated" operator: "Equal" value: "monitoring" effect: "NoSchedule"
场景二:有特殊硬件的节点(GPU)
给 GPU 节点打污点,防止普通 Pod 调度上去占用 CPU 资源,只允许需要 GPU 的 Pod 调度。
bash
kubectl taint nodes node-gpu gpu=true:NoSchedule
只有配置了容忍度的 Pod 才能使用 GPU 节点。
场景三:节点故障驱逐(NoExecute)
这是 Kubernetes 节点自我修复的关键机制。当节点失联(NotReady)时,Controller Manager 会自动给节点添加污点:
-
node.kubernetes.io/unreachable:节点失联。 -
node.kubernetes.io/not-ready:节点 NotReady 状态。
此时,如果 Pod 没有容忍这两个污点,且没有配置 tolerationSeconds,它们将被立即驱逐(或在默认的 5 分钟宽限期后驱逐)。
配置宽限期:
yaml
tolerations: - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" tolerationSeconds: 300 # 容忍 300 秒,如果 300 秒后节点仍未恢复,则 Pod 被驱逐
2.5 污点与亲和性的协作
在生产环境中,污点通常与亲和性结合使用。例如:
-
给节点打污点防止普通 Pod 进入。
-
给特定 Pod 加容忍度允许进入。
-
给该 Pod 加 Node Affinity,确保它只调度到这些打了污点的节点(防止调度到其他节点)。
第三部分:Pod 生命周期(Lifecycle)
理解 Pod 从创建到终止的全过程,对于排查故障和编写优雅的应用至关重要。
3.1 Pod 的阶段(Phase)
Pod 的 status.phase 是一个简单的状态机,汇总了 Pod 的生命周期阶段:
| Phase | 描述 |
|---|---|
| Pending | Pod 已被 Kubernetes 系统接受,但有一个或多个容器镜像尚未创建,或者尚未调度到节点。 |
| Running | Pod 已绑定到某个节点,且所有容器已创建,至少有一个容器正在运行,或正在启动/重启中。 |
| Succeeded | Pod 中所有容器均已成功终止,并且不会重启。常见于 Job 或 CronJob 完成任务后。 |
| Failed | Pod 中所有容器均已终止,且至少有一个容器以失败状态终止(非 0 退出码)。 |
| Unknown | 无法获取 Pod 状态,通常是由于与 Pod 所在节点通信失败。 |
3.2 容器状态(Container States)
Pod 内部的每个容器都有更细粒度的状态:
-
Waiting:容器正在等待(比如拉取镜像、等待依赖)。
-
Running:正常运行。
-
Terminated:运行结束(成功或失败)。包含
exit code、reason和signal。
3.3 容器生命周期钩子(Lifecycle Hooks)
Kubernetes 提供了两个钩子函数,让容器可以在生命周期中的关键点执行代码,实现优雅启动和优雅终止。
3.3.1 PostStart
在容器 创建后 立即执行。注意:PostStart 的执行与容器的 ENTRYPOINT 是异步的,Kubernetes 不会等待 PostStart 完成才标记容器为 Running。如果 PostStart 失败,容器会被终止。
3.3.2 PreStop
在容器 终止前 执行。这是实现优雅下线最关键的钩子。当 Pod 被删除时,Kubernetes 会先调用 PreStop 钩子,等待钩子执行完毕(或超过宽限期),再发送 SIGTERM 信号给容器。
典型用法:在 PreStop 中让应用从负载均衡器摘除、等待现有连接处理完毕。
yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: nginx
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from postStart > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15 && nginx -s quit"]
在这个例子中,当 Pod 被删除时,会先执行 sleep 15,给应用 15 秒的时间处理完现有请求,然后再优雅关闭 Nginx。
3.4 Pod 的终止流程(Termination Flow)
当执行 kubectl delete pod 或 Deployment 缩容时,会发生一系列有序的事件:
-
Pod 标记为 Terminating:API Server 更新 Pod 状态,设置
deletionTimestamp。 -
kubelet 执行 PreStop 钩子:如果定义了
preStop,kubelet 会在本地执行该命令。 -
kubelet 发送 SIGTERM 信号:如果 PreStop 执行完毕,或者超过了
terminationGracePeriodSeconds(默认 30s)的限制,kubelet 会向容器主进程发送SIGTERM信号。 -
Endpoint 更新:几乎在 Pod 标记为 Terminating 的同时,控制平面会将该 Pod 从所有 Service 的 Endpoints 列表中移除。新的流量不再转发给该 Pod。
-
宽限期倒计时:kubelet 等待容器优雅退出(默认 30 秒)。
-
强制终止:如果容器在宽限期后仍未退出,kubelet 发送
SIGKILL强制杀死容器。 -
Pod 移除:kubelet 通知 API Server 移除该 Pod 对象。
关键参数 terminationGracePeriodSeconds:
在 Pod 的 spec 中定义,默认为 30 秒。如果应用需要更长的关闭时间(如处理长连接),应调大此值。
yaml
spec: terminationGracePeriodSeconds: 60
3.5 重启策略(RestartPolicy)
Pod 的 spec.restartPolicy 决定了容器退出后的行为:
-
Always(默认):无论容器退出码是什么,都会重启。
-
OnFailure:只有容器以非 0 状态退出时才重启。
-
Never:从不重启。
注意:重启策略是针对 Pod 内的 容器,且由 kubelet 在本节点上执行。对于 Job 类工作负载,通常设为 OnFailure 或 Never;对于长期运行的 Service,使用 Always。
第四部分:健康探测(Probes)
生产环境中,Pod 成功启动并进入 Running 状态,并不意味着它可以对外提供服务(例如可能处于死锁状态、内存泄露但进程未挂)。健康探测是保障服务 自愈 和 流量安全 的核心机制。
Kubernetes 提供了三种探针(Probe),由 kubelet 在节点上执行:
-
livenessProbe(存活探针):判断容器是否 活着。如果失败,kubelet 会 杀死容器,并根据
restartPolicy决定是否重启。用于修复死锁。 -
readinessProbe(就绪探针):判断容器是否 准备好接收流量。如果失败,Pod 的 IP 会从 Service 的 Endpoints 中移除。不会重启容器。用于实现滚动更新和流量切换。
-
startupProbe(启动探针):v1.16+ 引入。针对启动较慢的容器(如 Java 应用)。如果定义了 startupProbe,在它成功之前,livenessProbe 和 readinessProbe 将被禁用。一旦 startupProbe 成功,其余探针接管。如果 startupProbe 在
failureThreshold * periodSeconds时间内一直失败,容器会被杀死。
4.1 探测方式
三种探针均支持以下三种执行方式:
4.1.1 exec
在容器内执行命令,退出码为 0 表示成功,非 0 表示失败。
yaml
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
4.1.2 httpGet
向容器发送 HTTP GET 请求,状态码在 200-400 之间(含 200,不含 400)视为成功。
yaml
readinessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 5
4.1.3 tcpSocket
尝试与容器的指定端口建立 TCP 连接,能连接则成功。
yaml
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 15
periodSeconds: 10
4.2 探针参数调优
合理配置探针参数是 K8s 生产环境最佳实践的关键。配置不当可能导致“误杀”或“流量丢失”。
| 参数 | 含义 | 默认值 | 生产建议 |
|---|---|---|---|
| initialDelaySeconds | 容器启动后多久开始探测 | 0 | 必须设置,尤其是对于启动慢的应用(如 Java),建议 10-30s。 |
| periodSeconds | 探测频率 | 10 | 默认即可。高频探测(如 1s)会增加 kubelet 负载。 |
| timeoutSeconds | 探测超时时间 | 1 | 对于网络波动或复杂脚本,建议调至 5-10s。 |
| successThreshold | 成功阈值(失败->成功所需连续成功次数) | 1 | 通常保持 1。对于 readinessProbe,可以设为 2 防止抖动。 |
| failureThreshold | 失败阈值(成功->失败所需连续失败次数) | 3 | 默认 3 次。对于关键服务或启动慢的服务,建议调高(如 5-10)防止瞬时空闲毛刺导致重启。 |
公式:容器被重启的最短时间 ≈ initialDelaySeconds + (failureThreshold * periodSeconds)。
4.3 常见场景配置
场景一:Java Spring Boot 应用
Spring Boot 应用启动慢(加载类、连接数据库),且通常有 /actuator/health 端点。
yaml
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60 # 给 JVM 足够启动时间
periodSeconds: 10
failureThreshold: 3 # 连续 3 次失败才重启(约 30 秒容忍)
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
failureThreshold: 2
场景二:大型单体应用(启动极慢)
如果应用启动需要 2-3 分钟,使用 startupProbe 防止存活探针在启动期间误杀。
yaml
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 允许探测 30 次
periodSeconds: 10 # 每 10 秒一次,总共允许 300 秒启动
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
initialDelaySeconds: 0 # 注意:startupProbe 成功前,livenessProbe 不会生效
4.4 探针的最佳实践
-
Readiness 与 Liveness 端点分离:永远不要将业务逻辑的“繁忙”状态(如高 CPU)作为 Liveness 失败的依据。Liveness 应该只检测“死锁”或“彻底崩溃”。如果 Liveness 因为 CPU 高而失败,会导致滚动更新中断。
-
Readiness 可以包含依赖检查:如果应用依赖数据库或 Redis,Readiness 探针可以检测这些依赖是否可用。如果依赖不可用,就绪状态变为 False,流量被切断,但容器不会重启,直到依赖恢复。
-
避免在 Liveness 中做过于复杂的校验:如果 Liveness 探针需要执行复杂的 SQL 查询,一旦数据库响应慢,Liveness 超时,容器会被重启,形成“雪崩效应”。
第五部分:实战结合——生产级 Deployment 配置
将以上四个概念融合到一个生产级的 Deployment 示例中,展示它们如何协同工作。
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: awesome-app
spec:
replicas: 3
selector:
matchLabels:
app: awesome-app
template:
metadata:
labels:
app: awesome-app
version: v2
spec:
# 1. 优雅终止时间
terminationGracePeriodSeconds: 60
# 2. 容器定义
containers:
- name: app
image: myregistry/awesome-app:2.0
ports:
- containerPort: 8080
# 3. 资源限制
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "1000m"
# 4. 健康探测配置
startupProbe:
httpGet:
path: /actuator/info
port: 8080
failureThreshold: 12 # 12 * 5 = 60秒启动时间
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 2
successThreshold: 1
timeoutSeconds: 3
# 5. 生命周期钩子(优雅下线)
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 15 && /usr/local/bin/app-graceful-stop"
# 6. 调度策略:反亲和性(打散到不同节点) + 容忍度(若节点特殊)
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- awesome-app
topologyKey: kubernetes.io/hostname
tolerations:
- key: "dedicated"
operator: "Equal"
value: "app-tier"
effect: "NoSchedule"
第六部分:故障排查与调试
在实际运维中,经常会遇到 Pod 无法调度、频繁重启等问题。以下是一些排查思路。
6.1 调度失败(Pending)
-
命令:
kubectl describe pod <pod-name>查看 Events。 -
常见原因:
-
资源不足:节点资源(CPU/Memory)不足,或节点有污点未容忍。
-
节点选择器不匹配:
nodeSelector或 Node Affinity 无法匹配任何节点。 -
Pod 反亲和性:由于反亲和性规则,找不到符合要求的节点(例如副本数大于可用节点数)。
-
6.2 容器不断重启(CrashLoopBackOff)
-
命令:
kubectl logs <pod-name> --previous查看上一次崩溃的日志。 -
常见原因:
-
Liveness 探针配置太严格:
initialDelaySeconds太小或failureThreshold太小,导致启动慢的应用被杀死。 -
应用内部错误:应用启动后立即退出(如连接数据库失败)。
-
内存限制(OOMKilled):
kubectl describe pod查看Last State是否为OOMKilled。
-
6.3 服务不可用(但 Pod 状态为 Running)
-
原因:
readinessProbe失败,Pod 未进入 Endpoints。 -
排查:
kubectl describe pod查看 Readiness 探针的失败日志。检查/healthz端点是否返回了非 2xx 状态码。
第七部分:总结对比
| 特性 | 核心目的 | 作用对象 | 调度阶段 | 失败后果 |
|---|---|---|---|---|
| Node Affinity | Pod 选择特定属性的节点 | Node | Scheduling | 无法调度 (Pending) |
| Pod Affinity | Pod 与参考 Pod 聚集 | Node/Zone | Scheduling | 无法调度 (Pending) |
| Pod Anti-Affinity | Pod 与参考 Pod 分散 | Node/Zone | Scheduling | 无法调度 (Pending) |
| Taints | 节点排斥 Pod | Node | Scheduling + Execution | 无法调度或驱逐 |
| Tolerations | Pod 允许被调度到有污点的节点 | Pod | Scheduling + Execution | 无(允许) |
| livenessProbe | 检测容器是否存活,修复死锁 | Container | Runtime | 重启容器 |
| readinessProbe | 检测容器是否可接收流量 | Container | Runtime | 摘除 Service 流量 |
| startupProbe | 保护启动慢的容器 | Container | Runtime | 重启容器(仅在启动阶段) |
| Lifecycle Hooks | 自定义启动和终止动作 | Container | Runtime | 影响容器启动或终止流程 |
核心思想
-
调度(Affinity/Taints) 解决的是“Pod 放在哪”的问题,主要影响 集群资源利用率 和 可用性拓扑。
-
生命周期与探针(Probes/Lifecycle) 解决的是“Pod 运行得怎么样”的问题,主要影响 服务自愈 和 流量无损变更。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)