1. 当Pod突然消失时:Crash背后的真相
凌晨三点,我被一阵刺耳的告警声惊醒——生产环境中有个关键Pod突然Crash了。这种场景对于云原生工程师来说再熟悉不过,就像急诊室医生面对突发病人一样,我们需要在最短时间内找到病因并实施救治。
1.1 第一响应:快速获取现场快照
当Pod发生Crash时,kubectl是我们最趁手的"听诊器"。以下是我每次必查的黄金命令组合:
# 查看Pod当前状态和最近事件 kubectl describe pod <pod-name> -n <namespace> # 获取容器崩溃前的最后日志(重要!) kubectl logs --previous <pod-name> -c <container-name> -n <namespace> # 检查Pod的yaml定义是否被修改过 kubectl get pod <pod-name> -n <namespace> -o yaml经验之谈:describe命令输出的"Last State"和"Exit Code"是破案的关键线索。比如Exit Code 137通常表示OOMKilled,而139则是段错误(Segmentation Fault)。
1.2 深度尸检:容器退出码解读
就像法医通过尸体特征判断死因,我们需要解读容器退出码:
| 退出码 | 常见原因 | 典型场景 |
|---|---|---|
| 0 | 正常退出 | 定时任务执行完毕 |
| 1 | 应用错误 | 配置文件语法错误 |
| 137 | SIGKILL(内存不足) | 超过内存限制被OOM Killer终止 |
| 143 | SIGTERM(优雅终止) | 滚动更新时旧Pod被终止 |
| 255 | 容器运行时错误 | 镜像启动命令不存在 |
上周我就遇到一个典型案例:某Java应用频繁Crash,退出码137。通过kubectl top pod发现内存使用量呈锯齿状波动,最终确认是JVM堆内存设置(-Xmx)超过了Pod的memory limit。
1.3 高级侦查:临时调试容器
当常规手段无法定位问题时,ephemeral container(临时容器)是我们的"内窥镜":
kubectl debug -it <pod-name> --image=busybox --target=<container-name>这个技巧特别适合以下场景:
- 容器启动即Crash无法获取日志
- 需要检查容器内文件系统状态
- 网络连通性测试(如ping/telnet)
避坑提示:使用debug容器时需要确保Pod的shareProcessNamespace设置为true,否则无法看到目标容器的进程。
2. 网络超时:云原生时代的"疑难杂症"
如果说Pod Crash是急性病,那么网络超时就是慢性顽疾。在微服务架构中,一次API调用可能穿越多个网络边界:
客户端 → Ingress → Service → Pod → 跨Namespace → 跨集群 → 外部服务2.1 网络诊断工具包
我的工具箱里常备这些网络诊断利器:
基础检查:
# 检查Service的Endpoints是否正常 kubectl get endpoints <service-name> # 测试ClusterIP连通性 kubectl run -it --rm test-curl --image=curlimages/curl -- curl -v http://<service>:<port> # DNS解析测试 kubectl run -it --rm test-nslookup --image=busybox -- nslookup <service>高级武器:
- Istio的Envoy访问日志(需开启access_log)
- Cilium的Hubble网络观测
- kube-proxy的conntrack表检查(
conntrack -L)
2.2 典型网络问题矩阵
根据我的故障记录本,云原生网络问题主要有这几类:
| 问题现象 | 可能原因 | 排查工具 |
|---|---|---|
| 间歇性连接重置 | 活跃连接被kube-proxy丢弃 | conntrack -L |
| 跨Namespace调用失败 | NetworkPolicy阻断 | kubectl get networkpolicy |
| DNS解析超时 | CoreDNS负载过高 | kubectl top pod -n kube-system |
| 节点间通信异常 | CNI插件配置错误 | ip route show |
| TLS握手失败 | 证书过期或SAN不匹配 | openssl s_client -connect |
上个月遇到一个经典案例:某服务调用另一个Namespace的服务频繁超时。最终发现是两个Namespace之间的NetworkPolicy默认是deny-all,添加如下策略后解决:
kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: allow-cross-namespace spec: podSelector: {} ingress: - from: - namespaceSelector: matchLabels: project: my-app3. 全链路排查方法论
经过多年实战,我总结出云原生故障排查的"五步法":
3.1 第一步:划定故障边界
- 是单个Pod还是整个Deployment?
- 影响单个服务还是跨服务调用?
- 是否与环境相关(如特定集群/节点/可用区)?
3.2 第二步:时间轴分析
- 故障发生前是否有变更(部署/配置/扩缩容)?
- 是否周期性出现(如整点定时任务)?
- 与业务流量是否相关(如促销活动期间)?
3.3 第三步:资源瓶颈检查
# 节点资源压力 kubectl top nodes # Pod资源使用情况 kubectl top pods --containers # 查看Pod的requests/limits kubectl get pod -o custom-columns=NAME:.metadata.name,CPU_REQ:.spec.containers[*].resources.requests.cpu,CPU_LIM:.spec.containers[*].resources.limits.cpu3.4 第四步:组件健康状态
# 核心组件状态 kubectl get cs # 查看kubelet日志 journalctl -u kubelet -n 100 # etcd健康检查 kubectl exec -n kube-system etcd-<node-name> -- etcdctl endpoint health3.5 第五步:分布式追踪
对于微服务场景,我习惯使用Jaeger做调用链分析。以下是一个典型的问题定位流程:
- 在Jaeger UI中搜索高延迟的span
- 查看该span的详细标签和日志
- 检查上游/下游服务的耗时分布
- 对比正常和异常请求的参数差异
4. 防御性编程:预防胜于治疗
4.1 Pod健康检查最佳实践
很多Crash其实可以通过合理的健康检查提前预防:
livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 # 避免启动阶段被误杀 periodSeconds: 10 failureThreshold: 3 # 连续失败3次才重启 readinessProbe: exec: command: - /bin/sh - -c - curl -s http://localhost:8080/ready | grep OK initialDelaySeconds: 5 periodSeconds: 5血泪教训:曾经因为readinessProbe检查过于严格(依赖数据库连通性),导致部署时所有Pod都无法进入Ready状态,形成死锁。现在我会确保readiness检查只验证应用本身状态。
4.2 资源限制的黄金法则
内存限制设置不当是OOM的罪魁祸首。我的经验公式:
JVM堆内存上限 = Pod内存限制 × 70% - 非堆内存预留(通常300MB)对于其他类型应用:
- Node.js:内存限制 = 常驻内存峰值 × 1.5
- Python:关注RSS而非VIRT(容器看的是物理内存)
4.3 网络弹性设计
这些配置可以显著提高网络可靠性:
# 增加TCP Keepalive env: - name: TCP_KEEPIDLE value: "60" - name: TCP_KEEPINTVL value: "10" - name: TCP_KEEPCNT value: "6" # 合理设置超时和重试 spring: cloud: loadbalancer: retry: maxRetriesOnNextServer: 1 maxAutoRetries: 15. 终极武器:可观测性体系建设
5.1 监控指标四象限
我团队必须监控的四大类指标:
资源维度:
- 容器CPU/Memory利用率
- 节点Disk IOPS/带宽
- 网络丢包率/重传率
应用维度:
- JVM GC次数/耗时
- 线程池活跃线程数
- 数据库连接池使用率
业务维度:
- 关键接口成功率
- 订单处理延迟
- 消息队列积压量
中间件维度:
- Redis命中率
- Kafka消费延迟
- Elasticsearch查询耗时
5.2 日志收集的智慧
经过多次优化,我们的日志方案演进为:
- 应用日志输出到stdout/stderr
- DaemonSet模式的FluentBit收集节点日志
- 通过Pipeline将不同级别日志路由到:
- ERROR级别 → 告警系统
- INFO级别 → 长期存储
- DEBUG级别 → 按需采样
# 查看特定时间段的日志(时间格式RFC3339) kubectl logs --since-time="2023-08-01T00:00:00Z" <pod-name>5.3 分布式追踪的三种境界
- 基础版:只追踪跨服务调用
- 进阶版:加入数据库/缓存等中间件调用
- 终极版:将业务流水ID贯穿全链路
这是我们使用的Jaeger采样策略,平衡了开销和有效性:
sampling: strategies: - type: probabilistic param: 0.1 # 10%采样率 - type: rate-limiting param: 100 # 每秒最多100条在云原生世界里,故障就像天气变化一样不可避免。但有了系统化的排查方法和完善的观测手段,我们就能像气象学家预测天气一样,提前发现并化解风险。每次故障都是一次学习机会,记录下你的排障过程,它们会成为你最宝贵的经验库。