ITPub博客

首页 > 云计算 > Docker/K8 > Java 应用上 Kubernetes 的 20 条生产级必备条件

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

原创 Docker/K8 作者:hanxianchao 时间:2026-02-24 19:38:02 0 删除 编辑

图片

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

在 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 敏感(接口链路中段)

  • 每次大促、批量任务都会被放大

部署方式非常“标准”:

    resources:  
      requests:    
        cpu: "1"    
        memory: "2Gi"  
      limits:    
        cpu: "2"    
        memory: "2Gi"

    JVM 参数也“很传统”:

      -Xms2g 
      -Xmx2g

      上线初期,一切正常。

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

      1️⃣ Pod 开始偶发 OOMKilled

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

        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 实际消耗的内存是:

          Heap
          +Metaspace
          +Direct Memory
          +Thread Stack
          +JVM 自身开销
          +OS Buffer

          而 Kubernetes 只认一件事:

          总和 > memory limit → 直接 OOMKilled

          这和物理机时代 完全不同 。

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

          1️⃣ 取消固定 Xmx / Xms

          这是最关键的一步。

            # 删除
            -Xms2g
            -Xmx2g

            改为:

              -XX:+UseContainerSupport
              -XX:MaxRAMPercentage=70

              设计逻辑:

              • 容器 limit = 2Gi

              • Heap ≈ 1.4Gi

              • 剩余 600M 给非堆和 OS

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

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

              重新定义资源策略:

                requests:  
                  cpu: "800m"  
                  memory: "2Gi"
                limits:  
                  cpu: "2"  
                  memory: "2.5Gi"

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

                • 稳态内存 ≈ 1.6Gi

                • 峰值 ≈ 1.9Gi

                • 非堆波动 ≈ 300~400M


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

                1️⃣ 强制开启 OOM Dump

                  -XX:+HeapDumpOnOutOfMemoryError
                  -XX:HeapDumpPath=/heapdumps

                  架构原则:

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

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

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

                    -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️⃣ 稳定性来自长期治理,而不是一次调优


                    来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70042969/viewspace-3110883/ ,如需转载,请注明出处,否则将追究法律责任。

                    请登录后发表评论 登录
                    全部评论
                    云计算架构师、阿里云开发者社区专家博主、腾讯云架构师北京同盟成员、Linux基金会开源软件学园2023、2024年度优秀讲师,在中国计算机学会(CCF)任职人工智能与模式识别委员和计算机应用委员、中国科普作家协会会员,清华大学出版社出版的畅销书《Kubernetes从入门到DevOps企业应用实战》作者。

                    注册时间:2017-10-15

                    • 博文量
                      127
                    • 访问量
                      61682