云原生与 Kubernetes
第 81 题:K8s 部署与 OOMKilled 治理 K8s
OOMKilled(exit 137),但 JVM 堆只设 2G,容器 limit 也 2G,加上元空间/栈/堆外后超 limit 被 kill。要求:正确设置 JVM 与容器内存关系。- 1. 为什么 OOMKilled?2. JVM 与 limit 关系?3. 怎么设才不 kill?4. 监控?5. 压测验证?
第一步:分析。OOMKilled 是容器内存超 limit 被 cgroup kill,不是 JVM heap OOM。JVM 总内存=堆+元空间+栈+堆外,常超容器 limit。
第二步:挑战。内存账、limit 设置、监控。
第三步:架构。①内存账:limit ≥ Xmx + MaxMetaspace + 线程栈×线程数 + 堆外(Netty)+ 系统;②设置:Xmx 留余量(如 limit 4G → Xmx 2.5G);③探针:liveness/readiness;④监控:容器内存使用率 + JVM 各区;⑤压测:验证峰值不超 limit。
第四步:选型。K8s resources.limits + JVM 参数 + 监控(cAdvisor/Prometheus)。
第五步:一致性。内存设置不影响语义;降 OOM 保稳定。
第六步:高可用。OOMKilled 后 Pod 重启;多副本防单点。
第七步:优化。用 UseContainerSupport(JDK 8u191+ 默认识别容器);限池大小。
为什么 kill:cgroup limit 是硬上限,JVM 不知堆外/栈也算,总内存超 limit 即被 kill(137)。内存账:limit 必须覆盖全部,不能只等于 Xmx。UseContainerSupport:新版 JVM 自动读容器 limit 作为可用内存,避免按宿主机算错。最佳实践:Xmx ≈ limit×0.7~0.8,留其他区。
// 容器 limit=4G, JVM 合理设置 -Xms2g -Xmx2g -XX:MaxMetaspaceSize=256m -Xss256k -XX:+UseContainerSupport -XX:MaxDirectMemorySize=512m // K8s deployment resources: limits: {memory: 4Gi, cpu: 2} requests: {memory: 4Gi, cpu: 1} // 监控: container_memory_working_set_bytes 告警
追问 1:requests 与 limits 不同?同值保 QoS=Guaranteed(不被优先驱逐);不同值可被驱逐。
追问 2:堆外泄漏 OOMKilled?NMT 跟踪;限 MaxDirectMemorySize;Netty release。
追问 3:老 JDK 不识容器?手动设 -Xmx;或升级 JDK(8u191+ 默认识别)。
追问 4:监控看哪个指标?container_memory_working_set_bytes(含 cache)逼近 limit 即预警。
追问 5:压测验证?峰值压测观察内存曲线,确认不触 limit。
- ❌ Xmx=limit——其他区超被 kill。正确:Xmx 留余量。
- ❌ 不识容器——按宿主机算。正确:UseContainerSupport。
- ❌ 只盯 heap——漏堆外/栈。正确:全内存账。
第 82 题:HPA 自动扩容与容量弹性 HPA
- 1. HPA 原理?2. 基于什么指标?3. 震荡怎么防?4. 扩容上限?5. 扩容后仍慢?
第一步:分析。HPA 按指标自动调副本数,实现弹性。需防震荡、设上限、配合理指标。
第二步:挑战。指标选择、震荡、上限、依赖。
第三步:架构。①指标:CPU/内存(基础)或 QPS/自定义(更准,Prometheus Adapter);②防震荡:stabilizationWindow(冷却)、合理阈值;③上限:maxReplicas 防无限扩(成本);④依赖:扩容后 DB/Redis 能力需跟上(否则扩了也崩);⑤预案:大促前预扩 + HPA 保底。
第四步:选型。HPA v2 + Prometheus Adapter(自定义指标)+ KEDA(事件驱动)。
第五步:一致性。扩容无状态服务,实例间无状态一致。
第六步:高可用。弹性防过载;上限防成本爆炸。
第七步:优化。预扩(大促)+ HPA(突发);指标驱动。
指标选择:CPU 通用但滞后(GC 也吃 CPU);QPS/RT 更贴近业务,用 Prometheus Adapter 暴露自定义指标。震荡:扩容后指标降→又缩→又涨,用 stabilizationWindow(如 300s)平滑。上限:maxReplicas 防成本失控,也防下游(DB)被打。依赖瓶颈:只扩应用,DB 成瓶颈则无效,需全链路容量匹配。
// HPA 基于 QPS 自定义指标 apiVersion: autoscaling/v2 metrics: - type: Pods pods: { metric: {name: http_qps}, target: {averageValue: 2000} } behavior: scaleDown: stabilizationWindowSeconds: 300 # 防震荡 maxReplicas: 100 minReplicas: 10 // 大促前预扩: kubectl scale deploy --replicas=50
追问 1:扩容后仍慢?下游(DB/Redis)瓶颈;需全链路扩容或优化。
追问 2:CPU 指标抖动?用 QPS/RT 业务指标 + 冷却窗口稳。
追问 3:无状态才能扩?有状态(如带本地会话)需先无状态化(外置会话)。
追问 4:成本?maxReplicas 上限 + 缩容快扩容留冷却;非高峰自动缩。
追问 5:事件驱动扩容?KEDA 按 MQ 长度/Kafka lag 扩消费者,适合队列场景。
- ❌ 无上限扩——成本爆炸。正确:maxReplicas。
- ❌ 不防震荡——频繁扩缩。正确:冷却窗口。
- ❌ 只扩应用——下游崩。正确:全链路容量。
第 83 题:Pod 故障排查套路 排查
CrashLoopBackOff / Pending / ImagePullBackOff,新人不清楚从哪查。要求:给出标准 Pod 故障排查路径。- 1. 排查顺序?2. Pending 原因?3. CrashLoopBackOff?4. 资源/探针?5. 日志与事件?
第一步:分析。Pod 故障先分层:调度(Pending)→ 拉镜像(ImagePull)→ 启动(CrashLoop)→ 运行(探针/资源)。
第二步:挑战。分层定位、资源、探针。
第三步:架构。①describe:看 Events(调度失败/拉镜像错);②Pending:资源不足/节点亲和/污点;③CrashLoop:看 logs + 退出码(137=OOM,1=应用错);④探针:liveness 配错致反复重启;⑤资源:limit 太小 OOMKilled。
第四步:选型。kubectl describe/logs/events + 监控。
第五步:一致性。排查不影响其他 Pod(隔离)。
第六步:高可用。多副本;探针正确防误杀。
第七步:优化。就绪探针控流量;资源 requests 合理。
排查顺序:kubectl get pod → describe(Events 最关键)→ logs → 退出码。Pending:集群资源不够 / nodeSelector 不匹配 / 污点未容忍。CrashLoop:退出 137=OOM(Q81),1/2=应用启动异常(看 logs)。探针误杀:liveness 初始延迟太短或阈值严,Pod 启动慢被 kill 重启;需配 initialDelaySeconds。
// 探针配置(避免误杀) livenessProbe: { tcpSocket: {port: 8080}, initialDelaySeconds: 30, periodSeconds: 10 } readinessProbe: { httpGet: {path: /health}, initialDelaySeconds: 10 } // 排查命令 kubectl describe pod order-xxx # 看 Events kubectl logs order-xxx --previous # 看上次崩溃日志
追问 1:Pending 资源够也调度不了?nodeSelector/亲和不匹配或污点未容忍;检查 taint/toleration。
追问 2:ImagePullBackOff?镜像名错/仓库秘钥缺失/私有仓未配 imagePullSecret。
追问 3:就绪探针配错?就绪失败→不接流量但 Pod 活;与 liveness 区分。
追问 4:退出码 139?段错误(JNI/原生),极少;查 native 代码。
追问 5:批量 Pod 异常?可能是节点/网络/存储问题,查节点状态与事件聚合。
- ❌ 不看 describe Events——盲查。正确:先 Events。
- ❌ 探针 initialDelay 太短——误杀。正确:配延迟。
- ❌ 不区分就绪/存活——流量错。正确:分开配。
第 84 题:滚动发布与优雅停机 发布
- 1. 为什么直接 kill 丢请求?2. 优雅停机怎么做?3. 探针配合?4. 消费位怎么保?5. 发布策略?
第一步:分析。滚动发布需「先接新、再退旧、旧 Pod 处理完再杀」,靠就绪探针 + 优雅停机 + preStop 实现零中断。
第二步:挑战。中断、消费位、探针、策略。
第三步:架构。①探针:新 Pod 就绪(readiness 通过)才接流量;②优雅停机:收到 SIGTERM 先停接收新请求、处理完在途请求、关连接池/MQ 再退;③preStop:sleep 几秒等 LB 摘流量;④消费位:Kafka 用 enable.auto.commit=false,停机前 commitSync;⑤策略:maxSurge/maxUnavailable 控制节奏。
第四步:选型。K8s RollingUpdate + Spring 优雅停机 + Kafka 手动提交。
第五步:一致性。在途请求处理完,消费位提交,无丢失。
第六步:高可用。滚动保最少可用(maxUnavailable=0);零中断。
第七步:优化。配合灰度(Q35)降低风险。
为什么中断:默认 SIGTERM 后宽限期(terminationGracePeriodSeconds)内强制 kill,在途请求/未提交消费位丢失。优雅停机:Spring 注册 ShutdownHook,先关端口(不再收新)、等线程池任务完、关资源。preStop sleep:等 LB/就绪探针摘掉流量再处理在途。消费位:停机前 commitSync 防重复消费。
// Spring 优雅停机 server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s // Kafka 停机前提交 public void shutdown(){ consumer.commitSync(); } // ShutdownHook // K8s: preStop 等流量摘离 lifecycle: preStop: { exec: { command: ["sh","-c","sleep 10"] } } strategy: rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
追问 1:宽限期不够?调 terminationGracePeriodSeconds 大于在途最长处理时间。
追问 2:长事务停机?停机前等事务完或提交;超长事务用补偿。
追问 3:MQ 消费重复?消费幂等(Q63)兜底,重复无害。
追问 4:maxUnavailable=0 慢?保零中断但扩副本数;权衡速度用 1。
追问 5:配合灰度?滚动+灰度(Q35)双保险,错误半径更小。
- ❌ 直接 kill——请求/消费位丢。正确:优雅停机。
- ❌ 无 preStop——流量未摘即杀。正确:preStop 摘流。
- ❌ 自动提交消费——位丢。正确:手动提交。
第 85 题:服务网格与灰度流量 Mesh
- 1. Mesh 灰度怎么路由?2. 为什么下游没跟?3. 标怎么透传?4. 与 Q35 应用级灰度区别?5. mTLS?
第一步:分析。Mesh 灰度靠 Sidecar 按 VirtualService 路由,但跨服务标透传需应用配合(把标放进请求头),否则下游不知灰度。
第二步:挑战。路由、标透传、与应用级灰度配合。
第三步:架构。①路由:VirtualService 按 header(x-version)路由 1% 到 v2;②标透传:应用把 x-version 在调用下游时透传(Feign 拦截器,Q35);③全链路:标从入口到每个服务一致;④mTLS:Mesh 自动服务间加密;⑤配合:Mesh 管流量,应用管标透传与数据兼容。
第四步:选型。Istio(VirtualService/DestinationRule)+ 应用标透传 + OTel baggage。
第五步:一致性。同用户全链路同版本(标透传保证)。
第六步:高可用。灰度故障自动回滚(流量权重);mTLS 零信任。
第七步:优化。基于指标自动推进(Argo Rollouts)。
为什么下游没跟:Sidecar 只管「入站请求按规则路由」,但应用调下游时的请求头若无 x-version,下游 Sidecar 不知该路由哪版。解决:应用层把灰度标透传(Q35)。Mesh vs 应用级:Mesh 管流量与 mTLS(无侵入),应用管标生成/透传与数据兼容,两者互补。mTLS:Istio 自动签发证书,服务间零信任。
// 应用透传灰度标(Feign拦截器) public void apply(RequestTemplate t){ t.header("x-version", GrayContext.get()); // 透传下游 } // Istio VirtualService: 1% 路由 v2 apiVersion: networking.istio.io/v1beta1 route: - destination: {host: order-v2} weight: 1 - destination: {host: order} weight: 99 match: [{headers: {x-version: {exact: v2}}}]
追问 1:标丢了下游错乱?应用层强制透传 + 默认走稳定版,避免错乱。
追问 2:Mesh 灰度 vs 应用灰度?Mesh 管流量/mTLS 无侵入,应用管标/数据;互补。
追问 3:mTLS 性能?Sidecar TLS 有少量开销,现代 CPU 可承受;收益是零信任。
追问 4:自动推进?Argo Rollouts 基于指标(错误率/RT)自动放量/回滚。
追问 5:非 K8s 服务?Mesh 主要 K8s;非 K8s 用应用级灰度(Q35)。
- ❌ 只配 VS 不透明传——下游错乱。正确:应用透传标。
- ❌ Mesh 包办一切——数据兼容不管。正确:应用配合。
- ❌ 无 mTLS——零信任缺。正确:开启。