排障三件套:事件、状态、日志
Kubernetes 排障和普通服务器排障最大的不同,是一切都有状态、有事件、有日志。Pod 卡住、容器重启、节点失联,这些都不是"悄悄发生"的,集群早就把线索写在了某处。所以排障第一步不是瞎猜,而是先按顺序把「事件—状态—日志」三件套拿齐。
先看 Pod 的状态(STATUS):Pending、ContainerCreating、Running、CrashLoopBackOff、ImagePullBackOff……不同状态指向不同问题域。再看事件(Events):describe 输出的 Events 段是排障第一现场,调度失败、拉取失败、探针失败都会在这里写明原因。最后看日志(Logs):容器到底为什么退出,业务日志和 stderr 里最清楚。
# 三件套命令速查
kubectl get pods -A # 看所有 Pod 的状态
kubectl describe pod <pod> -n <ns> # 看事件、容器状态、条件
kubectl logs <pod> -n <ns> # 看容器标准输出日志
kubectl logs <pod> -n <ns> --previous # 看上一次退出容器的日志
- 事件describe 的 Events 段是排障第一现场。
- 状态Pending、CrashLoop 等状态指向不同问题域。
- 日志业务日志和退出码说明根因。
- 资源节点与集群层资源决定调度与运行。
Pod 卡在 Pending:调度问题怎么定位
Pod 一直停在 Pending,意思是调度器还没找到合适的节点,或者节点上 Pod 起不来。最常见的原因有三个:资源不够(requests 超了节点剩余可分配量)、节点有污点而 Pod 没有容忍、PVC 没绑定成功(Pod 声明了持久卷但卷一直 Pending)。
不要猜,直接看 describe 的 Events:调度器会把原因写得明明白白,比如 0/3 nodes are available: insufficient cpu 或者 node(s) had untolerated taint。再配合查节点可分配资源,就能判断是加规格、调 requests,还是加 toleration。
# 定位 Pending 根因
kubectl describe pod <pod> -n <ns> # 看 Events 段的调度失败原因
kubectl get nodes # 看节点状态与角色
kubectl describe node <node> # 看 Allocatable 与已分配资源
kubectl get pvc -n <ns> # 若 Pod 挂卷,先查 PVC 是否 Bound
资源不够时,Events 会明确告诉你每个节点差多少;这时候要么调小 requests,要么扩容节点,而不是反复删 Pod 重试——重试多少次结果都一样。
ImagePullBackOff:镜像拉取失败
ImagePullBackOff 是镜像拉取失败的标志,kubelet 会指数退避地重试。常见原因:镜像名或标签写错、私有仓库没配认证、镜像不存在、仓库网络不通或被限流。describe 的 Events 里通常有 Failed to pull image 和具体的 rpc error。
私有镜像仓库需要在 Pod 上配 imagePullSecrets,并且在 Deployment 里引用;镜像标签要显式写全(比如 nginx:1.25 而不是 nginx),否则可能拉到意想不到的版本。限流(比如 Docker Hub 的 rate limit)会让拉取一直超时,可以换镜像源或加认证。
# 给 Deployment 配置私有仓库凭据
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=ops \
--docker-password=xxxx
# 在 Deployment 的 pod 模板中引用
# spec:
# template:
# spec:
# imagePullSecrets:
# - name: regcred
排查顺序:先确认镜像在仓库里真实存在,再确认标签正确,最后确认认证和网络。别一上来就怀疑集群,多数时候是镜像侧的问题。
CrashLoopBackOff:容器反复重启
CrashLoopBackOff 表示容器起来后很快退出,kubelet 按指数退避不断重启。这几乎总是应用自身的问题:启动命令或参数错了、依赖的环境变量或配置缺失、连不上数据库或配置中心、健康检查探针把正常启动中的进程误判为失败。
第一手证据是 kubectl logs --previous——看上一次退出容器的日志,而不是当前正在重启的容器;再配合退出码判断:1 通常是应用内部错误,137 是被杀(多半 OOM),127 是命令找不到。探针误杀的问题,要看 readiness/liveness 的探测路径和启动时长是否合理。
# CrashLoop 排查组合拳
kubectl logs <pod> -n <ns> --previous # 上一次退出的日志
kubectl describe pod <pod> -n <ns> # 看 Last State 与退出码
kubectl get pod <pod> -n <ns> -o yaml # 看探针与 env 配置
# 退出码速查:1=应用错误 127=命令不存在
# 137=SIGKILL(常见 OOM) 143=SIGTERM(优雅退出失败)
注意一个反直觉的点:CrashLoopBackOff 并不总是应用崩了。健康检查探针配得太激进,会让一个本来能正常启动的应用被反复杀死,排查时先把探针和启动日志一起看。
节点异常:NotReady 与资源压力
Pod 本身没问题,但节点 NotReady 了,这时候要跳到节点层排查。kubelet 与 API 失联、容器运行时(containerd)挂了、磁盘或内存达到压力阈值,都会让节点进入 NotReady。压力状态下 kubelet 还会驱逐(eviction) Pod,表现为一批 Pod 同时消失。
# 节点层排查
kubectl get nodes # 看 READY 状态
kubectl describe node <node> # 看 Conditions 与 Pressure
ssh <node> "journalctl -u kubelet --since 30m" # kubelet 日志
df -h; free -m # 磁盘与内存压力直接看
看到 MemoryPressure 或 DiskPressure 条件为 True,就去清日志、清镜像缓存、调整驱逐阈值或扩容。节点磁盘被日志或镜像占满是运维里最常踩的坑,给节点预留 20% 的缓冲空间是基本素养。
控制面诊断:从 Pod 到集群
Pod 都 Running,但服务整体访问异常,问题可能出在集群层:API Server、etcd、网络插件(CNI)、CoreDNS。检查控制面组件健康,先探 API Server 的健康端点,再看核心组件的状态与日志;访问异常还要查 Service 的 Endpoints 是否就绪、CoreDNS 能否解析服务名。
# 控制面与网络诊断
curl -sk https://<apiserver>:6443/healthz # 控制面健康检查
kubectl get endpoints -A # Service 后端是否就绪
kubectl get pods -n kube-system # 核心组件是否 Running
kubectl -n kube-system logs -l k8s-app=kube-dns # CoreDNS 日志
排查要按层收敛:Pod → 节点 → 控制面 → 网络。每往上一层,先确认下一层是好的,别在应用层排了半天,最后发现是 CNI 插件把 Pod 网络都断了。
一套可复用的排障清单
把上面的经验固化成清单,出问题时照着走,不慌也不会漏:
排障清单(按顺序执行)
1. kubectl get pods -A → 记录异常 Pod 与状态
2. describe pod → 读 Events,定位直接原因
3. logs --previous → 看退出日志与退出码
4. 查 PVC / 镜像 / 探针 → 排除配置侧问题
5. get/describe node → 检查节点与资源压力
6. 控制面与网络 → healthz / endpoints / DNS
7. 记录时间线并写进复盘 → 让下次排障更快
排障能力的差距不在知道多少命令,而在有没有稳定的排查顺序和完整的记录习惯。每处理一次故障,就把根因、排查路径、修复动作记下来,沉淀成 runbook,下一次同类问题十分钟内就能收敛。这套思路和前面讲的 SRE 故障响应、混沌工程是一脉相承的。