Kubernetes Pod 深度解析:生命周期、调度与核心配置
Kubernetes Pod 深度解析:生命周期、调度与核心配置
一、Pod 生命周期概述
Pod 作为 Kubernetes 中最小的部署单元,其生命周期涵盖从创建到终止的完整过程,涉及多个关键阶段与状态转换。理解 Pod 生命周期是保障容器化应用稳定运行的核心基础。
1.1 生命周期核心阶段
Pod 生命周期主要包含以下 4 个核心过程,各阶段环环相扣,共同构成 Pod 的完整运行周期:
- Pod 创建过程:用户或控制器发起创建请求,Kubernetes 组件协同完成 Pod 资源的定义、存储与调度。
- 初始化容器(Init Container)执行过程:在主容器启动前运行,负责完成主容器依赖的前置准备工作,如环境检查、配置初始化等。
- 主容器(Main Container)运行过程:核心业务容器运行阶段,包含钩子函数执行与健康探测机制,确保应用可用性。
- 钩子函数:容器启动后(PostStart)与终止前(PreStop)触发的自定义操作。
- 健康探测:通过存活性探测(Liveness Probe)与就绪性探测(Readiness Probe)监控容器状态。
- Pod 终止过程:遵循预设流程优雅停止容器,释放资源并更新集群状态。
1.2 Pod 五大核心状态(相位)
在生命周期中,Pod 会呈现 5 种不同状态,反映其当前运行状况,具体含义与场景如下表所示:
| 状态(Phase) | 核心含义 | 典型场景 |
|---|---|---|
| 挂起(Pending) | API Server 已创建 Pod 资源,但未完成调度或镜像下载 | 1. 集群资源不足(CPU/内存不足) 2. 镜像拉取缓慢或镜像地址错误 3. 调度规则不满足(如节点亲和性不匹配) |
| 运行中(Running) | Pod 已调度至目标节点,所有容器均成功启动 | 主容器正常运行,健康探测无异常 |
| 成功(Succeeded) | 所有容器均正常终止,且不会重启 | 一次性任务(如数据备份、批处理任务)执行完成 |
| 失败(Failed) | 所有容器终止,但至少一个容器退出码非 0 | 1. 容器内应用崩溃 2. 健康探测连续失败导致容器被终止 |
| 未知(Unknown) | API Server 无法获取 Pod 状态信息 | 1. 节点与集群网络通信中断 2. Kubelet 服务异常停止 |
二、Pod 创建与终止流程
2.1 Pod 创建过程(6 步完整流程)
Pod 创建是多组件协同的过程,涉及 API Server、etcd、Scheduler、Kubelet 等核心组件,具体步骤如下:
- 发起创建请求:用户通过
kubectl命令、API 客户端或控制器(如 Deployment)向 API Server 提交 Pod 创建请求,请求中包含 Pod 的规格定义(如容器镜像、资源限制、网络配置等)。 - 资源存储与确认:API Server 接收到请求后,首先验证请求的合法性(如权限、资源格式),验证通过后生成 Pod 对象,并将其元数据与规格信息存入 etcd(Kubernetes 集群的分布式存储),随后向客户端返回创建确认信息。
- 状态变更通知:API Server 实时监控 etcd 中 Pod 资源的变化,通过 Watch 机制将 Pod 创建事件推送至集群中其他组件(如 Scheduler、Kubelet)。
- 调度节点分配:Scheduler(调度器)通过 Watch 机制感知到新的未调度 Pod 后,基于预设调度算法(如资源均衡、亲和性规则)为 Pod 选择最优目标节点,并将调度结果(节点名称)更新至 API Server,再由 API Server 同步至 etcd。
- 容器启动与状态反馈:目标节点上的 Kubelet 组件通过 Watch 机制发现调度到本地的 Pod,首先检查节点资源是否满足 Pod 需求,随后调用容器运行时(如 Docker、Containerd)拉取镜像并启动容器。容器启动完成后,Kubelet 将 Pod 的运行状态(如容器 ID、IP 地址)反馈至 API Server。
- 状态同步与存储:API Server 接收 Kubelet 上报的 Pod 状态信息,更新 etcd 中对应的 Pod 数据,确保集群中所有组件对 Pod 状态的认知一致。
2.2 Pod 终止过程(9 步优雅停止)
为避免强制终止容器导致数据丢失或业务中断,Kubernetes 设计了优雅的 Pod 终止流程,默认宽限期为 30 秒(可通过 terminationGracePeriodSeconds 配置),具体步骤如下:
- 发起删除请求:用户或控制器向 API Server 发送 Pod 删除命令(如
kubectl delete pod <pod-name>)。 - 宽限期标记:API Server 将 Pod 的
deletionTimestamp字段设置为当前时间,并开始计算宽限期。在宽限期内,Pod 仍被视为“存活”状态,但不再接收新的业务流量。 - 标记终止状态:API Server 将 Pod 的状态更新为
Terminating,并通过 Watch 机制通知集群中相关组件(如 Kubelet、Service)。 - 启动关闭流程:Kubelet 感知到 Pod 进入
Terminating状态后,启动本地的 Pod 关闭流程,包括停止容器、清理网络规则等。 - 移除服务关联:节点控制器(Node Controller)发现 Pod 正在关闭,将该 Pod 从所有关联的 Service 资源的 endpoints 列表中移除,确保流量不再路由至该 Pod。
- 执行 PreStop 钩子:若 Pod 定义了
PreStop钩子函数(如关闭应用进程、保存数据),Kubelet 会同步执行该函数。钩子执行期间会阻塞删除操作,若钩子执行时间超过宽限期,Kubelet 将在宽限期结束后强制终止容器。 - 发送停止信号:Kubelet 向容器内的主进程发送
SIGTERM信号(终止信号),通知应用准备停止。应用应监听该信号,执行优雅关闭逻辑(如处理完当前请求、释放连接)。 - 强制终止(可选):若宽限期结束后,容器内仍有运行的进程,Kubelet 将发送
SIGKILL信号(强制终止信号),确保容器被彻底停止。 - 完成删除操作:Kubelet 向 API Server 发送请求,将 Pod 的宽限期设置为 0,API Server 随后从 etcd 中删除该 Pod 的数据,Pod 对用户彻底不可见。
三、初始化容器(Init Container)
初始化容器是在主容器启动前运行的特殊容器,主要用于完成主容器依赖的前置准备工作,其运行机制与应用场景具有鲜明特点。
3.1 核心特性
初始化容器与主容器的差异主要体现在以下两点,这些特性决定了其“前置准备”的角色定位:
- 必成功性:初始化容器必须完全运行并成功退出(退出码为 0)。若某一个初始化容器运行失败(如命令执行错误、依赖未满足),Kubernetes 会反复重启该容器,直至成功完成,主容器不会在初始化容器失败时启动。
- 串行执行性:多个初始化容器严格按照配置顺序串行执行,只有前一个初始化容器成功退出后,下一个初始化容器才会启动。这种串行机制确保了依赖准备工作的顺序性与完整性。
3.2 典型应用场景
初始化容器的设计初衷是解耦主容器与依赖准备逻辑,常见应用场景包括:
- 补充工具与代码:主容器镜像为精简体积未包含的工具(如
curl、ping、jq),可通过初始化容器提供。例如,主容器运行 Java 应用,但需要通过初始化容器下载配置文件或依赖包。 - 依赖等待与检查:确保主容器启动前,其依赖的服务(如数据库、缓存、API 服务)已就绪。例如,主容器运行 Web 应用,需等待 MySQL 数据库启动并可连接后再启动。
- 环境配置初始化:在主容器启动前,修改系统配置、设置环境变量或初始化存储卷。例如,初始化容器为共享存储卷创建目录结构、设置权限。
3.3 实战案例:依赖 MySQL 与 Redis 的 Nginx Pod
需求描述
创建一个运行 Nginx 的 Pod,但要求 Nginx 启动前,必须能连接到 MySQL(192.168.100.60)与 Redis(192.168.100.70)服务器。
配置文件(pod-initcontainer.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pod-initcontainer # Pod 名称
namespace: dev # 所属命名空间,用于资源隔离
spec:
containers:
- name: main-container # 主容器(Nginx)
image: nginx:1.17.1 # 容器镜像,指定版本确保环境一致性
ports:
- name: nginx-port # 端口名称,便于识别
containerPort: 80 # 容器内部监听端口
initContainers: # 初始化容器列表,串行执行
- name: test-mysql # 第一个初始化容器:检查 MySQL 连通性
image: busybox:1.30 # Busybox 镜像包含基础工具(ping)
# 循环 ping MySQL,直到成功(退出码 0),失败则每 2 秒重试
command: ['sh', '-c', 'until ping 192.168.100.60 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
- name: test-redis # 第二个初始化容器:检查 Redis 连通性
image: busybox:1.30
command: ['sh', '-c', 'until ping 192.168.100.70 -c 1 ; do echo waiting for redis...; sleep 2; done;']
操作与验证步骤
-
创建 Pod:
[root@k8s-master01 ~]# kubectl create -f pod-initcontainer.yaml pod/pod-initcontainer created -
查看 Pod 初始状态:
[root@k8s-master01 ~]# kubectl describe pod pod-initcontainer -n dev # 关键事件输出(截取) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 49s default-scheduler Successfully assigned dev/pod-initcontainer to node1 Normal Pulled 48s kubelet, node1 Container image "busybox:1.30" already present on machine Normal Created 48s kubelet, node1 Created container test-mysql Normal Started 48s kubelet, node1 Started container test-mysql
[root@k8s-master01 ~]# kubectl get pods pod-initcontainer -n dev -w
此时 Pod 卡在 `Init:0/2` 状态(表示 2 个初始化容器中 0 个完成),因为 MySQL 与 Redis 地址未可达。
3. **模拟依赖服务就绪**:
在集群节点上新增两个虚拟 IP,模拟 MySQL 与 Redis 服务器就绪:
```bash
[root@k8s-master01 ~]# ifconfig ens160:1 192.168.100.60 netmask 255.255.255.0 up
[root@k8s-master01 ~]# ifconfig ens160:2 192.168.100.70 netmask 255.255.255.0 up
-
动态观察 Pod 状态变化:
[root@k8s-master01 ~]# kubectl get pods pod-initcontainer -n dev -w NAME READY STATUS RESTARTS AGE pod-initcontainer 0/1 Init:0/2 0 15s # 初始状态:0 个初始化完成 pod-initcontainer 0/1 Init:1/2 0 52s # 第一个初始化容器完成(MySQL 连通) pod-initcontainer 0/1 PodInitializing 0 89s # 所有初始化完成,准备启动主容器 pod-initcontainer 1/1 Running 0 90s # 主容器启动成功,Pod 正常运行
四、钩子函数(Hook Functions)
钩子函数是 Kubernetes 为容器提供的生命周期事件回调机制,允许用户在容器启动后与终止前执行自定义逻辑,实现应用的优雅启动与停止。
4.1 两种核心钩子函数
Kubernetes 为每个主容器提供两个钩子函数,分别对应容器生命周期的关键节点:
| 钩子函数 | 触发时机 | 核心作用 | 失败处理机制 |
|---|---|---|---|
| PostStart | 容器创建完成后立即触发(不保证与容器内应用启动顺序,可能应用未就绪时执行) | 1. 初始化应用配置(如修改配置文件) 2. 注册容器信息至服务发现 3. 发送启动通知 |
若钩子执行失败,Kubernetes 会重启容器(遵循 Pod 重启策略) |
| PreStop | 容器收到终止信号(SIGTERM)前触发,阻塞容器终止流程 |
1. 优雅关闭应用(如处理完当前请求) 2. 保存业务数据(如写入持久化存储) 3. 注销服务注册信息 |
若执行时间超过 Pod 宽限期,Kubernetes 会强制终止容器 |
4.2 三种钩子动作定义方式
钩子函数支持通过三种方式定义具体执行的动作,适用于不同场景:
-
Exec 命令:在容器内部执行一条或多条命令,适用于简单的命令行操作。
lifecycle: postStart: exec: command: - /bin/sh - -c - echo "Pod started" > /var/log/pod-start.log # 容器内创建启动日志 -
TCPSocket:尝试与容器内指定的 TCP 端口建立连接,用于验证容器网络可用性。
lifecycle: postStart: tcpSocket: port: 8080 # 检查容器 8080 端口是否可连接(仅验证端口监听,不发送数据) -
HTTPGet:向容器内指定的 HTTP 接口发起 GET 请求,适用于 Web 应用的健康检查或通知。
lifecycle: postStart: httpGet: path: /health # 请求的 URI 路径 port: 80 # 容器内端口 host: 127.0.0.1 # 目标主机(默认容器 IP,可指定其他地址) scheme: HTTP # 协议类型(HTTP 或 HTTPS)
4.3 实战案例:Exec 方式使用钩子函数
需求描述
创建一个 Nginx Pod,通过 PostStart 钩子修改 Nginx 默认首页内容,通过 PreStop 钩子优雅停止 Nginx 服务。
配置文件(pod-hook-exec.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pod-hook-exec
namespace: dev
spec:
containers:
- name: main-container
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
lifecycle:
postStart:
exec:
# 容器启动后,修改 Nginx 默认首页(/usr/share/nginx/html/index.html)
command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
preStop:
exec:
# 容器终止前,执行 Nginx 优雅停止命令(-s quit 等待请求完成,-s stop 强制停止)
command: ["/usr/sbin/nginx","-s","quit"]
操作与验证步骤
-
创建 Pod:
[root@k8s-master01 ~]# kubectl create -f pod-hook-exec.yaml pod/pod-hook-exec created -
查看 Pod 状态:
[root@k8s-master01 ~]# kubectl get pods pod-hook-exec -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE pod-hook-exec 1/1 Running 0 29s 10.244.2.48 node2 # Pod 正常运行 -
验证 PostStart 钩子效果:
通过curl访问 Nginx 首页,确认内容已被修改:[root@k8s-master01 ~]# curl 10.244.2.48 postStart... # 输出结果与钩子函数中定义的内容一致,说明钩子执行成功
五、容器探测(Probe)
容器探测(健康检查)是保障应用可用性的关键机制,通过定期检查容器内应用状态,实现故障自动恢复与流量隔离。Kubernetes 提供两种探测类型,分别解决“应用是否存活”与“应用是否可用”的问题。
5.1 两种核心探测类型
两种探测类型的定位与作用截然不同,需根据业务需求合理配置:
| 探测类型 | 核心目标 | 失败处理逻辑 | 适用场景 |
|---|---|---|---|
| 存活性探测(Liveness Probe) | 检查容器内应用是否“存活”(如进程是否崩溃、死锁) | 若探测失败,Kubernetes 会重启容器(遵循重启策略) | 1. 应用可能出现死锁但进程未退出 2. 应用崩溃后需自动恢复 3. 长期运行的服务(如 Web 服务、数据库) |
| 就绪性探测(Readiness Probe) | 检查容器内应用是否“就绪”(如依赖服务已连接、初始化完成) | 若探测失败,Kubernetes 将该 Pod 从 Service 的 endpoints 列表中移除,不再转发流量 | 1. 应用启动后需耗时初始化(如加载配置、连接数据库) 2. 应用临时不可用(如资源耗尽、依赖服务离线) 3. 避免流量发送至未就绪的应用 |
5.2 三种探测实现方式
与钩子函数类似,容器探测也支持三种实现方式,适用于不同应用类型:
- Exec 命令:在容器内执行命令,通过命令退出码判断状态(0 为成功,非 0 为失败)。
livenessProbe: exec: command: - cat - /tmp/healthy # 检查文件是否存在,存在则探测成功 - TCPSocket:尝试与容器内指定 TCP 端口建立连接,成功建立则探测成功。
readinessProbe: tcpSocket: port: 3306 # 检查 MySQL 3306 端口是否可连接,验证数据库是否就绪 - HTTPGet:向容器内 HTTP 接口发起请求,状态码在 200-399 之间则探测成功。
readinessProbe: httpGet: path: /api/ready # 应用自定义的就绪检查接口 port: 8080 scheme: HTTP
5.3 探测配置参数(精细化控制)
除探测方式外,还可通过以下参数控制探测行为,适配不同应用的特性:
initialDelaySeconds:容器启动后延迟多久开始第一次探测(单位:秒)。关键参数,避免应用未初始化完成时误判失败。例如,Java 应用启动较慢,可设置为 30 秒。timeoutSeconds:探测超时时间(单位:秒),默认 1 秒。若应用接口响应较慢,需适当增大(如 5 秒)。periodSeconds:探测执行频率(单位:秒),默认 10 秒。频率过高会增加资源消耗,过低则可能延迟故障发现。failureThreshold:连续探测失败多少次后判定为最终失败,默认 3 次。避免偶发故障导致误重启或流量移除。successThreshold:连续探测成功多少次后判定为最终成功,默认 1 次。适用于故障恢复后确认应用已恢复。
5.4 实战案例:三种探测方式演示
案例 1:Exec 方式(Liveness Probe)
需求描述
通过检查 /tmp/hello.txt 文件是否存在,判断 Nginx 容器是否存活。若文件不存在,重启容器。
配置文件(pod-liveness-exec.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-exec
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
exec:
command: ["/bin/cat","/tmp/hello.txt"] # 检查文件是否存在
initialDelaySeconds: 5 # 容器启动 5 秒后开始探测
periodSeconds: 10 # 每 10 秒探测一次
操作与验证
-
创建 Pod:
[root@k8s-master01 ~]# kubectl create -f pod-liveness-exec.yaml pod/pod-liveness-exec created -
查看 Pod 详情:
[root@k8s-master01 ~]# kubectl describe pods pod-liveness-exec -n dev # 关键事件输出(截取) Events: Normal Created 20s (x2 over 50s) kubelet, node1 Created container nginx Normal Started 20s (x2 over 50s) kubelet, node1 Started container nginx Normal Killing 20s kubelet, node1 Container nginx failed liveness probe, will be restarted Warning Unhealthy 0s (x5 over 40s) kubelet, node1 Liveness probe failed: cat: can't open '/tmp/hello.txt': No such file or directory可见,因
/tmp/hello.txt不存在,探测失败,Kubelet 重启容器。 -
修复问题:
进入容器创建文件,验证探测恢复:[root@k8s-master01 ~]# kubectl exec -it pod-liveness-exec -n dev -- /bin/sh # 在容器内执行 / # touch /tmp/hello.txt / # exit再次查看 Pod 状态,重启次数不再增加,探测成功。
案例 2:TCPSocket 方式(Liveness Probe)
需求描述
通过检查 8080 端口是否可连接,判断 Nginx 容器是否存活。Nginx 默认监听 80 端口,因此探测会失败,触发容器重启。
配置文件(pod-liveness-tcpsocket.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-tcpsocket
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
tcpSocket:
port: 8080 # 检查 8080 端口(Nginx 未监听)
initialDelaySeconds: 5
periodSeconds: 10
操作与验证
- 创建 Pod后查看状态:
[root@k8s-master01 ~]# kubectl get pods pod-liveness-tcpsocket -n dev NAME READY STATUS RESTARTS AGE pod-liveness-tcpsocket 0/1 CrashLoopBackOff 2 3m19s # 因探测失败,容器反复重启 - 修复问题:
将port: 8080修改为port: 80,重新创建 Pod,探测成功,Pod 稳定运行。
案例 3:HTTPGet 方式(Liveness Probe)
需求描述
通过访问 /hello 路径,判断 Nginx 容器是否存活。Nginx 默认首页路径为 /,/hello 路径不存在,探测失败。
配置文件(pod-liveness-httpget.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-httpget
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet:
scheme: HTTP
port: 80
path: /hello # 访问不存在的路径
initialDelaySeconds: 5
periodSeconds: 10
操作与验证
- 创建 Pod后查看详情:
[root@k8s-master01 ~]# kubectl describe pod pod-liveness-httpget -n dev # 关键事件输出(截取) Warning Unhealthy 6s (x6 over 56s) kubelet, node1 Liveness probe failed: HTTP probe failed with statuscode: 404 # 404 表示路径不存在 Normal Killing 6s (x2 over 36s) kubelet, node1 Container nginx failed liveness probe, will be restarted - 修复问题:
将path: /hello修改为path: /,重新创建 Pod,探测成功(状态码 200),Pod 稳定运行。
六、重启策略(Restart Policy)
Pod 的重启策略定义了容器失败时 Kubernetes 的重启行为,适用于 Pod 内所有容器,是故障恢复的核心配置。
6.1 三种重启策略
Kubernetes 提供三种重启策略,分别对应不同的故障处理需求:
| 重启策略 | 核心逻辑 | 适用场景 |
|---|---|---|
| Always | 默认策略:无论容器退出码为何,均重启容器 | 长期运行的服务(如 Web 服务、数据库、消息队列),需确保服务持续可用 |
| OnFailure | 仅当容器终止且退出码非 0 时(应用故障),才重启容器 | 一次性任务(如数据处理、备份脚本),正常完成(退出码 0)后不重启 |
| Never | 无论容器状态如何,均不重启容器 | 测试环境的临时 Pod、无需恢复的一次性任务 |
6.2 重启延迟机制
为避免容器频繁重启导致资源耗尽,Kubernetes 采用指数退避的重启延迟策略:
- 首次重启:立即执行(无延迟)。
- 后续重启:延迟时间依次为 10 秒、20 秒、40 秒、80 秒、160 秒,最大延迟时间为 300 秒(5 分钟)。
- 若容器稳定运行超过 10 分钟,重启延迟将重置为初始状态(下次故障时立即重启)。
6.3 实战案例:Never 策略演示
需求描述
创建一个 Nginx Pod,配置 Never 重启策略。当存活性探测失败时,容器不重启。
配置文件(pod-restartpolicy.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pod-restartpolicy
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet:
scheme: HTTP
port: 80
path: /hello # 不存在的路径,探测失败
restartPolicy: Never # 设置为不重启
操作与验证
- 创建 Pod:
[root@k8s-master01 ~]# kubectl create -f pod-restartpolicy.yaml pod/pod-restartpolicy created - 查看 Pod 状态:
[root@k8s-master01 ~]# kubectl describe pods pod-restartpolicy -n dev # 关键事件输出(截取) Warning Unhealthy 15s (x3 over 35s) kubelet, node1 Liveness probe failed: HTTP probe failed with statuscode: 404 Normal Killing 15s kubelet, node1 Container nginx failed liveness probe - 验证重启次数:
[root@k8s-master01 ~]# kubectl get pods pod-restartpolicy -n dev NAME READY STATUS RESTARTS AGE pod-restartpolicy 0/1 Running 0 5min42s # 重启次数始终为 0,符合 Never 策略
七、Pod 调度机制
Pod 调度是指 Kubernetes 为 Pod 选择合适节点的过程,默认由 Scheduler 组件自动完成。但实际场景中,常需根据业务需求(如资源偏好、亲和性、安全隔离)控制调度行为。Kubernetes 提供四类调度方式,覆盖从自动到强制的全场景需求。
7.1 调度方式分类与对比
四类调度方式的定位、灵活性与适用场景差异显著,需根据需求选择:
| 调度方式 | 核心逻辑 | 灵活性 | 适用场景 |
|---|---|---|---|
| 自动调度 | Scheduler 基于默认算法(如资源均衡、优先级)自动选择节点 | 低(无需人工配置) | 无特殊调度需求的普通应用,如测试环境服务、无状态应用 |
| 定向调度 | 通过 nodeName 或 nodeSelector 强制指定调度节点 |
中(仅支持标签或节点名匹配) | 1. 固定节点部署(如需访问本地硬件的应用) 2. 简单的节点分组部署(如生产环境节点组、测试环境节点组) |
| 亲和性调度 | 通过 NodeAffinity、PodAffinity、PodAntiAffinity 实现灵活的调度规则,支持“硬限制”与“软限制” |
高(支持复杂规则、优先级) | 1. 应用与节点属性匹配(如 GPU 节点部署 AI 应用) 2. 应用间协同部署(如 Web 应用与缓存应用同节点) 3. 应用副本打散部署(如多副本服务跨节点部署,提高可用性) |
| 污点与容忍调度 | 节点通过“污点”拒绝 Pod 调度,Pod 通过“容忍”忽略污点,实现节点级别的排斥与例外 | 高(支持节点级别的安全隔离、资源保护) | 1. 保护关键节点(如 master 节点不部署业务 Pod) 2. 特殊节点隔离(如 GPU 节点仅允许 AI 应用调度) 3. 故障节点临时隔离(如磁盘故障节点拒绝新 Pod 调度) |
7.2 1. 自动调度(默认)
自动调度是 Kubernetes 的默认行为,无需人工配置,由 Scheduler 组件完成。其核心算法流程如下:
- 过滤(Filtering):从所有节点中排除不满足 Pod 需求的节点(如资源不足、端口冲突、污点排斥),得到“可行节点列表”。
- 打分(Scoring):对可行节点按优先级评分(如资源利用率低的节点得分高、节点负载均衡),选择得分最高的节点。
- 绑定(Binding):将 Pod 与目标节点绑定,更新 API Server 与 etcd 中的 Pod 调度信息。
7.3 2. 定向调度(强制约束)
定向调度通过强制约束将 Pod 调度到指定节点,不满足条件时 Pod 会失败(而非调度到其他节点),适用于需严格控制部署位置的场景。
7.3.1 nodeName:直接指定节点名
nodeName 是最直接的调度方式,跳过 Scheduler 调度逻辑,直接将 Pod 调度到指定名称的节点。
实战案例
- 配置文件(pod-nodename.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-nodename namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 nodeName: node1 # 强制调度到名称为 node1 的节点 - 操作与验证:
- 创建 Pod 后查看调度结果:
[root@k8s-master01 ~]# kubectl get pods pod-nodename -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... pod-nodename 1/1 Running 0 56s 10.244.1.87 node1 # 成功调度到 node1 - 测试无效节点:将
nodeName改为node3(不存在的节点),创建 Pod 后状态为Pending,且NODE字段为node3,说明调度强制但失败。
- 创建 Pod 后查看调度结果:
7.3.2 nodeSelector:基于节点标签匹配
nodeSelector 通过节点标签(Label)进行匹配,需先为节点添加标签,再在 Pod 中指定标签选择器,实现“标签相同的节点才调度”。
实战案例
- 为节点添加标签:
# 为 node1 添加标签 nodeenv=pro(生产环境节点) [root@k8s-master01 ~]# kubectl label nodes node1 nodeenv=pro node/node1 labeled # 为 node2 添加标签 nodeenv=test(测试环境节点) [root@k8s-master01 ~]# kubectl label nodes node2 nodeenv=test node/node2 labeled - 配置文件(pod-nodeselector.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-nodeselector namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 nodeSelector: nodeenv: pro # 仅调度到具有 nodeenv=pro 标签的节点(即 node1) - 操作与验证:
- 创建 Pod 后查看调度结果:
[root@k8s-master01 ~]# kubectl get pods pod-nodeselector -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... pod-nodeselector 1/1 Running 0 47s 10.244.1.87 node1 # 成功调度到 node1 - 测试无效标签:将
nodeSelector改为nodeenv: xxxx(无节点匹配),Pod 状态为Pending,NODE字段为<none>,调度失败。
- 创建 Pod 后查看调度结果:
7.4 3. 亲和性调度(灵活规则)
定向调度的局限性在于“不满足条件则失败”,而亲和性调度支持“硬限制”(必须满足)与“软限制”(优先满足,不满足也可调度),灵活性更高。亲和性调度分为三类,分别对应不同的调度目标。
7.4.1 NodeAffinity(节点亲和性)
以节点属性(标签)为目标,控制 Pod 调度到哪些节点,是 nodeSelector 的扩展,支持更复杂的匹配规则。
核心配置项
affinity:
nodeAffinity:
# 硬限制:必须满足所有规则,否则调度失败(类似 nodeSelector)
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms: # 节点选择列表,满足一个即可
- matchExpressions: # 标签匹配规则(推荐)
- key: nodeenv # 标签键
operator: In # 运算符(In/NotIn/Exists/DoesNotExist/Gt/Lt)
values: ["pro"] # 标签值列表
# 软限制:优先满足规则,不满足也可调度到其他节点
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100 # 权重(1-100),权重越高,优先级越高
preference:
matchExpressions:
- key: gpu
operator: Exists # 匹配存在 gpu 标签的节点(无需指定值)
运算符说明
| 运算符 | 作用 | 示例 |
|---|---|---|
| In | 标签值在指定列表中 | key: nodeenv, operator: In, values: ["pro", "test"] |
| NotIn | 标签值不在指定列表中 | key: nodeenv, operator: NotIn, values: ["dev"] |
| Exists | 节点存在指定标签(无论值为何) | key: gpu, operator: Exists |
| DoesNotExist | 节点不存在指定标签 | key: gpu, operator: DoesNotExist |
| Gt | 标签值(数值型)大于指定值 | key: cpu-cores, operator: Gt, values: ["8"] |
| Lt | 标签值(数值型)小于指定值 | key: cpu-cores, operator: Lt, values: ["4"] |
实战案例 1:硬限制(requiredDuringSchedulingIgnoredDuringExecution)
- 配置文件(pod-nodeaffinity-required.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-nodeaffinity-required namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nodeenv operator: In values: ["xxx", "yyy"] # 无节点匹配该标签 - 操作与验证:
- 创建 Pod 后状态为
Pending,调度失败:[root@k8s-master01 ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... pod-nodeaffinity-required 0/1 Pending 0 16s <none> <none> ...... - 修改
values: ["pro", "yyy"](node1 有nodeenv=pro标签),重新创建 Pod,调度成功:[root@k8s-master01 ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... pod-nodeaffinity-required 1/1 Running 0 11s 10.244.1.89 node1 ......
- 创建 Pod 后状态为
实战案例 2:软限制(preferredDuringSchedulingIgnoredDuringExecution)
- 配置文件(pod-nodeaffinity-preferred.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-nodeaffinity-preferred namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: nodeenv operator: In values: ["xxx", "yyy"] # 无节点匹配该标签(软限制) - 操作与验证:
- 尽管无节点匹配软限制规则,Pod 仍会调度到其他可用节点(如 node2),状态为
Running:[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-preferred -n dev NAME READY STATUS RESTARTS AGE pod-nodeaffinity-preferred 1/1 Running 0 40s
- 尽管无节点匹配软限制规则,Pod 仍会调度到其他可用节点(如 node2),状态为
NodeAffinity 注意事项
- 若同时配置
nodeSelector与nodeAffinity,需两者均满足,Pod 才会调度。 nodeSelectorTerms是列表,满足其中一个即可。- 一个
nodeSelectorTerm中的多个matchExpressions需全部满足,节点才会被选中。 - Pod 运行期间,若节点标签变化导致不再满足亲和性规则,Kubernetes 不会驱逐 Pod(
IgnoredDuringExecution含义)。
7.4.2 PodAffinity(Pod 亲和性)
以已运行的 Pod 为目标,控制新 Pod 与目标 Pod 调度到同一拓扑域(如同一节点、同一机架、同一区域),适用于应用间需低延迟通信的场景(如 Web 应用与 Redis 缓存)。
核心配置项
affinity:
podAffinity:
# 硬限制:新 Pod 必须与目标 Pod 在同一拓扑域
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: # 目标 Pod 的标签选择器
matchExpressions:
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname # 拓扑域键(同一节点:kubernetes.io/hostname;同一区域:failure-domain.beta.kubernetes.io/region)
namespaces: ["dev"] # 目标 Pod 所在的命名空间(默认当前命名空间)
# 软限制:优先与目标 Pod 在同一拓扑域
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname
拓扑域键(topologyKey)说明
拓扑域键用于定义“同一区域”的范围,常用值如下:
kubernetes.io/hostname:同一节点(最常用)。failure-domain.beta.kubernetes.io/zone:同一可用区。failure-domain.beta.kubernetes.io/region:同一区域。- 自定义标签:如
rack-id(同一机架)、room-id(同一机房)。
实战案例
-
创建目标 Pod(pod-podaffinity-target.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-podaffinity-target namespace: dev labels: podenv: pro # 目标 Pod 标签 spec: containers: - name: nginx image: nginx:1.17.1 nodeName: node1 # 固定调度到 node1创建并验证:
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-target.yaml pod/pod-podaffinity-target created [root@k8s-master01 ~]# kubectl get pods pod-podaffinity-target -n dev NAME READY STATUS RESTARTS AGE pod-podaffinity-target 1/1 Running 0 4s # 目标 Pod 运行在 node1 -
创建新 Pod(pod-podaffinity-required.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-podaffinity-required namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: podenv operator: In values: ["pro"] # 匹配目标 Pod 标签 topologyKey: kubernetes.io/hostname # 同一节点 -
操作与验证:
- 创建新 Pod 后,查看调度结果:
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... pod-podaffinity-required 1/1 Running 0 6s 10.244.1.90 node1 # 与目标 Pod 同节点(node1) - 若修改
values: ["xxx"](无目标 Pod 匹配),新 Pod 状态为Pending,调度失败。
- 创建新 Pod 后,查看调度结果:
7.4.3 PodAntiAffinity(Pod 反亲和性)
与 PodAffinity 相反,PodAntiAffinity 控制新 Pod 与目标 Pod 调度到不同拓扑域,适用于多副本应用的高可用部署(如避免多个副本调度到同一节点,防止节点故障导致服务中断)。
实战案例
-
使用上一节的目标 Pod(运行在 node1,标签
podenv=pro)。 -
创建反亲和性 Pod(pod-podantiaffinity-required.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-podantiaffinity-required namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: podenv operator: In values: ["pro"] # 匹配目标 Pod 标签 topologyKey: kubernetes.io/hostname # 不同节点 -
操作与验证:
- 创建 Pod 后,查看调度结果:
[root@k8s-master01 ~]# kubectl get pods pod-podantiaffinity-required -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE ...... pod-podantiaffinity-required 1/1 Running 0 30s 10.244.2.96 node2 # 调度到 node2(与目标 Pod 不同节点)
- 创建 Pod 后,查看调度结果:
7.5 4. 污点与容忍(Taints & Toleration)
前面的调度方式均从“Pod 主动选择节点”角度出发,而污点与容忍从“节点主动拒绝 Pod”角度出发:节点通过“污点”拒绝 Pod 调度,Pod 通过“容忍”忽略污点,实现节点级别的排斥与例外。
7.5.1 污点(Taints):节点的排斥规则
污点是节点的属性,格式为 key=value:effect,其中 effect 定义排斥行为,支持三种类型:
| 污点类型(effect) | 核心作用 | 适用场景 |
|---|---|---|
| PreferNoSchedule | 尽量避免将 Pod 调度到该节点,仅当无其他节点可用时才调度 | 1. 节点性能较低,优先调度其他节点 2. 节点用于特定场景,非必要不调度 |
| NoSchedule | 禁止将新 Pod 调度到该节点,但不影响已运行的 Pod | 1. 节点维护中(如升级系统),不接收新 Pod 2. 节点资源紧张,保护已有应用 |
| NoExecute | 禁止新 Pod 调度到该节点,且驱逐已运行的无容忍的 Pod | 1. 节点故障(如磁盘错误、内存泄漏),需清空 Pod 2. 节点下线,强制迁移所有 Pod |
污点操作命令
# 为节点添加污点(node1 节点添加 tag=chenyu:PreferNoSchedule 污点)
kubectl taint nodes node1 tag=chenyu:PreferNoSchedule
# 移除节点污点(移除 node1 节点的 tag=chenyu:PreferNoSchedule 污点)
kubectl taint nodes node1 tag:PreferNoSchedule-
# 移除节点上所有与 key=tag 相关的污点
kubectl taint nodes node1 tag-
实战案例:污点效果演示
-
环境准备:停止 node2 节点,仅保留 node1 节点,便于观察效果。
-
添加 PreferNoSchedule 污点:
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=chenyu:PreferNoSchedule node/node1 tainted # 创建 Pod,因无其他节点,仍调度到 node1 [root@k8s-master01 ~]# kubectl run taint1 --image=nginx:1.17.1 -n dev [root@k8s-master01 ~]# kubectl get pods taint1 -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE taint1-7665f7fd85-574h4 1/1 Running 0 2m 10.244.1.59 node1 # 调度成功 -
修改为 NoSchedule 污点:
# 移除旧污点,添加新污点 [root@k8s-master01 ~]# kubectl taint nodes node1 tag:PreferNoSchedule- [root@k8s-master01 ~]# kubectl taint nodes node1 tag=chenyu:NoSchedule # 创建新 Pod,调度失败(Pending) [root@k8s-master01 ~]# kubectl run taint2 --image=nginx:1.17.1 -n dev [root@k8s-master01 ~]# kubectl get pods taint2 -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE taint2-544694789-6zmlf 0/1 Pending 0 21s <none> <none> # 新 Pod 调度失败 # 旧 Pod(taint1)仍正常运行 [root@k8s-master01 ~]# kubectl get pods taint1 -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE taint1-7665f7fd85-574h4 1/1 Running 0 5m 10.244.1.59 node1 # 旧 Pod 不受影响 -
修改为 NoExecute 污点:
# 移除旧污点,添加新污点 [root@k8s-master01 ~]# kubectl taint nodes node1 tag:NoSchedule- [root@k8s-master01 ~]# kubectl taint nodes node1 tag=chenyu:NoExecute # 查看所有 Pod 状态,均被驱逐,调度失败 [root@k8s-master01 ~]# kubectl get pods -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED taint1-7665f7fd85-htkmp 0/1 Pending 0 35s <none> <none> <none> taint2-544694789-bn7wb 0/1 Pending 0 35s <none> <none> <none>
7.5.2 容忍(Toleration):Pod 的例外规则
容忍是 Pod 的属性,用于忽略节点的污点,使 Pod 可以调度到有污点的节点。容忍需与污点的 key、value、effect 匹配(部分字段可省略,实现模糊匹配)。
核心配置项
tolerations:
- key: "tag" # 要容忍的污点的 key(必须与污点 key 一致,空则匹配所有 key)
operator: "Equal" # 运算符(Equal:需匹配 value;Exists:无需匹配 value,仅需 key 存在)
value: "chenyu" # 要容忍的污点的 value(仅当 operator=Equal 时需配置)
effect: "NoExecute" # 要容忍的污点的 effect(必须与污点 effect 一致,空则匹配所有 effect)
tolerationSeconds: 30 # 仅当 effect=NoExecute 时生效,Pod 在节点污点触发后,延迟 30 秒被驱逐(给应用优雅关闭时间)
实战案例:容忍 NoExecute 污点
-
环境准备:node1 节点已添加
tag=chenyu:NoExecute污点,Pod 无法调度。 -
创建带容忍的 Pod(pod-toleration.yaml):
apiVersion: v1 kind: Pod metadata: name: pod-toleration namespace: dev spec: containers: - name: nginx image: nginx:1.17.1 tolerations: - key: "tag" operator: "Equal" value: "chenyu" effect: "NoExecute" # 容忍 NoExecute 类型的污点 -
操作与验证:
- 创建 Pod 后,查看状态:
[root@k8s-master01 ~]# kubectl create -f pod-toleration.yaml pod/pod-toleration created [root@k8s-master01 ~]# kubectl get pods pod-toleration -n dev -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED pod-toleration 1/1 Running 0 3s 10.244.1.62 node1 <none> # 成功调度到 node1 - 说明容忍生效,Pod 忽略了 node1 的 NoExecute 污点,正常调度并运行。
- 创建 Pod 后,查看状态:
7.5.3 常见使用场景
- 保护 Master 节点:Kubeadm 搭建的集群默认给 Master 节点添加
node-role.kubernetes.io/master:NoSchedule污点,禁止业务 Pod 调度到 Master 节点,确保 Master 稳定。 - 特殊资源节点隔离:GPU 节点添加
gpu=true:NoSchedule污点,仅允许带容忍的 AI 应用调度,避免资源浪费。 - 节点故障临时隔离:节点磁盘故障时,添加
node.kubernetes.io/disk-pressure:NoExecute污点,驱逐现有 Pod 并拒绝新 Pod,防止故障扩散。
八、总结
Pod 作为 Kubernetes 生态的核心,其生命周期管理与调度机制直接决定了应用的可用性、稳定性与资源利用率。本文从 Pod 生命周期的完整流程出发,详细解析了初始化容器、钩子函数、容器探测、重启策略四大核心机制,以及自动调度、定向调度、亲和性调度、污点与容忍四大调度方式,并通过实战案例验证了关键配置的效果。
在实际应用中,需根据业务需求灵活组合这些机制:
- 对于长期运行的服务,需配置合理的健康探测(Liveness + Readiness)与重启策略(Always),确保故障自动恢复。
- 对于有依赖的应用,通过初始化容器(Init Container)处理前置依赖,避免主容器启动失败。
- 对于资源敏感或高可用需求的应用,需利用亲和性调度(PodAntiAffinity)实现副本打散,或通过污点与容忍保护关键节点。
深入理解并熟练运用这些机制,是构建稳定、高效 Kubernetes 集群的基础,也是迈向 Kubernetes 高级应用的关键一步。
3s 10.244.1.62 node1 # 成功调度到 node1
```
- 说明容忍生效,Pod 忽略了 node1 的 NoExecute 污点,正常调度并运行。
7.5.3 常见使用场景
- 保护 Master 节点:Kubeadm 搭建的集群默认给 Master 节点添加
node-role.kubernetes.io/master:NoSchedule污点,禁止业务 Pod 调度到 Master 节点,确保 Master 稳定。 - 特殊资源节点隔离:GPU 节点添加
gpu=true:NoSchedule污点,仅允许带容忍的 AI 应用调度,避免资源浪费。 - 节点故障临时隔离:节点磁盘故障时,添加
node.kubernetes.io/disk-pressure:NoExecute污点,驱逐现有 Pod 并拒绝新 Pod,防止故障扩散。
八、总结
Pod 作为 Kubernetes 生态的核心,其生命周期管理与调度机制直接决定了应用的可用性、稳定性与资源利用率。本文从 Pod 生命周期的完整流程出发,详细解析了初始化容器、钩子函数、容器探测、重启策略四大核心机制,以及自动调度、定向调度、亲和性调度、污点与容忍四大调度方式,并通过实战案例验证了关键配置的效果。
在实际应用中,需根据业务需求灵活组合这些机制:
- 对于长期运行的服务,需配置合理的健康探测(Liveness + Readiness)与重启策略(Always),确保故障自动恢复。
- 对于有依赖的应用,通过初始化容器(Init Container)处理前置依赖,避免主容器启动失败。
- 对于资源敏感或高可用需求的应用,需利用亲和性调度(PodAntiAffinity)实现副本打散,或通过污点与容忍保护关键节点。
深入理解并熟练运用这些机制,是构建稳定、高效 Kubernetes 集群的基础,也是迈向 Kubernetes 高级应用的关键一步。
更多推荐
所有评论(0)