案例一: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 分钟再退出。
排查过程
- 查看 Pod 事件 执行
kubectl describe pod,事件中仅有 Killing 记录,无 OOMKilled 标记,也没有调度失败或健康检查失败的事件。 - 检查资源限制 该 Pod 的
resources.limits.memory设置为 2Gi,requests.memory为 1Gi。通过kubectl top pod查看实时内存使用,发现崩溃前内存使用约 1.8Gi,未超过 2Gi,但接近上限。怀疑内存抖动触发 cgroup 回收,但未触发 OOM Killer。 - 查看宿主机 syslog 登录 Pod 所在节点,执行
journalctl -k | grep -i oom,未发现 OOM 事件。但发现大量Memory cgroup out of memory警告,表明容器被 cgroup 内存限制强制终止,只是未作为 OOM 计分(因进程退出较快)。 - 分析容器退出码 状态码 137 = 128 + 9(SIGKILL),通常由系统或外部进程杀死。结合 cgroup 警告,判断是内核因内存超限直接 SIGKILL 了容器进程,但并未触发 OOM Killer 的日志记录(进程可能在回收前已经退出)。
- 检查应用 JVM 参数 该 Java 应用未设置
-Xmx,默认 JVM 会使用宿主机可用内存的 1/4。在容器环境下可能超出容器内存限制。 虽然 K8s 1.24 支持容器感知 JVM 参数UseContainerSupport,但该应用基础镜像较旧(OpenJDK 8u181),不支持该特性,导致 JVM 实际堆内存分配接近节点内存(64Gi)的 1/4 ≈16Gi,远大于容器限制 2Gi,从而在 GC 或堆扩张时被 cgroup 强制杀死。 - 辅助证据 查看 Pod 的
/sys/fs/cgroup/memory/memory.limit_in_bytes确认限制为 2Gi,而 JVM 通过 jcmd 查询MaxHeapSize约为 16Gi,证实堆内存配置溢出容器限制。
根本原因
老旧 JDK 版本在容器环境下无法自动识别 cgroup 内存限制,默认使用宿主机内存计算堆大小,导致堆内存请求远超容器限制,引发 cgroup 强制 SIGKILL。且因进程未触发内核 OOM 计分,日志无明确报错,增加排查难度。
解决方案
- 临时修复 在 Deployment/StatefulSet 的容器启动参数中显式设置 JVM 堆大小:
-Xmx1536m -Xms1536m -XX:MaxMetaspaceSize=256m
使其低于容器内存限制(2Gi)并预留堆外内存空间。
- 长期优化 升级基础镜像至 OpenJDK 11 或 8u191+(支持
-XX:+UseContainerSupport),或使用-XX:MaxRAMPercentage=70.0动态适配容器限制。 - 增加健康检查 添加
livenessProbe和readinessProbe,确保 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,且响应时间极短(几毫秒),表明连接在后端即刻被拒绝或中断。
排查过程
- 查看 Ingress Controller 的错误日志 执行
kubectl logs -n ingress-nginx <controller-pod>,发现大量报错记录:upstream prematurely closed connection while reading response header from upstream
该报错通常表示 Nginx 与上游(后端 Pod)建立连接后,后端主动断开了连接,或 SSL/TLS 握手失败。
- 检查后端 Pod 运行状态与业务日志
kubectl get pods -n prod-order显示 Pod 均为 Running 状态,就绪探针正常。 查看后端业务日志,在请求发生时间点没有任何访问记录,说明 Ingress 的流量实际上并未到达 Pod 内的应用进程。 - 验证 Service 与 Endpoints 的连通性
kubectl get endpoints -n prod-order显示 Endpoints 正确关联了后端 Pod 的 IP 和端口 8080。 在集群内 Debug 容器中,通过 Service ClusterIP curl 访问,返回正常,证明 K8s 内部网络转发链路无问题。 - 排查安全组与网络策略 检查 Namespace 下的 NetworkPolicy,未施加任何入站限制。 检查云厂商安全组,Ingress Controller 与 Pod 在同一 VPC 子网,内网互访无 ACL 拦截。
- 聚焦 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。
解决方案
- 临时修复 编辑 Ingress 资源:
kubectl edit ingress -n prod-order order-ingress将nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"修改为"HTTP",或者直接删除此行(默认使用 HTTP)。 - 配置生效 Nginx Ingress Controller 自动重载,10‑20秒生效;如需立即生效:
kubectl exec -it <controller-pod> -n ingress-nginx -- nginx -s reload
- 验证恢复 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秒。
排查过程
- 查看 Ingress Controller 错误日志 执行
kubectl logs -n ingress-nginx <controller-pod>,报错:upstream timed out (110: Connection timed out) while reading response header from upstream代表 Nginx 等待上游响应头到达超时阈值。 - 检查后端 Pod 运行状态与业务日志 Pod Running、探针正常。后端日志收到请求并开始处理,但业务处理完成前连接已被 Ingress 断开,日志出现
broken pipe/connection reset by peer,确认是 Ingress 主动切断连接。 - 聚焦 Ingress 超时相关配置
kubectl describe ingress -n prod-report,没有任何超时相关注解。使用 Ingress 默认超时:proxy‑read‑timeout、proxy‑send‑timeout默认都是60秒,与故障现象60秒超时吻合。 - 确认后端服务平均响应时间 监控显示报表复杂查询 P95≈75秒,P99≈120秒,大于 Ingress 默认60秒。
根本原因
Nginx Ingress Controller 默认60秒超时时间小于后端业务真实处理耗时。后端处理超过阈值,Ingress 主动断开连接返回504,属于组件超时配置不匹配故障。
解决方案
- 临时修复,调大超时注解
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"
- 配置生效 Controller 自动重载,10‑20秒生效。
- 验证恢复 大数据请求测试,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。
排查过程
- 查看 Pod 详细事件
kubectl describe pod app‑pod‑2 -n <namespace>
Warning FailedScheduling 0/5 nodes are available: 5 Insufficient memory.
全部Worker节点内存不足,调度失败。
- 检查节点剩余资源
kubectl describe nodes,节点 allocatable 内存64Gi,requests合计占用约62Gi,剩余不足2Gi。新Podrequests.memory:4Gi,大于剩余可用调度内存,调度预选全部节点失败。 - 排查内存占用来源 测试命名空间
test大量压测Pod,实际内存使用率只有30%,但是requests.memory统一设置4Gi。调度器按 requests 做预留,造成资源虚占。 - 关注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同步卡住,容易误导运维排查存储。
解决方案
- 临时恢复 缩容/删除 test 命名空间高 requests 闲置Pod,释放调度内存。Pod调度成功后PVC自动Bound,Pod进入Running。
- 长期优化
- 根据监控调整 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存储。