首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java 应用上 Kubernetes 的 20 条生产级必备条件

Java 应用上 Kubernetes 的 20 条生产级必备条件

原创
作者头像
用户5741377
发布2026-02-24 19:59:59
发布2026-02-24 19:59:59
660
举报

正文如下👇

图片
图片

从“能跑”到“稳定运行三年不出大事故”

在 Kubernetes 上跑 Java,很多团队一开始都很自信:

不就是打个镜像、写个 YAML、起个 Pod 吗?

但真正上了生产你会发现,Java + K8s 是一场长期博弈

  • 有的服务隔三差五被 OOMKilled
  • 有的在高峰期内存飙升但 CPU 不高
  • 有的滚动升级一半流量 502
  • 有的 Pod 明明还活着,但已经“假死”

一个 Java 服务在 Kubernetes 上的真实生产治理案例:

在 Kubernetes 上跑 Java,几乎所有团队都会经历同一个阶段:

一开始很自信,上线很顺利,半年后开始疲于救火。

最初的认知通常是这样的:

  • 镜像能 build
  • Deployment 能起
  • Service 能通
  • 压测也能过

于是大家会下意识地认为: “Java + K8s 也就这么回事。”

但真正进入长期运行阶段后,问题开始慢慢出现。

一、真实背景:一个“看起来没问题”的 Java 服务

这是一个典型的业务中台服务:

  • Spring Boot + JDK 17
  • 日均请求量不高,但有明显高峰
  • 对 RT 敏感(接口链路中段)
  • 每次大促、批量任务都会被放大

部署方式非常“标准”:

代码语言:javascript
复制
resources:  requests:    cpu: "1"    memory: "2Gi"  limits:    cpu: "2"    memory: "2Gi"

JVM 参数也“很传统”:

代码语言:javascript
复制
-Xms2g -Xmx2g

上线初期,一切正常。

二、问题出现:不是一次事故,而是一连串“小问题”

1️⃣ Pod 开始偶发 OOMKilled

没有任何 Java OOM 日志,只看到:

代码语言:javascript
复制
Reason: OOMKilled

重启之后又能跑。

第一次,大家认为是偶发; 第二次,认为是流量波动; 第三次,开始有人说“是不是 K8s 不稳定”。

2️⃣ 高峰期 RT 抖动,但 CPU 很平稳

监控上看到一个反直觉的现象:

  • CPU 使用率:40%~50%
  • 内存接近 limit
  • RT 偶发性拉长
  • Full GC 偶尔出现

这时候,很多团队会犯一个错误: 👉 盲目加 Pod、副本翻倍

结果:

  • OOM 问题缓解了一点
  • 但资源成本上来了
  • 根因完全没解决

3️⃣ 滚动升级出现 502

滚动发布时,偶发:

  • 一半 Pod 已 Ready
  • 另一半一直重启
  • 用户侧看到 502

这类问题最要命的地方在于:你很难在测试环境复现。

三、问题精准判断

这个阶段,我给团队下了一个结论:

这不是 Kubernetes 的问题,也不是 Java 的问题,而是 JVM 没有被当成“运行在容器里的 JVM”来治理。

换句话说: 我们一直在用“物理机时代的 JVM 思维”,跑在“强边界的容器环境里”。

四、从“救火”转向“体系化治理”

第一件事:明确容器内 JVM 的内存模型

在容器里,JVM 实际消耗的内存是:

代码语言:javascript
复制
Heap+ Metaspace+ Direct Memory+ Thread Stack+ JVM 自身开销+ OS Buffer

而 Kubernetes 只认一件事:

总和 > memory limit → 直接 OOMKilled

这和物理机时代完全不同

五、第一轮整改:资源与 JVM 参数重构

1️⃣ 取消固定 Xmx / Xms

这是最关键的一步。

代码语言:javascript
复制
# 删除-Xms2g-Xmx2g

改为:

代码语言:javascript
复制
-XX:+UseContainerSupport-XX:MaxRAMPercentage=70

设计逻辑:

  • 容器 limit = 2Gi
  • Heap ≈ 1.4Gi
  • 剩余 600M 给非堆和 OS

👉 这是“容器优先”的 JVM 设计思路

2️⃣ 明确 request / limit 的工程含义

重新定义资源策略:

代码语言:javascript
复制
requests:  cpu: "800m"  memory: "2Gi"limits:  cpu: "2"  memory: "2.5Gi"

不是一拍脑袋就给值了,而是基于监控数据:

  • 稳态内存 ≈ 1.6Gi
  • 峰值 ≈ 1.9Gi
  • 非堆波动 ≈ 300~400M

六、第二轮整改:OOM 不再是“黑盒”

1️⃣ 强制开启 OOM Dump

代码语言:javascript
复制
-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/heapdumps

架构原则:

线上 OOM,必须留下“现场证据”。

没有 Dump 的 OOM 排查,本质是猜谜。

2️⃣ OOM 立即退出,而不是“苟活”

代码语言:javascript
复制
-XX:+ExitOnOutOfMemoryError

这一步是为 Kubernetes 设计的,而不是 JVM。

  • JVM 出问题
  • Pod 立刻退出
  • K8s 自动拉起新实例

这是平台化自愈的基础

七、第三轮整改:把“假死”交给平台处理

1️⃣ 探针职责重新划分

  • Readiness:只判断是否接流量
  • Liveness:只判断是否需要重启

避免一个接口干两件事。


2️⃣ 引入 Startup Probe

java程序启动慢是事实。

没有 Startup Probe 的结果只有一个: 👉 启动期被误杀


3️⃣ 实现真正的优雅停机

  • 捕获 SIGTERM
  • 停止接新请求
  • 等待正在处理的请求完成

这是避免滚动升级 502 的根本手段。

八、第四轮整改:从“能看到”到“能预测”

JVM 指标体系化

通过 Micrometer 暴露:

  • Heap / Non-Heap
  • GC 次数与耗时
  • 线程数变化

架构层面的价值是:

  • OOM 不再是突发
  • 内存泄漏有趋势
  • GC 抖动能提前感知

九、扩缩容策略的再认知

为什么 HPA 只看 CPU 一定会踩坑?

在 Java 场景下:

  • GC 抖动
  • 内存压力
  • 线程阻塞

往往不会直接反映到 CPU。

最终策略是:

  • CPU + RT
  • 内存趋势
  • 请求队列长度

十、最终结果:三年稳定运行的关键不是“参数”

这套服务上线后:

  • OOM 从“偶发事故” → “可预测事件”
  • 滚动升级 502 消失
  • 资源使用率下降 20%+
  • 再没有半夜被 OOMKilled 叫醒

真正起作用的不是某个 JVM 参数,而是这几个认知转变:

1️⃣ JVM 必须是“容器感知的 JVM” 2️⃣ limit 不是 JVM 的可用内存 3️⃣ OOM 是系统设计问题,不是偶发异常 4️⃣ K8s 负责重启,JVM 负责退出 5️⃣ 稳定性来自长期治理,而不是一次调优

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一个 Java 服务在 Kubernetes 上的真实生产治理案例:
  • 一、真实背景:一个“看起来没问题”的 Java 服务
  • 二、问题出现:不是一次事故,而是一连串“小问题”
    • 1️⃣ Pod 开始偶发 OOMKilled
    • 2️⃣ 高峰期 RT 抖动,但 CPU 很平稳
    • 3️⃣ 滚动升级出现 502
  • 三、问题精准判断
  • 四、从“救火”转向“体系化治理”
    • 第一件事:明确容器内 JVM 的内存模型
  • 五、第一轮整改:资源与 JVM 参数重构
    • 1️⃣ 取消固定 Xmx / Xms
    • 2️⃣ 明确 request / limit 的工程含义
  • 六、第二轮整改:OOM 不再是“黑盒”
    • 1️⃣ 强制开启 OOM Dump
    • 2️⃣ OOM 立即退出,而不是“苟活”
  • 七、第三轮整改:把“假死”交给平台处理
    • 1️⃣ 探针职责重新划分
    • 2️⃣ 引入 Startup Probe
    • 3️⃣ 实现真正的优雅停机
  • 八、第四轮整改:从“能看到”到“能预测”
    • JVM 指标体系化
  • 九、扩缩容策略的再认知
    • 为什么 HPA 只看 CPU 一定会踩坑?
  • 十、最终结果:三年稳定运行的关键不是“参数”
    • 1️⃣ JVM 必须是“容器感知的 JVM” 2️⃣ limit 不是 JVM 的可用内存 3️⃣ OOM 是系统设计问题,不是偶发异常 4️⃣ K8s 负责重启,JVM 负责退出 5️⃣ 稳定性来自长期治理,而不是一次调优
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档