第 22 篇 k8s 之 Pod: 生命周期与重启策略

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。


在第 21 篇中,我们编写了 Pod YAML,部署了包含 Flask 和 Redis 的双容器 Pod,体验了共享网络和 localhost 互访。但关于 Pod,还有几个关键问题没有回答:Pod 从创建到销毁经历了哪些阶段?Pending 和 Running 之间发生了什么?容器退出了,Pod 为什么还在?RESTARTS 列的数字是谁在控制?

回想第 6 篇,我们学容器生命周期时,Docker 容器的状态流转相对简单------created → running → paused → exited → deleted。而 K8s 的 Pod 生命周期在此基础上增加了更多阶段,并引入了重启策略 来定义"容器退出后该怎么办"。今天这篇就是要把这些规则彻底搞清楚,同时引出Init Container这个启动前置机制,为第 24 篇的探针健康检查做好铺垫。

一、Pod 生命周期的完整阶段

1.1 生命周期全景图

一个 Pod 从创建到销毁,会经历以下阶段:

bash 复制代码
                          ┌─────────────┐
                          │   Pending   │ ← 已提交,等待调度 + 拉取镜像
                          └──────┬──────┘
                                 │ 调度成功 + 镜像拉取完成
                          ┌──────▼──────┐
                          │   Running   │ ← 至少一个容器在运行
                          └──────┬──────┘
                                 │
              ┌──────────────────┼──────────────────┐
              │                  │                  │
     ┌───────▼──────┐   ┌───────▼──────┐   ┌───────▼──────┐
     │  Succeeded   │   │   Failed     │   │   Unknown    │
     │ 正常退出码0  │   │ 非零退出码   │   │ 节点失联等   │
     └──────────────┘   └──────────────┘   └──────────────┘
  • Pending:Pod 已被 API Server 接受并写入 etcd,但尚未在任何节点上运行。这个阶段包含 Scheduler 选择节点、kubelet 拉取镜像的过程。如果 Pod 长时间卡在 Pending,通常是因为资源不足或镜像拉取失败。

  • Running:Pod 已被调度到节点,所有容器创建完成,至少有一个容器仍在运行(未退出)。

  • Succeeded:Pod 内所有容器都已正常终止(退出码为 0),且不会再重启。典型场景是一次性 Job。

  • Failed:Pod 内至少有一个容器以非零退出码终止,且不会再重启。

  • Unknown:Pod 状态无法获取,通常是因为 Pod 所在节点失联(网络分区、节点宕机)。

1.2 动手观察:Pod 的状态变化

部署一个临时 Pod,观察它的状态变化过程:

bash 复制代码
kubectl run lifecycle-demo --image=nginx:alpine --restart=Never

-w 持续观察:

bash 复制代码
kubectl get pod lifecycle-demo -w

输出:

bash 复制代码
NAME              READY   STATUS              RESTARTS   AGE
lifecycle-demo    0/1     Pending             0          0s
lifecycle-demo    0/1     ContainerCreating   0          1s
lifecycle-demo    1/1     Running             0          5s

ContainerCreating 不是官方 Pod 阶段之一,而是 kubectl 在 Pending 阶段中展示的过渡状态------kubelet 正在拉取镜像并创建容器。如果镜像较大或网络较慢,这个过渡状态会持续更长时间。

1.3 Pod 状态 vs 容器状态

Pod 状态和容器状态是两个不同的概念,这一点经常被混淆:

  • Pod Status:由 kubelet 汇总所有容器状态后上报(Pending / Running / Succeeded / Failed / Unknown)

  • Container State:单个容器的具体状态(Waiting / Running / Terminated),受重启策略影响

可以通过 kubectl describe pod 查看每个容器的详细状态信息:

bash 复制代码
kubectl describe pod lifecycle-demo | grep -A10 "Containers:"

1.4 容器终止流程与 TerminationGracePeriodSeconds

当 Pod 被删除时,K8s 会按照以下流程优雅终止容器:

  1. Pod 进入 Terminating 状态

  2. kubelet 向每个容器的主进程发送 SIGTERM 信号

  3. 等待 terminationGracePeriodSeconds 秒(默认 30 秒)

  4. 如果容器仍未退出,kubelet 发送 SIGKILL 强制终止

  5. 从 API Server 中删除 Pod 对象

你可以在 Pod spec 中自定义这个宽限期:

bash 复制代码
spec:
  terminationGracePeriodSeconds: 60

这与 Docker 中 docker stop -t 30 的机制完全一致------先 SIGTERM 优雅退出,超时后 SIGKILL 强制终止。

二、重启策略:容器退出后怎么办?

K8s 提供了三种重启策略,用 restartPolicy 字段指定。这三种策略和 Docker 的 --restart 参数有对应关系,但适用层级不同------Docker 的 restart 作用于单个容器,而 K8s 的 restartPolicy 作用于 Pod 内的所有容器

2.1 Always(默认值)

行为:容器退出(无论退出码是否为 0),kubelet 都会重启它。重启间隔由指数退避算法控制:首次重启立即执行,之后间隔 10 秒、20 秒、40 秒......,最长不超过 5 分钟,重置间隔需要容器正常运行至少 10 分钟。

适用场景 :长期运行的服务(Web 服务器、数据库、缓存),这是 Deployment、StatefulSet 等控制器创建 Pod 时强制使用的策略。

2.2 OnFailure

行为 :只有当容器以非零退出码退出时,才执行重启。容器正常退出(退出码 0)不会重启。

适用场景:Job 或 CronJob,批处理任务失败时需要重试,成功则停止。

2.3 Never

行为:容器退出后永不重启。

适用场景:一次性初始化任务(如数据库迁移),或用于调试的临时 Pod。

2.4 策略对比

2.5 动手验证:对比三种策略

Always(默认):

bash 复制代码
# 创建一个 Pod,容器执行后立即退出(退出码 0)
kubectl run always-demo --image=alpine --restart=Always --command -- sh -c "echo 'done' && exit 0"

# 观察 RESTARTS 列
kubectl get pod always-demo -w
# NAME          READY   STATUS             RESTARTS   AGE
# always-demo   0/1     CrashLoopBackOff   3          90s

RESTARTS 数值不断增加------即使容器正常退出,Always 策略也会重启它。CrashLoopBackOff 是 kubelet 的一种保护机制:当容器在短时间内被反复重启,kubelet 会逐渐延长重启间隔,避免陷入无限重启循环消耗节点资源。

OnFailure:

bash 复制代码
# 模拟失败:退出码 1
kubectl run onfailure-demo --image=alpine --restart=OnFailure --command -- sh -c "exit 1"

kubectl get pod onfailure-demo -w
# NAME             READY   STATUS             RESTARTS   AGE
# onfailure-demo   0/1     CrashLoopBackOff   3          90s

容器退出码非零,OnFailure 策略触发重启。

Never:

bash 复制代码
# 正常退出
kubectl run never-demo --image=alpine --restart=Never --command -- sh -c "echo 'done'"

kubectl get pod never-demo
# NAME         READY   STATUS      RESTARTS   AGE
# never-demo   0/1     Completed   0          5s

STATUS=CompletedRESTARTS=0------容器退出后不再重启,Pod 进入 Succeeded 阶段。Completed 是 kubectl 对 Succeeded 阶段的展示名称。

2.6 清理实验 Pod

bash 复制代码
kubectl delete pod lifecycle-demo always-demo onfailure-demo never-demo

三、Init Container:启动前置任务

3.1 什么是 Init Container?

在某些场景下,应用容器启动前需要先完成一些准备工作------等数据库就绪、下载配置文件、检查外部依赖。如果把重试逻辑写进应用代码会使应用变得复杂,K8s 提供的解决方案是 Init Container

Init Container 是 Pod 中一种特殊的容器,它在应用容器启动之前 执行,并且必须成功完成(退出码 0)后,kubelet 才会启动应用容器。如果 Init Container 失败,kubelet 会根据 Pod 的重启策略决定是否重试。

Init Container 与普通容器的关键区别:

3.2 实战:等待 Redis 就绪的 Init Container

以下 YAML 定义了一个 Init Container,在应用启动前轮询 Redis 是否可用:

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: flask-with-init
spec:
  restartPolicy: Always
  initContainers:
    - name: wait-for-redis
      image: redis:alpine
      command:
        - sh
        - -c
        - |
          echo "等待 Redis 就绪..."
          until redis-cli -h redis-service -p 6379 ping; do
            echo "Redis 尚未就绪,2 秒后重试..."
            sleep 2
          done
          echo "Redis 已就绪!"
  containers:
    - name: flask-app
      image: flask-redis-counter:2.0
      ports:
        - containerPort: 5000
      env:
        - name: REDIS_HOST
          value: redis-service
    - name: redis
      image: redis:alpine
      ports:
        - containerPort: 6379

initContainers 字段与 containers 同级。这里定义的 wait-for-redis Init Container 会循环执行 redis-cli ping 直到成功,退出码 0 后 kubelet 才启动 Flask 容器。实际的 Redis 依赖应在 K8s 中用 Service 暴露------此时我们用 redis-service 指代这个 Service 名称。

3.3 执行流程追踪

部署这个 Pod 后查看事件:

bash 复制代码
kubectl describe pod flask-with-init | grep -A5 "Events:"

输出会清晰展示 Init Container 的启动和执行过程。Init Container 常用于等待数据库、下载配置模板、设置文件权限等场景。如果应用本身已包含重试逻辑(比如我们 Flask 应用中连接 Redis 的 get_hit_count 函数),Init Container 并非必须,但在需要确保某个外部依赖完全就绪再启动的场景中,它是最简洁的 K8s 原生方案。

四、对比 Docker Compose 的启动控制

学到这里,做一个贯穿系列的知识串联。

第 15 篇我们学过 Compose 用 depends_on + condition: service_healthy 控制启动顺序。K8s 中实现相同效果的手段有三层:

  1. Init Container :确保某个外部依赖(如数据库)可连接,再启动应用容器。对应 Compose 中 depends_on 的条件等待。

  2. Readiness Probe :告知 Service 该 Pod 是否可接收流量。对应 Compose 中 healthcheck 的"可用性"判定(第 24 篇详解)。

  3. Liveness Probe :检测容器是否处于死锁/假死状态,触发重启。对应 Compose 的 healthcheck 配合 restart 策略(第 24 篇详解)。

五、命令速查表

六、本篇总结

  • Pod 生命周期五阶段:Pending → Running → Succeeded / Failed / Unknown,每个阶段有明确含义。

  • 三种重启策略Always(默认,服务类 Pod 必须使用)、OnFailure(批处理重试)、Never(一次性任务),控制容器退出后的行为。

  • TerminationGracePeriodSeconds:控制优雅终止的超时窗口(默认 30 秒),SIGTERM → 等待 → SIGKILL。

  • Init Container :在应用容器前执行的初始化任务,按顺序串行执行,全部成功后才启动普通容器。对应 Compose 的 depends_on 逻辑。

  • CrashLoopBackOff:kubelet 的保护机制,容器反复重启触发指数退避。

这一篇让我们真正理解了 Pod 从创建到终止的完整过程,以及如何通过重启策略和 Init Container 控制容器行为。下一篇文章------第 23 篇:多容器 Pod 与设计模式(Sidecar 等),我们将深入 Pod 的常见设计模式,看看如何在实际项目中优雅地组织多容器 Pod。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
Shan12051 小时前
浅谈:无服务器WebSocket解决方案
云原生·flask·serverless
java_logo2 小时前
Docker 部署 GitLab CE 完整版教程
docker·容器·gitlab·gitlab docker部署·gitlab部署文档·gitlab部署·gitlab部署教程
maomao大哥闯天下2 小时前
高可用集群软件Keepalived
云原生
llf_cloud2 小时前
docker compose滚动部署实践
运维·docker·容器
IT策士2 小时前
第19篇 Kubernetes 架构解读:控制平面与工作节点
平面·架构·kubernetes
ai产品老杨2 小时前
Docker分布式部署与GB28181/RTSP全协议汇聚:基于源码交付的异构边缘计算AI视频管理平台架构解析
docker·容器·架构
张忠琳3 小时前
【kubernetes v1.21】(五)Kubelet 组件超深度分析
云原生·架构·kubernetes·kubelet
xier_ran3 小时前
【infra之路】模块三:Kubernetes (上) — 概念、集群搭建、Pod 与 Deployment
云原生·容器·kubernetes
IT策士3 小时前
第 23篇 k8s之Pod:多容器 Pod 与设计模式(Sidecar 等)
设计模式·容器·kubernetes