K8s生产故障实战案例合集

案例一:K8s集群Pod频繁CrashLoopBackOff,日志无明确报错

背景

某政务业务系统部署在自建 K8s 集群(版本 v1.24)上,使用 StatefulSet 运行一个 Java 微服务应用,该服务依赖 MongoDB 和 Redis。集群采用 Calico 网络插件,存储使用 Rook‑Ceph 提供的 PVC。

问题现象

业务方反馈,该微服务 Pod 在最近一次发布后,持续处于 CrashLoopBackOff 状态,无法对外提供服务。

查看 Pod 日志(kubectl logs)仅显示应用启动过程中的 INFO 级别日志,无 ERROR 或 FATAL 异常,最后一行日志为 Started Application in 12.3 seconds,然后容器退出,状态码为 137(被 SIGKILL 终止)。

重启多次,问题依旧,且并非每次都立即崩溃,有时能运行 1‑2 分钟再退出。

排查过程

  1. 查看 Pod 事件 执行 kubectl describe pod,事件中仅有 Killing 记录,无 OOMKilled 标记,也没有调度失败或健康检查失败的事件。
  2. 检查资源限制 该 Pod 的 resources.limits.memory 设置为 2Gi,requests.memory 为 1Gi。通过 kubectl top pod 查看实时内存使用,发现崩溃前内存使用约 1.8Gi,未超过 2Gi,但接近上限。怀疑内存抖动触发 cgroup 回收,但未触发 OOM Killer。
  3. 查看宿主机 syslog 登录 Pod 所在节点,执行 journalctl -k | grep -i oom,未发现 OOM 事件。但发现大量 Memory cgroup out of memory 警告,表明容器被 cgroup 内存限制强制终止,只是未作为 OOM 计分(因进程退出较快)。
  4. 分析容器退出码 状态码 137 = 128 + 9(SIGKILL),通常由系统或外部进程杀死。结合 cgroup 警告,判断是内核因内存超限直接 SIGKILL 了容器进程,但并未触发 OOM Killer 的日志记录(进程可能在回收前已经退出)。
  5. 检查应用 JVM 参数 该 Java 应用未设置 -Xmx,默认 JVM 会使用宿主机可用内存的 1/4。在容器环境下可能超出容器内存限制。 虽然 K8s 1.24 支持容器感知 JVM 参数 UseContainerSupport,但该应用基础镜像较旧(OpenJDK 8u181),不支持该特性,导致 JVM 实际堆内存分配接近节点内存(64Gi)的 1/4 ≈16Gi,远大于容器限制 2Gi,从而在 GC 或堆扩张时被 cgroup 强制杀死。
  6. 辅助证据 查看 Pod 的 /sys/fs/cgroup/memory/memory.limit_in_bytes 确认限制为 2Gi,而 JVM 通过 jcmd 查询 MaxHeapSize 约为 16Gi,证实堆内存配置溢出容器限制。

根本原因

老旧 JDK 版本在容器环境下无法自动识别 cgroup 内存限制,默认使用宿主机内存计算堆大小,导致堆内存请求远超容器限制,引发 cgroup 强制 SIGKILL。且因进程未触发内核 OOM 计分,日志无明确报错,增加排查难度。

解决方案

  1. 临时修复 在 Deployment/StatefulSet 的容器启动参数中显式设置 JVM 堆大小:
-Xmx1536m -Xms1536m -XX:MaxMetaspaceSize=256m

使其低于容器内存限制(2Gi)并预留堆外内存空间。

  1. 长期优化 升级基础镜像至 OpenJDK 11 或 8u191+(支持 -XX:+UseContainerSupport),或使用 -XX:MaxRAMPercentage=70.0 动态适配容器限制。
  2. 增加健康检查 添加 livenessProbereadinessProbe,确保 Pod 启动失败时能快速重启,同时结合 terminationMessagePath 记录详细退出信息。

验证与恢复

修改 StatefulSet 环境变量 JAVA_OPTS="-Xmx1536m -Xms1536m" 后重新发布,Pod 稳定运行,内存使用维持在 1.2Gi 左右,不再 CrashLoopBackOff。 业务方确认接口调用正常,监控显示 GC 频率和响应时间均达标。

经验总结

  • 在容器化 Java 应用时,必须显式配置堆内存参数,切勿依赖旧版 JVM 的自动检测。
  • 排查容器异常退出时,优先查看宿主机内核日志和 cgroup 事件,而非仅依赖 Pod 事件和业务日志。
  • 建议为所有容器统一设置 terminationMessagePolicy: FallbackToLogsOnError,以便在退出时记录更多信息。

案例二:Nginx Ingress反向代理后端服务返回502 Bad Gateway

背景

某政务业务系统采用 K8s 集群(v1.26)部署,使用 Helm 部署的 Nginx Ingress Controller(v1.9.0)作为南北流量的统一接入网关,对外暴露 HTTPS 服务。后端服务为一个 Spring Boot 微服务(订单服务),部署在独立的 Namespace(prod-order)中,通过 ClusterIP Service 对外暴露 8080 端口。

问题现象

业务方反馈,通过外部域名 https://order.xxx.gov.cn/api/v1/list 访问订单查询接口时,返回 502 Bad Gateway 错误页面。

运维人员尝试跳过 Ingress,直接在集群内部通过 ClusterIP+端口 或 Pod IP+端口进行 curl 访问,后端服务能够正常返回 JSON 数据(HTTP 200)。

查看 Nginx Ingress Controller 的访问日志,发现上游状态码为 502,且响应时间极短(几毫秒),表明连接在后端即刻被拒绝或中断。

排查过程

  1. 查看 Ingress Controller 的错误日志 执行 kubectl logs -n ingress-nginx <controller-pod>,发现大量报错记录: upstream prematurely closed connection while reading response header from upstream

该报错通常表示 Nginx 与上游(后端 Pod)建立连接后,后端主动断开了连接,或 SSL/TLS 握手失败。

  1. 检查后端 Pod 运行状态与业务日志kubectl get pods -n prod-order 显示 Pod 均为 Running 状态,就绪探针正常。 查看后端业务日志,在请求发生时间点没有任何访问记录,说明 Ingress 的流量实际上并未到达 Pod 内的应用进程。
  2. 验证 Service 与 Endpoints 的连通性kubectl get endpoints -n prod-order 显示 Endpoints 正确关联了后端 Pod 的 IP 和端口 8080。 在集群内 Debug 容器中,通过 Service ClusterIP curl 访问,返回正常,证明 K8s 内部网络转发链路无问题。
  3. 排查安全组与网络策略 检查 Namespace 下的 NetworkPolicy,未施加任何入站限制。 检查云厂商安全组,Ingress Controller 与 Pod 在同一 VPC 子网,内网互访无 ACL 拦截。
  4. 聚焦 Ingress 资源配置清单 执行 kubectl describe ingress -n prod-order,关键配置片段:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: order.xxx.gov.cn
    http:
      paths:
      - path: /api/v1
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 8080

注意注解中显式声明了 backend‑protocol: "HTTPS"。经确认,后端 Spring Boot 实际只开放 HTTP 8080,没有开启 TLS。该注解是安全演练误添加,应用配置回滚,但 Ingress 注解没有同步删除。

根本原因

Ingress Controller 根据注解 backend‑protocol: HTTPS,和后端建立连接时使用 TLS 加密握手。而后端只监听纯 HTTP,收到 SSL 握手包无法解析,直接中断 TCP 连接。 连接在协议握手阶段被重置,请求没有到达业务进程,后端日志无输出,Nginx 返回 502。

解决方案

  1. 临时修复 编辑 Ingress 资源:kubectl edit ingress -n prod-order order-ingressnginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 修改为 "HTTP",或者直接删除此行(默认使用 HTTP)。
  2. 配置生效 Nginx Ingress Controller 自动重载,10‑20秒生效;如需立即生效:
kubectl exec -it <controller-pod> -n ingress-nginx -- nginx -s reload
  1. 验证恢复 curl 测试接口,返回正常 JSON,HTTP 200。

验证与恢复

业务方确认订单查询、列表刷新功能全部恢复。监控中 502 计数归零。 增加 Ingress 变更审计告警,涉及 backend‑protocol 关键注解修改触发工单复审。

经验总结

  • Ingress Annotation 注解必须和后端真实协议、端口严格匹配。HTTPS 接入层不代表后端一定是 HTTPS,两者互相解耦。
  • 排查 502:直连 Pod 正常、Ingress 异常,优先看 Controller 握手报错,再核对 Service、端口、协议类注解。
  • 使用标准化 Ingress 模板,结合 GitOps + kubectl diff,防止配置残留引发故障。

案例三:Nginx Ingress访问后端服务超时返回504 Gateway Timeout

背景

某政务业务系统采用 K8s 集群(v1.26)部署,使用 Nginx Ingress Controller(v1.9.0)作为统一流量入口。后端为数据报表生成服务,部署在 prod-report 命名空间,ClusterIP Service 暴露 8080 端口。该服务需要从数仓拉取大量数据做聚合计算生成报表。

问题现象

业务方反馈:https://report.xxx.gov.cn/api/v1/data 小数据请求正常;大数据耗时请求等待约60秒返回 504 Gateway Timeout

集群内部绕过 Ingress 访问接口,大数据请求后端 90 秒左右可以正常返回 200,证明后端、数据库链路正常。 Ingress 访问日志中超时请求上游状态码全部为504,响应时间刚好60秒。

排查过程

  1. 查看 Ingress Controller 错误日志 执行 kubectl logs -n ingress-nginx <controller-pod>,报错: upstream timed out (110: Connection timed out) while reading response header from upstream 代表 Nginx 等待上游响应头到达超时阈值。
  2. 检查后端 Pod 运行状态与业务日志 Pod Running、探针正常。后端日志收到请求并开始处理,但业务处理完成前连接已被 Ingress 断开,日志出现 broken pipe / connection reset by peer,确认是 Ingress 主动切断连接。
  3. 聚焦 Ingress 超时相关配置kubectl describe ingress -n prod-report,没有任何超时相关注解。使用 Ingress 默认超时:proxy‑read‑timeoutproxy‑send‑timeout 默认都是60秒,与故障现象60秒超时吻合。
  4. 确认后端服务平均响应时间 监控显示报表复杂查询 P95≈75秒,P99≈120秒,大于 Ingress 默认60秒。

根本原因

Nginx Ingress Controller 默认60秒超时时间小于后端业务真实处理耗时。后端处理超过阈值,Ingress 主动断开连接返回504,属于组件超时配置不匹配故障。

解决方案

  1. 临时修复,调大超时注解 kubectl edit ingress -n prod-report report-ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "180"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "180"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
  1. 配置生效 Controller 自动重载,10‑20秒生效。
  2. 验证恢复 大数据请求测试,75秒左右正常返回200。

验证与恢复

报表复杂查询全部正常,504指标归零。 长期优化:业务侧异步化、分页改造,降低单次接口耗时。

经验总结

  • 默认超时并不适配所有业务;大数据查询、AI推理、文件上传等长耗时场景,务必显式配置 Ingress 超时注解。
  • 504排障思路:先看Ingress日志超时时间,对比后端真实耗时,核对两端超时配置。
  • 根据业务类型制定不同 Ingress 配置模板,上线压测拿到响应时间基线作为配置依据。

案例四:K8s StatefulSet扩容时新增Pod持续Pending

背景

某政务业务系统迁移至 K8s(v1.28),Rook‑Ceph 提供持久存储。StorageClass 设置 volumeBindingMode: WaitForFirstConsumer(延迟绑定):PVC 需要等待 Pod 调度到节点之后,才创建并绑定 PV。业务需要将有状态应用副本从2扩容到3。

问题现象

扩容后新 Pod app‑pod‑2 长时间 Pending,超过30分钟无法调度。kubectl get pods 状态 0/1。 原有2个Pod运行正常;新 Pod 对应的 PVC 也处于 Pending。

排查过程

  1. 查看 Pod 详细事件 kubectl describe pod app‑pod‑2 -n <namespace>
Warning FailedScheduling  0/5 nodes are available: 5 Insufficient memory.

全部Worker节点内存不足,调度失败。

  1. 检查节点剩余资源kubectl describe nodes,节点 allocatable 内存64Gi,requests 合计占用约62Gi,剩余不足2Gi。新Pod requests.memory:4Gi,大于剩余可用调度内存,调度预选全部节点失败。
  2. 排查内存占用来源 测试命名空间 test 大量压测Pod,实际内存使用率只有30%,但是 requests.memory 统一设置4Gi。调度器按 requests 做预留,造成资源虚占。
  3. 关注PVC状态,避免误判 PVC 同样 Pending,describe pvc 事件:waiting for first consumer to be created before binding。 因为 Pod 调度失败,无法确定目标节点,延迟绑定模式下PVC不会创建PV。PVC Pending是Pod调度失败连锁反应,不是Ceph存储故障。

根本原因

直接原因:测试环境大量工作负载高 requests 虚占集群调度内存,新Pod无法调度。 间接原因:requests 设置不合理,没有按照真实使用量配置,造成资源碎片化。 放大效应:StorageClass延迟绑定,PVC同步卡住,容易误导运维排查存储。

解决方案

  1. 临时恢复 缩容/删除 test 命名空间高 requests 闲置Pod,释放调度内存。Pod调度成功后PVC自动Bound,Pod进入Running。
  2. 长期优化
  • 根据监控调整 requests:requests 使用真实业务水位,limits保留峰值;示例 requests=1.5Gi,limits=4Gi。
  • 开启 ResourceQuota,每个命名空间设置资源上限,防止测试抢占生产。
  • 部署 Cluster Autoscaler,节点自动扩缩容。

验证与恢复

清理测试Pod后,Pod快速由Pending → ContainerCreating → Running;PVC变为Bound。业务读写数据正常,扩容成功。持续监控节点 requests 占用低于70%。

经验总结

  • Pod Pending,优先执行 kubectl describe pod,看 Events 的调度失败原因,不要上来就查存储网络。
  • requests 是调度硬依据,不等于真实内存使用量,不合理设置会造成资源虚占。
  • 使用 WaitForFirstConsumer 延迟绑定,会出现Pod Pending连带PVC Pending假象;优先解决调度问题,不要盲目排查CSI存储。
上一篇
下一篇