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 等核心组件,具体步骤如下:

  1. 发起创建请求:用户通过 kubectl 命令、API 客户端或控制器(如 Deployment)向 API Server 提交 Pod 创建请求,请求中包含 Pod 的规格定义(如容器镜像、资源限制、网络配置等)。
  2. 资源存储与确认:API Server 接收到请求后,首先验证请求的合法性(如权限、资源格式),验证通过后生成 Pod 对象,并将其元数据与规格信息存入 etcd(Kubernetes 集群的分布式存储),随后向客户端返回创建确认信息。
  3. 状态变更通知:API Server 实时监控 etcd 中 Pod 资源的变化,通过 Watch 机制将 Pod 创建事件推送至集群中其他组件(如 Scheduler、Kubelet)。
  4. 调度节点分配:Scheduler(调度器)通过 Watch 机制感知到新的未调度 Pod 后,基于预设调度算法(如资源均衡、亲和性规则)为 Pod 选择最优目标节点,并将调度结果(节点名称)更新至 API Server,再由 API Server 同步至 etcd。
  5. 容器启动与状态反馈:目标节点上的 Kubelet 组件通过 Watch 机制发现调度到本地的 Pod,首先检查节点资源是否满足 Pod 需求,随后调用容器运行时(如 Docker、Containerd)拉取镜像并启动容器。容器启动完成后,Kubelet 将 Pod 的运行状态(如容器 ID、IP 地址)反馈至 API Server。
  6. 状态同步与存储:API Server 接收 Kubelet 上报的 Pod 状态信息,更新 etcd 中对应的 Pod 数据,确保集群中所有组件对 Pod 状态的认知一致。

2.2 Pod 终止过程(9 步优雅停止)

为避免强制终止容器导致数据丢失或业务中断,Kubernetes 设计了优雅的 Pod 终止流程,默认宽限期为 30 秒(可通过 terminationGracePeriodSeconds 配置),具体步骤如下:

  1. 发起删除请求:用户或控制器向 API Server 发送 Pod 删除命令(如 kubectl delete pod <pod-name>)。
  2. 宽限期标记:API Server 将 Pod 的 deletionTimestamp 字段设置为当前时间,并开始计算宽限期。在宽限期内,Pod 仍被视为“存活”状态,但不再接收新的业务流量。
  3. 标记终止状态:API Server 将 Pod 的状态更新为 Terminating,并通过 Watch 机制通知集群中相关组件(如 Kubelet、Service)。
  4. 启动关闭流程:Kubelet 感知到 Pod 进入 Terminating 状态后,启动本地的 Pod 关闭流程,包括停止容器、清理网络规则等。
  5. 移除服务关联:节点控制器(Node Controller)发现 Pod 正在关闭,将该 Pod 从所有关联的 Service 资源的 endpoints 列表中移除,确保流量不再路由至该 Pod。
  6. 执行 PreStop 钩子:若 Pod 定义了 PreStop 钩子函数(如关闭应用进程、保存数据),Kubelet 会同步执行该函数。钩子执行期间会阻塞删除操作,若钩子执行时间超过宽限期,Kubelet 将在宽限期结束后强制终止容器。
  7. 发送停止信号:Kubelet 向容器内的主进程发送 SIGTERM 信号(终止信号),通知应用准备停止。应用应监听该信号,执行优雅关闭逻辑(如处理完当前请求、释放连接)。
  8. 强制终止(可选):若宽限期结束后,容器内仍有运行的进程,Kubelet 将发送 SIGKILL 信号(强制终止信号),确保容器被彻底停止。
  9. 完成删除操作:Kubelet 向 API Server 发送请求,将 Pod 的宽限期设置为 0,API Server 随后从 etcd 中删除该 Pod 的数据,Pod 对用户彻底不可见。

三、初始化容器(Init Container)

初始化容器是在主容器启动前运行的特殊容器,主要用于完成主容器依赖的前置准备工作,其运行机制与应用场景具有鲜明特点。

3.1 核心特性

初始化容器与主容器的差异主要体现在以下两点,这些特性决定了其“前置准备”的角色定位:

  1. 必成功性:初始化容器必须完全运行并成功退出(退出码为 0)。若某一个初始化容器运行失败(如命令执行错误、依赖未满足),Kubernetes 会反复重启该容器,直至成功完成,主容器不会在初始化容器失败时启动
  2. 串行执行性:多个初始化容器严格按照配置顺序串行执行,只有前一个初始化容器成功退出后,下一个初始化容器才会启动。这种串行机制确保了依赖准备工作的顺序性与完整性。

3.2 典型应用场景

初始化容器的设计初衷是解耦主容器与依赖准备逻辑,常见应用场景包括:

  • 补充工具与代码:主容器镜像为精简体积未包含的工具(如 curlpingjq),可通过初始化容器提供。例如,主容器运行 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;']
操作与验证步骤
  1. 创建 Pod

    [root@k8s-master01 ~]# kubectl create -f pod-initcontainer.yaml
    pod/pod-initcontainer created
    
  2. 查看 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
  1. 动态观察 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 三种钩子动作定义方式

钩子函数支持通过三种方式定义具体执行的动作,适用于不同场景:

  1. Exec 命令:在容器内部执行一条或多条命令,适用于简单的命令行操作。

    lifecycle:
      postStart: 
        exec:
          command:
          - /bin/sh
          - -c
          - echo "Pod started" > /var/log/pod-start.log  # 容器内创建启动日志
    
  2. TCPSocket:尝试与容器内指定的 TCP 端口建立连接,用于验证容器网络可用性。

    lifecycle:
      postStart:
        tcpSocket:
          port: 8080  # 检查容器 8080 端口是否可连接(仅验证端口监听,不发送数据)
    
  3. 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"]
操作与验证步骤
  1. 创建 Pod

    [root@k8s-master01 ~]# kubectl create -f pod-hook-exec.yaml
    pod/pod-hook-exec created
    
  2. 查看 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 正常运行
    
  3. 验证 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 三种探测实现方式

与钩子函数类似,容器探测也支持三种实现方式,适用于不同应用类型:

  1. Exec 命令:在容器内执行命令,通过命令退出码判断状态(0 为成功,非 0 为失败)。
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy  # 检查文件是否存在,存在则探测成功
    
  2. TCPSocket:尝试与容器内指定 TCP 端口建立连接,成功建立则探测成功。
    readinessProbe:
      tcpSocket:
        port: 3306  # 检查 MySQL 3306 端口是否可连接,验证数据库是否就绪
    
  3. 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 秒探测一次
操作与验证
  1. 创建 Pod

    [root@k8s-master01 ~]# kubectl create -f pod-liveness-exec.yaml
    pod/pod-liveness-exec created
    
  2. 查看 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 重启容器。

  3. 修复问题
    进入容器创建文件,验证探测恢复:

    [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
操作与验证
  1. 创建 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  # 因探测失败,容器反复重启
    
  2. 修复问题
    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
操作与验证
  1. 创建 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
    
  2. 修复问题
    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  # 设置为不重启
操作与验证
  1. 创建 Pod
    [root@k8s-master01 ~]# kubectl create -f pod-restartpolicy.yaml
    pod/pod-restartpolicy created
    
  2. 查看 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
    
  3. 验证重启次数
    [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 基于默认算法(如资源均衡、优先级)自动选择节点 低(无需人工配置) 无特殊调度需求的普通应用,如测试环境服务、无状态应用
定向调度 通过 nodeNamenodeSelector 强制指定调度节点 中(仅支持标签或节点名匹配) 1. 固定节点部署(如需访问本地硬件的应用)
2. 简单的节点分组部署(如生产环境节点组、测试环境节点组)
亲和性调度 通过 NodeAffinityPodAffinityPodAntiAffinity 实现灵活的调度规则,支持“硬限制”与“软限制” 高(支持复杂规则、优先级) 1. 应用与节点属性匹配(如 GPU 节点部署 AI 应用)
2. 应用间协同部署(如 Web 应用与缓存应用同节点)
3. 应用副本打散部署(如多副本服务跨节点部署,提高可用性)
污点与容忍调度 节点通过“污点”拒绝 Pod 调度,Pod 通过“容忍”忽略污点,实现节点级别的排斥与例外 高(支持节点级别的安全隔离、资源保护) 1. 保护关键节点(如 master 节点不部署业务 Pod)
2. 特殊节点隔离(如 GPU 节点仅允许 AI 应用调度)
3. 故障节点临时隔离(如磁盘故障节点拒绝新 Pod 调度)

7.2 1. 自动调度(默认)

自动调度是 Kubernetes 的默认行为,无需人工配置,由 Scheduler 组件完成。其核心算法流程如下:

  1. 过滤(Filtering):从所有节点中排除不满足 Pod 需求的节点(如资源不足、端口冲突、污点排斥),得到“可行节点列表”。
  2. 打分(Scoring):对可行节点按优先级评分(如资源利用率低的节点得分高、节点负载均衡),选择得分最高的节点。
  3. 绑定(Binding):将 Pod 与目标节点绑定,更新 API Server 与 etcd 中的 Pod 调度信息。

7.3 2. 定向调度(强制约束)

定向调度通过强制约束将 Pod 调度到指定节点,不满足条件时 Pod 会失败(而非调度到其他节点),适用于需严格控制部署位置的场景。

7.3.1 nodeName:直接指定节点名

nodeName 是最直接的调度方式,跳过 Scheduler 调度逻辑,直接将 Pod 调度到指定名称的节点。

实战案例
  1. 配置文件(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 的节点
    
  2. 操作与验证
    • 创建 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,说明调度强制但失败。
7.3.2 nodeSelector:基于节点标签匹配

nodeSelector 通过节点标签(Label)进行匹配,需先为节点添加标签,再在 Pod 中指定标签选择器,实现“标签相同的节点才调度”。

实战案例
  1. 为节点添加标签
    # 为 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
    
  2. 配置文件(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)
    
  3. 操作与验证
    • 创建 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 状态为 PendingNODE 字段为 <none>,调度失败。

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)
  1. 配置文件(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"]  # 无节点匹配该标签
    
  2. 操作与验证
    • 创建 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 ......
      
实战案例 2:软限制(preferredDuringSchedulingIgnoredDuringExecution)
  1. 配置文件(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"]  # 无节点匹配该标签(软限制)
    
  2. 操作与验证
    • 尽管无节点匹配软限制规则,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
      
NodeAffinity 注意事项
  1. 若同时配置 nodeSelectornodeAffinity,需两者均满足,Pod 才会调度。
  2. nodeSelectorTerms 是列表,满足其中一个即可。
  3. 一个 nodeSelectorTerm 中的多个 matchExpressions 需全部满足,节点才会被选中。
  4. 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(同一机房)。
实战案例
  1. 创建目标 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
    
  2. 创建新 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  # 同一节点
    
  3. 操作与验证

    • 创建新 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,调度失败。
7.4.3 PodAntiAffinity(Pod 反亲和性)

与 PodAffinity 相反,PodAntiAffinity 控制新 Pod 与目标 Pod 调度到不同拓扑域,适用于多副本应用的高可用部署(如避免多个副本调度到同一节点,防止节点故障导致服务中断)。

实战案例
  1. 使用上一节的目标 Pod(运行在 node1,标签 podenv=pro

  2. 创建反亲和性 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  # 不同节点
    
  3. 操作与验证

    • 创建 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 不同节点)
      

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-
实战案例:污点效果演示
  1. 环境准备:停止 node2 节点,仅保留 node1 节点,便于观察效果。

  2. 添加 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   # 调度成功
    
  3. 修改为 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 不受影响
    
  4. 修改为 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 可以调度到有污点的节点。容忍需与污点的 keyvalueeffect 匹配(部分字段可省略,实现模糊匹配)。

核心配置项
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 污点
  1. 环境准备:node1 节点已添加 tag=chenyu:NoExecute 污点,Pod 无法调度。

  2. 创建带容忍的 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 类型的污点
    
  3. 操作与验证

    • 创建 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 污点,正常调度并运行。
7.5.3 常见使用场景
  1. 保护 Master 节点:Kubeadm 搭建的集群默认给 Master 节点添加 node-role.kubernetes.io/master:NoSchedule 污点,禁止业务 Pod 调度到 Master 节点,确保 Master 稳定。
  2. 特殊资源节点隔离:GPU 节点添加 gpu=true:NoSchedule 污点,仅允许带容忍的 AI 应用调度,避免资源浪费。
  3. 节点故障临时隔离:节点磁盘故障时,添加 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 常见使用场景
  1. 保护 Master 节点:Kubeadm 搭建的集群默认给 Master 节点添加 node-role.kubernetes.io/master:NoSchedule 污点,禁止业务 Pod 调度到 Master 节点,确保 Master 稳定。
  2. 特殊资源节点隔离:GPU 节点添加 gpu=true:NoSchedule 污点,仅允许带容忍的 AI 应用调度,避免资源浪费。
  3. 节点故障临时隔离:节点磁盘故障时,添加 node.kubernetes.io/disk-pressure:NoExecute 污点,驱逐现有 Pod 并拒绝新 Pod,防止故障扩散。

八、总结

Pod 作为 Kubernetes 生态的核心,其生命周期管理与调度机制直接决定了应用的可用性、稳定性与资源利用率。本文从 Pod 生命周期的完整流程出发,详细解析了初始化容器、钩子函数、容器探测、重启策略四大核心机制,以及自动调度、定向调度、亲和性调度、污点与容忍四大调度方式,并通过实战案例验证了关键配置的效果。

在实际应用中,需根据业务需求灵活组合这些机制:

  • 对于长期运行的服务,需配置合理的健康探测(Liveness + Readiness)与重启策略(Always),确保故障自动恢复。
  • 对于有依赖的应用,通过初始化容器(Init Container)处理前置依赖,避免主容器启动失败。
  • 对于资源敏感或高可用需求的应用,需利用亲和性调度(PodAntiAffinity)实现副本打散,或通过污点与容忍保护关键节点。

深入理解并熟练运用这些机制,是构建稳定、高效 Kubernetes 集群的基础,也是迈向 Kubernetes 高级应用的关键一步。

更多推荐