Appearance
Pod 生命周期
Pod 是 Kubernetes 最小调度单元——一个或多个容器的组合,共享网络命名空间和存储卷。排查 K8s 服务时,Pod 是从"期望状态"到"实际运行进程"之间的关键环节:是否被调度、是否拉动镜像、是否挂载成功、容器是否启动、探针是否通过、退出后是否重启。
Pod 从创建到运行
每个阶段都可能卡住,对应的排查入口不同:
| 阶段 | 说明 | 这个阶段特有的问题 |
|---|---|---|
| Pending | 尚未完成调度或依赖准备 | 资源不足、污点没容忍、PVC 未绑定 |
| ContainerCreating | 节点正在创建容器和网络 | 拉镜像失败、挂载卷失败、CNI 分配 IP 失败 |
| Running | 至少一个容器在运行 | 容器进程在跑,但不等于业务可用 |
| Succeeded | 所有容器正常结束 | Job 类工作负载的终点 |
| Failed | 容器异常结束且不再重启 | 退出码非 0、OOM、restartPolicy 相关 |
Running 只是容器进程在跑。Service 是否接流量看 readinessProbe。
initContainer
initContainer 在业务容器之前执行,全部成功后才会启动普通容器。
不用 initContainer 时,应用容器要自己处理"等数据库就绪、初始化目录、生成配置"这些前置动作——启动脚本里塞满 sleep 和 until nc -z。initContainer 把这些前置逻辑从应用镜像里分离出来,按顺序执行,且每个 initContainer 可以用不同镜像和工具。
yaml
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
initContainers:
- name: wait-db
image: busybox:1.36
command: ["sh", "-c", "until nc -z mysql 3306; do sleep 2; done"]
containers:
- name: app
image: nginx:1.26initContainer 失败时 Pod 处于 Init:Error 或 Init:CrashLoopBackOff,不会进入 ContainerCreating 阶段。initContainer 按顺序执行——第一个成功后才跑第二个,全部成功后才启动业务容器。只保证顺序,不保证幂等——数据库 schema 迁移这类操作放在 initContainer 里时,需要单独处理重复执行和失败重试。
重启策略
Pod 级别 restartPolicy 决定容器退出后 kubelet 的行为:
| 策略 | 含义 | 哪里用 |
|---|---|---|
Always | 容器退出后总是重启 | Deployment、StatefulSet、DaemonSet 默认 |
OnFailure | 非 0 退出码才重启 | Job 常见 |
Never | 退出后不重启 | 一次性 Job、初始化 Pod |
Deployment 的 Pod 默认 Always——应用进程退出后 kubelet 重新拉起来。但 Always 不等于"无限重试"——连续失败后会进入 CrashLoopBackOff,每次重启间隔逐渐拉长(指数退避,最长 5 分钟)。
三种探针
| 探针 | 解决的问题 | 失败后果 |
|---|---|---|
| startupProbe | 应用启动慢——启动窗口内不触发 liveness 误杀 | 超过 failureThreshold 后 kubelet 重启容器 |
| readinessProbe | 应用在跑但暂时接不了流量——缓存没热、连接没建好 | 从 Service 后端摘除,不影响容器进程 |
| livenessProbe | 应用进程在但实际已经僵死——死锁、死循环、内存泄漏 | 失败后 kubelet 重启容器 |
先从最简 Deployment(无探针)开始:
yaml
containers:
- name: app
image: harbor.example.com/app:v1
ports:
- containerPort: 8080没有探针时,容器进程一启动就被认为 Ready,流量立刻打进来。启动慢的服务——Java、需要预热缓存、连接池初始化——会在还没准备好时收到请求,返回 5xx。
加上 startupProbe 保护启动窗口:
yaml
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 最多等 30 × periodSeconds = 60 秒
periodSeconds: 2startupProbe 存在期间,livenessProbe 和 readinessProbe 不生效。startupProbe 成功后,另外两个探针接管。启动慢但 startupProbe 没配或 failureThreshold 太小——应用还在初始化,liveness 已经判定失败——容易出现"容器反复重启"。
加上 readinessProbe 控制流量接入:
yaml
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3 # 连续失败 3 × 5 = 15 秒后摘除readinessProbe 失败后 Pod 从 Service EndpointSlice 里移除,不再接收流量——容器进程不受影响,不会被杀掉。恢复后探针通过,Pod 重新加入 Service 后端。滚动更新时,新 Pod 的 readinessProbe 通过之前旧 Pod 不会删除,更新节奏由探针驱动。
加上 livenessProbe 处理僵死:
yaml
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3livenessProbe 是修复手段——容器僵死后 kubelet 重启它。不要因为外部依赖(数据库、Redis、第三方 API)不可用就判定 liveness 失败——重启应用容器并不能修复外部依赖,反而可能把重启雪崩扩散出去。
容器状态和退出信息
bash
kubectl get pod <pod-name> -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
kubectl get pod <pod-name> -n <namespace> \
-o jsonpath='{.status.containerStatuses[*].state}{"\n"}'| 状态 | 说明 |
|---|---|
| Waiting | 等待中——拉镜像、等待创建、restartPolicy 退避 |
| Running | 容器进程运行中 |
| Terminated | 容器已退出——有 exitCode、reason、startedAt、finishedAt |
上一次退出原因:
bash
kubectl get pod <pod-name> -n <namespace> \
-o jsonpath='{.status.containerStatuses[0].lastState.terminated}{"\n"}'日志和事件
bash
kubectl logs <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --previous # 上一次崩溃前的日志
kubectl logs <pod-name> -n <namespace> -c <container-name> # 多容器 Pod 指定容器
kubectl get events -n <namespace> --sort-by=.lastTimestampEvents 偏平台动作(调度、拉镜像、挂载卷),日志偏应用进程。这两层的关系:Events 说 Pod 在哪个阶段卡住了(仍在 ContainerCreating → 还没到应用层面);日志说在启动后发生了什么(应用报错连不上数据库 → 平台层没问题,是应用配置)。平台还没把容器启动起来时,应用日志是空的——先看 Events。
常见状态处理
| 状态 | 常见原因 | 先看什么 |
|---|---|---|
Pending | 调度失败、PVC 未绑定 | describe pod → Events,节点资源,PVC 状态 |
ImagePullBackOff | 镜像名写错、仓库认证、网络 | Events、节点 crictl pull |
ContainerCreating | CNI 分配 IP、挂载卷、sandbox 创建 | Events、kubelet 日志 |
CrashLoopBackOff | 程序启动后退出 | logs --previous、exitCode、启动命令 |
OOMKilled | 内存超过 limit | 容器 lastState、limit 配置、应用内存曲线 |