ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Kubernetes从入门到精通】第33篇:Pod生命周期全解——从Pending到Terminating的每一步

【Kubernetes从入门到精通】第33篇:Pod生命周期全解——从Pending到Terminating的每一步 上一篇【第32篇】ResourceQuota——多团队共享集群的“公平秤“下一篇【第34篇】Pod健康检查——Liveness、Readiness和Startup探针摘要你创建了一个Pod它从Pending变成Running最后变成Terminating——这条时间线看似简单其实里面塞满了各种钩子、状态切换和容器交互。Pod不是一出生就能干活——在它的主容器启动之前Init容器会按顺序一个个执行比如等着数据库Ready、从Git拉配置文件主容器启动后会触发PostStart钩子往注册中心报到、预热缓存被删除时先执行PreStop钩子从注册中心注销、把最后一批日志写完再优雅退出。本文把Pod的一生从出生到死亡完整拆开Phase状态机Pending→Running→Succeeded/Failed→Unknown、Container状态Waiting/Running/Terminated、Init容器的执行顺序和实战妙用、PostStart/PreStop生命周期钩子的各种骚操作——看完你会发现Pod远比你想象的要有故事。一、Pod Phase——“身份证上的状态”1.1 Phase状态机全景图【Pod Phase 状态机——从出生到坟墓】 ┌─────────────────────────────────────────────────────────────────┐ │ │ │ kubectl apply → │ │ │ │ │ ▼ │ │ ┌─────────┐ 调度成功 ┌─────────┐ 全部完成 ┌─────────┐ │ │ Pending │──────────────►│ Running │──────────────►│Succeeded│ │ └────┬────┘ └────┬────┘ └─────────┘ │ │ │ │ │ 镜像拉取失败等 │ 容器异常退出 │ │ │ │ ▼ ▼ │ ┌─────────┐ ┌─────────┐ │ │ Failed │ │ Failed │ │ └─────────┘ └─────────┘ │ │ 特殊情况 │ ┌──────────┐ kubelet失联 ┌─────────┐ │ │ Running │───────────────►│ Unknown │ │ │或其他状态 │ └─────────┘ │ └──────────┘ └─────────────────────────────────────────────────────────────────┘Phase含义什么时候进入什么时候退出PendingPod已被API Server接受但还没在Node上完全运行创建请求被接受后所有容器Ready或失败RunningPod已绑定到Node所有容器已创建至少一个还在运行至少一个容器Running所有容器终止Succeeded所有容器正常终止退出码0不会重启所有容器exit 0且restartPolicyNever/OnFailure不会退出终态Failed所有容器已终止至少一个以非0退出码退出容器fail且restartPolicyNever不会退出终态或OnFailure重启Unknown无法获取Pod状态——Node失联kubelet心跳超时恢复通信或Pod被删除1.2 Phase不等于容器状态——“一个Pod可以有多个容器”# Phase是Pod级别容器状态是Container级别kubectl get pod my-pod# NAME READY STATUS RESTARTS AGE# my-pod 2/3 Running 0 5m# ↑ ↑# READY: 3个容器2个就绪 STATUS: Pod Phase# 看容器详细状态kubectl describe pod my-pod# Containers:# app: ← 主容器# State: Running# Started: Mon, 28 Jul 2026 10:00:00 0800# sidecar: ← Sidecar容器# State: Running# Started: Mon, 28 Jul 2026 10:00:01 0800# init-db: ← Init容器已退出# State: Terminated# Reason: Completed# Exit Code: 0# Pod Phase Running至少一个容器在跑# 但具体哪个容器什么状态得看Container States【Pod Phase 判定逻辑】 Pod Phase 由 kubelet 根据所有容器的状态汇总决定 ┌─────────────────────────────────────────────────────────┐ │ │ │ 有容器在 Waiting → Pending │ │ 至少一个容器 Running → Running │ │ 所有容器 Terminated 且 exit 0 → Succeeded │ │ 有容器 Terminated 且 exit ≠ 0 → Failed │ │ kubelet 失联 → Unknown │ │ │ │ 注意Phase 是快照值不是历史记录 │ │ 一个Pod从Pending→Running后phase不会是Pending过 │ └─────────────────────────────────────────────────────────┘要点很多新手看到STATUS: Running就以为Pod健康并正常服务中这其实不准确。Pod Running只意味着至少一个容器在运行——但不代表这个容器Ready就绪探针可能还没通过更不代表它能正常处理请求。判断Pod是否真的可用要看READY列1/1才是完全就绪。二、容器状态——“每个容器自己的一生”2.1 三种Container State【Container States——比Pod Phase更细的粒度】 Waiting等待 ┌─────────────────────────────────────────────────────────┐ │ 容器还没开始跑正在干这些事 │ │ • 拉镜像ImagePullBackOff │ │ • 等Init容器完成 │ │ • 挂载Volume │ │ • CrashLoopBackOff频繁崩溃后的冷却期 │ └─────────────────────────────────────────────────────────┘ Running运行中 ┌─────────────────────────────────────────────────────────┐ │ 容器正在运行没有挂 │ │ 注意Running ≠ Ready │ │ Readiness Probe 可能还在检查中 │ └─────────────────────────────────────────────────────────┘ Terminated已终止 ┌─────────────────────────────────────────────────────────┐ │ 容器跑完了或挂了包含额外信息 │ │ • Exit Code: 退出码0正常137SIGKILL143SIGTERM │ │ • Reason: Completed / Error / OOMKilled │ │ • Started: 开始时间 │ │ • Finished: 结束时间 │ └─────────────────────────────────────────────────────────┘# 查看容器的完整状态历史kubectl describe pod my-pod# 会看到类似# Containers:# app:# Container ID: containerd://abc123...# Image: nginx:1.25# State: Running# Started: Mon, 28 Jul 2026 10:00:00 0800# Last State: Terminated# Reason: Error# Exit Code: 1# Started: Mon, 28 Jul 2026 09:55:00 0800# Finished: Mon, 28 Jul 2026 09:55:30 0800# Ready: True ← Readiness Probe通过了# Restart Count: 3 ← 重启了3次2.2 常见的Waiting原因排查# Waiting Reason 速查表# ImagePullBackOff / ErrImagePull# 镜像拉不下来——镜像名写错了或Registry不通kubectl describe pod my-pod|grep-A5State:# State: Waiting# Reason: ImagePullBackOff# Message: Back-off pulling image nginx:999# → 检查镜像名、检查imagePullSecrets# CrashLoopBackOff# 容器启动后立刻挂了反复重启kubectl describe pod my-pod|grep-A10Last State# Last State: Terminated# Reason: Error# Exit Code: 1# → 看日志kubectl logs my-pod --previous# --previous 看上一个被杀掉的容器的日志# CreateContainerConfigError# 容器配置有问题——ConfigMap/Secret名字写错了kubectl describe pod my-pod# Events:# Warning Failed Error: configmap bad-config not found# CreateContainerError# 容器运行时错误——启动命令有问题、权限不够等kubectl describe pod my-pod|grep-A10Events三、Init容器——“主容器起床前先干的活”3.1 Init容器和普通容器的区别【Init容器——在主容器上台前的前置任务】 执行顺序 ┌─────────────────────────────────────────────────────────┐ │ │ │ Pod 创建 │ │ │ │ │ ▼ │ │ ┌──────────┐ │ │ │ Init:db │ ① 第一个Init容器——等待数据库就绪 │ │ │ │ 退出前检查mysql --hostdb -e SELECT 1 │ │ └────┬─────┘ │ │ │ 成功 │ │ ▼ │ │ ┌──────────┐ │ │ │ Init:cfg │ ② 第二个Init容器——拉配置文件 │ │ │ │ git clone ... /etc/config │ │ └────┬─────┘ │ │ │ 成功 │ │ ▼ │ │ ┌──────────┐ ┌──────────┐ │ │ │ App │ │ Sidecar │ ③ 主容器和Sidecar同时启动 │ │ │(主容器) │ │(sidecar) │ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────┘ 关键特性 • Init容器按顺序执行前一个成功才执行下一个 • 任何一个Init容器失败kubelet会不断重启它受restartPolicy影响 • Init容器全部成功后主容器才开始启动 • Init容器可以有独立的resources、环境变量、镜像——完全独立3.2 Init容器实战——“等数据库就绪再启动”apiVersion:v1kind:Podmetadata:name:app-with-initspec:# # Init容器1等待数据库就绪# initContainers:-name:wait-for-dbimage:busybox:1.36command:-sh--c-|echo 等待数据库就绪... until nslookup mysql-service; do echo 数据库还没起来等5秒... sleep 5 done echo 数据库DNS解析成功 until nc -z mysql-service 3306; do echo 数据库端口还没监听等5秒... sleep 5 done echo 数据库连接成功Init容器完成# # Init容器2初始化数据库表结构# -name:db-migrationimage:myapp-migration:v1.0command:[python,migrate.py]env:-name:DB_HOSTvalue:mysql-service-name:DB_NAMEvalue:myapp# # Init容器3预热缓存# -name:cache-warmupimage:redis:7-alpinecommand:-sh--c-|redis-cli -h redis-service PING echo 缓存服务连接成功# # 主容器# containers:-name:appimage:myapp:v2.0ports:-containerPort:8080env:-name:DB_HOSTvalue:mysql-service-name:REDIS_HOSTvalue:redis-service# 观察Init容器的执行过程kubectl get pod app-with-init-w# NAME READY STATUS RESTARTS AGE# app-with-init 0/1 Pending 0 0s# app-with-init 0/1 Init:0/3 0 2s ← 第1个Init# app-with-init 0/1 Init:1/3 0 15s ← 第2个Init# app-with-init 0/1 Init:2/3 0 20s ← 第3个Init# app-with-init 0/1 PodInitializing 0 25s ← 主容器启动中# app-with-init 1/1 Running 0 30s ← OK# 查看Init容器日志kubectl logs app-with-init-cwait-for-db# -c 指定容器名# 如果Init失败了用 --previous 看上一次的kubectl logs app-with-init-cwait-for-db--previous要点Init容器最常见的使用场景是启动依赖管理——你的应用依赖数据库但数据库Pod可能还没Ready。如果在主容器里等数据库应用进程可能还没初始化就在连接失败了放在Init容器里数据库不Ready就卡在这里不会浪费主容器的restartCount。另一个用途是做一些只需执行一次的前置操作——比如数据库迁移、证书生成、文件权限修改。3.3 Init容器和Sidecar容器的区别特性Init容器Sidecar容器执行时机主容器启动之前和主容器同时运行生命周期执行完就退出和Pod同生共死执行顺序严格串行一个接一个并行重启行为失败后根据restartPolicy重启失败后根据restartPolicy重启典型用途数据库迁移、等依赖、拉配置日志收集、代理转发、监控资源占用只在启动时占用全程占用四、生命周期钩子——PostStart和PreStop4.1 容器生命周期中的插口【容器生命周期钩子——关键时刻可以挂点逻辑】 容器启动过程 容器停止过程 ┌────────────────────┐ ┌────────────────────┐ │ 1. 拉镜像 │ │ 1. Pod被标记删除 │ │ 2. 创建容器 │ │ 2. PreStop Hook │ ← 可以在这做事 │ 3. 容器启动 │ │ 3. SIGTERM 信号 │ │ 4. PostStart Hook │ ← 可以在这做事 │ 4. 等待... │ │ 5. 容器正常运行 │ │ 5. SIGKILL 信号 │ │ 6. Readiness Probe │ │ 6. 容器被清理 │ └────────────────────┘ └────────────────────┘ PostStart 和 PreStop 可以通过两种方式执行 • exec在容器内执行命令 • httpGet向容器发送HTTP GET请求4.2 PostStart Hook——“容器起来后先办手续”apiVersion:v1kind:Podmetadata:name:app-with-poststartspec:containers:-name:appimage:myapp:v2.0lifecycle:postStart:exec:command:-/bin/sh--c-|# 1. 向服务注册中心注册自己 curl -X POST http://registry-service:8500/register \ -d {name:myapp,port:8080,host:$HOSTNAME}# 2. 预热本地缓存echo Warming up cache... curl-s http://localhost:8080/cache/warmup# 3. 写启动日志echo $(date):Container started,registered to registry/var/log/startup.log# 用httpGet方式——更轻量apiVersion:v1kind:Podmetadata:name:app-with-http-poststartspec:containers:-name:appimage:myapp:v2.0lifecycle:postStart:httpGet:path:/healthz/ready# 调用这个端点port:8080scheme:HTTP# 这个HTTP请求在容器启动后立即发送# 服务可以在 /healthz/ready 里执行初始化逻辑【PostStart Hook 执行时机——注意这个坑】 时间线 ┌─────────────────────────────────────────────────────────┐ │ │ │ 容器 ENTRYPOINT 开始执行 │ │ │ │ │ │ PostStart Hook 和 ENTRYPOINT 是并行执行的 │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ ENTRYPOINT 执行 │ │ PostStart 执行 │ │ │ │ │ 启动Nginx │ │ 注册到注册中心 │ │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ └────────────────────┘ │ │ │ 没有顺序保证 │ │ │ │ │ │ 可能的坑PostStart 调用 localhost:8080/register │ │ │ 但 Nginx 还没完全启动 → 请求失败 │ │ └─────────────────────────────────────────────────────────┘ 解决方案PostStart里加重试逻辑 command: - /bin/sh - -c - | for i in $(seq 1 30); do curl -s http://localhost:8080/register break echo Waiting for app... attempt $i sleep 2 done要点PostStart的坑在于它和ENTRYPOINT是并发执行的——不是ENTRYPOINT跑完才跑PostStart。如果你的PostStart依赖容器内的服务已经启动比如调localhost的API一定要在PostStart里加重试逻辑。另一个坑PostStart执行失败会导致容器被kill并重启所以PostStart里的逻辑要保证幂等。4.3 PreStop Hook——“优雅地告别”apiVersion:v1kind:Podmetadata:name:app-with-prestopspec:terminationGracePeriodSeconds:60# ← 给60秒做告别containers:-name:appimage:myapp:v2.0lifecycle:preStop:exec:command:-/bin/sh--c-|echo $(date): PreStop hook started# 1. 从注册中心注销——停止接收新流量curl-X DELETE http://registry-service:8500/deregister \-d {name:myapp,host:$HOSTNAME} echo Deregistered from service registry# 2. 等几秒让注册中心同步sleep 5# 3. 处理完队列里的遗留请求echo Draining remaining requests... curl-s http://localhost:8080/drain||true# 4. 把日志/状态持久化echo $(date):Saving state before shutdown/data/shutdown.logecho $(date):PreStop hook completed# 用httpGet——更简洁apiVersion:v1kind:Podmetadata:name:app-with-http-prestopspec:containers:-name:appimage:myapp:v2.0lifecycle:preStop:httpGet:path:/shutdown# 调这个端点port:8080# 应用在这个端点里做清理scheme:HTTP# 应用在 /shutdown 里# 1. 停止接收新连接# 2. 处理完现有请求# 3. 关闭数据库连接# 4. 返回200【PreStop Hook 的执行顺序——关键时序】 Pod删除触发后 ┌─────────────────────────────────────────────────────────┐ │ 时间线 │ │ │ │ T0s Pod被标记删除从Endpoint列表移除 │ │ T0s PreStop Hook 开始执行 │ │ │ │ │ │ PreStop执行中... │ │ │ • 从注册中心注销 │ │ │ • 等待已有请求处理完 │ │ │ • 持久化状态 │ │ │ │ │ TN秒 PreStop执行完毕N ≤ terminationGracePeriodSeconds │ │ TN秒 发送 SIGTERM 给容器主进程 │ │ │ │ │ │ 容器收到SIGTERM后 │ │ │ • 停止接受新请求 │ │ │ • 处理完当前请求 │ │ │ • 关闭连接 │ │ │ • 退出 │ │ │ │ │ T60s terminationGracePeriodSeconds到期 │ │ 如果容器还没退出 → 强制 SIGKILL │ └─────────────────────────────────────────────────────────┘# 实战观察——看Pod删除的完整流程# 终端1watch Pod状态kubectl get pod app-with-prestop-w# 终端2删除Pod并计时timekubectl delete pod app-with-prestop# 如果Pod正常退出大约需要 PreStop时间 优雅关闭时间# 如果Pod卡住超过 terminationGracePeriodSecondskubectl describe pod app-with-prestop# 会看到 TerminationGracePeriodSeconds exceeded → SIGKILL要点PreStop Hook的最大作用是优雅退场——从注册中心注销、等待连接排空、保存状态。最关键的一点terminationGracePeriodSeconds要大于PreStop执行时间应用优雅退出时间。比如PreStop要10秒完成应用需要30秒处理完现有请求那terminationGracePeriodSeconds至少设45-60秒。设太短PreStop没跑完就被SIGKILL了设太长会影响滚动更新的速度。五、完整生命周期示例——“一个Pod的完整剧本”5.1 从头到尾的完整YAMLapiVersion:v1kind:Podmetadata:name:full-lifecycle-demolabels:app:lifecycle-demospec:terminationGracePeriodSeconds:45# 告别时间45秒restartPolicy:Always# 挂了就重启# # Init容器——前置准备# initContainers:-name:init-setupimage:busybox:1.36command:-sh--c-|echo Init容器检查依赖 # 检查DNS echo 检查DNS解析... nslookup kubernetes.default.svc.cluster.local # 创建必要目录 mkdir -p /data/config mkdir -p /data/logs echo Init完成volumeMounts:-name:shared-datamountPath:/data# # 主容器# containers:-name:appimage:nginx:1.25ports:-containerPort:80name:http# 生命周期钩子lifecycle:postStart:exec:command:-/bin/sh--c-|echo $(date): [PostStart] 容器启动了开始初始化... # 模拟向注册中心注册 echo $(date): [PostStart] Registered to service registry # 写入一个ready标记文件 echo ready /data/status/ready echo $(date): [PostStart] 初始化完成preStop:exec:command:-/bin/sh--c-|echo $(date): [PreStop] 收到停止信号开始退出流程...# 1. 标记为不可用echo draining/data/status/ready# 2. 模拟从注册中心注销echo $(date):[PreStop]Deregistered from service registry# 3. 等待活跃连接排空echo $(date):[PreStop]Waiting for active connections to drain...# nginx 的 worker_shutdown_timeout 会处理# 4. 保存日志echo $(date):[PreStop]Shutdown complete,saving state/data/logs/shutdown.log# 资源resources:requests:cpu:100mmemory:128Milimits:cpu:500mmemory:256Mi# 卷挂载volumeMounts:-name:shared-datamountPath:/data# # Volumes# volumes:-name:shared-dataemptyDir:{}# 完整体验Pod的一生# 1. 创建kubectl apply-ffull-lifecycle-demo.yaml kubectl get pod full-lifecycle-demo-w# 观察状态变化Pending → Init:0/1 → PodInitializing → Running# 2. 看PostStart日志kubectl logs full-lifecycle-demo-capp|grepPostStart# Mon Jul 28 10:00:00 UTC 2026: [PostStart] 容器启动了...# 3. 看Init日志kubectl logs full-lifecycle-demo-cinit-setup# 4. 删除Pod观察PreStopkubectl delete pod full-lifecycle-demokubectl logs full-lifecycle-demo-capp-f|grepPreStop# Mon Jul 28 10:05:00 UTC 2026: [PreStop] 收到停止信号...# 5. 看删除耗时kubectl get pod full-lifecycle-demo-w# 状态变化Running → Terminating → (消失)本篇小结Pod的生命周期比你想象的要丰富——从Pending到Terminating之间有大量可以控制的地方Pod Phase是身份证Pending→Running→Succeeded/Failed但Phase只代表整体状态具体容器各自有各自的StateInit容器是助手按序执行启动前完成准备工作——等数据库、跑迁移、拉配置所有Init成功主容器才启PostStart Hook是上岗手续容器启动后执行但和ENTRYPOINT并发——需要加重试逻辑PreStop Hook是离职交接删除前执行——从注册中心注销、排空连接、保存状态赶在SIGTERM之前完成terminationGracePeriodSeconds是告别的时间比PreStop应用退出时间之和要长否则就被SIGKILL粗暴打断了下一篇咱们继续深挖Pod的生命——健康检查。Liveness探针告诉你容器还活着吗Readiness探针告诉你能接客了吗Startup探针告诉慢启动的应用别催我在起来了。上一篇【第32篇】ResourceQuota——多团队共享集群的“公平秤“下一篇【第34篇】Pod健康检查——Liveness、Readiness和Startup探针
返回列表