找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1489

积分

0

好友

195

主题
发表于 12 小时前 | 查看: 3| 回复: 0

三年前,我们满怀信心地把核心业务系统搬上了K8s。当时想得很简单:Java我们熟,K8s也学了,打个镜像、写个YAML,能跑不就完事了?

结果呢?第一个月风平浪静,半年后开始每周被叫醒救火。最夸张的时候,凌晨2点的报警能连环响,运维兄弟看到钉钉群的消息就手抖。

今天不聊虚的,就说说我们这套跑了三年的Java服务,是怎么从“能跑就行”进化到“三年不出大事故”的。全程真实踩坑记录,每一个问题都是真金白银换来的教训。

一、先说说我们当时有多惨

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

  • Spring Boot + JDK 17
  • 日均千万级请求,有明显业务高峰
  • 对响应时间敏感(处于调用链中段)
  • 大促期间流量能翻5-10倍

当时的标准配置长这样:

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

JVM参数也是祖传配方:

-Xms2g -Xmx2g

看着挺规范对吧?该有的都有了。但问题就是从这个“标准配置”开始的。

二、那些年我们踩过的坑(你以为的偶发,其实是必然)

1️⃣ Pod莫名其妙就挂了

最诡异的是:Pod显示OOMKilled,但翻遍Java日志,找不到任何OOM异常。

Reason: OOMKilled

重启就能好,但谁也不知道为什么。第一次,大家说是偶发;第二次,说是流量波动;第三次,有人开始嘀咕“K8s是不是不稳定”...

2️⃣ 高峰期响应时间忽长忽短

监控上出现了一个反直觉的现象:

  • CPU利用率只有40%-50%
  • 内存接近limit
  • RT(响应时间)隔一会儿就抽风一次
  • Full GC偶尔出现

这个时候最容易犯的错误是:盲目加Pod
结果呢?OOM确实少了,但资源成本直接翻倍,根因压根没解决。

3️⃣ 滚动升级必出502

发版的时候总有几个用户反馈“打不开页面”。一看日志:

  • 一半Pod已经Ready
  • 另一半还在重启
  • 网关疯狂报错

最头疼的是:测试环境永远复现不出来。这才是真正的“生产环境专属bug”。

三、问题的本质:我们用物理机的思维,跑容器的JVM

排查到最后,我给团队下了一个结论:这不是K8s的问题,也不是Java的问题,而是我们一直用“物理机时代的JVM思维”,在跑“强边界的容器环境”。

物理机时代,JVM觉得自己就是这台机器的老大,内存随便用,不行还有Swap。但容器里呢?

JVM实际消耗的内存包括:

  • Heap(堆)
  • Metaspace(元空间)
  • Direct Memory(直接内存)
  • Thread Stack(线程栈)
  • JVM自身开销
  • OS Buffer

而K8s只认一件事:总和 > memory limit → 立即OOMKilled
没有任何商量余地,没有Swap可以缓冲,超了就是死。

四、六轮治理,把“玄学”变成“科学”

第一轮:重构资源与JVM参数

最关键的一步:抛弃固定Xmx

# 删除这行祖传代码
# -Xms2g -Xmx2g

# 改为容器感知模式
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=70

设计逻辑很简单:

  • 容器limit = 2Gi
  • 堆内存 ≈ 1.4Gi(70%)
  • 剩下600M给非堆和OS
    这才是“容器优先”的JVM设计思路。

重新定义资源策略(基于监控数据,不是拍脑袋):

requests:
  cpu: "800m"      # 长期占用
  memory: "2Gi"    # 稳态内存≈1.6Gi
limits:
  cpu: "2"         # 允许突发
  memory: "2.5Gi"  # 非堆波动≈300-400M

第二轮:让OOM不再是“黑盒”

强制留下案发现场:

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

原则:线上OOM,必须有Dump文件。 没有Dump的OOM排查,本质就是猜谜。

让JVM学会“体面地死”:

-XX:+ExitOnOutOfMemoryError

这一步是为K8s设计的:JVM出问题,Pod立刻退出,K8s自动拉起新实例。这才是平台化自愈的基础,而不是让一个半死不活的Pod在那硬撑。

第三轮:把“假死”交给平台处理

探针职责重新划分:

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
  initialDelaySeconds: 30
  periodSeconds: 5

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
  initialDelaySeconds: 60
  periodSeconds: 15

startupProbe:
  httpGet:
    path: /actuator/health/startup
  failureThreshold: 30
  periodSeconds: 10

每个探针只干一件事:

  • Readiness:只判断是否接流量
  • Liveness:只判断是否需要重启
  • Startup:给慢启动应用留足时间(Java你懂的)

真正的优雅停机:

spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 20"]

配合Spring Boot配置:

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

第四轮:从“能看到”到“能预测”

通过Micrometer暴露JVM指标:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

监控这些关键指标:

  • Heap / Non-Heap使用趋势
  • GC次数与耗时(分代统计)
  • 线程数变化
  • 类加载/卸载数量

效果:

  • OOM不再是突发,内存泄漏有趋势可循
  • GC抖动能提前预警
  • 线程泄漏看得见

第五轮:重新设计扩缩容策略

为什么HPA只看CPU一定会踩坑?
在Java场景下:

  • GC抖动
  • 内存压力
  • 线程阻塞
    往往不会直接反映在CPU上

我们的最终策略:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Pods
    pods:
      metric:
        name: jvm_gc_pause_seconds_count
      target:
        type: AverageValue
        averageValue: "5"
  - type: Pods
    pods:
      metric:
        name: http_server_requests_seconds_count
      target:
        type: AverageValue
        averageValue: "100"

组合指标:CPU + GC频率 + QPS,多维度判断。

第六轮:建设稳定性文化

最后这一轮最虚,但也最重要。

建立三个习惯:

  1. 任何OOM必须有根因分析,不能“重启就好”
  2. 每次发布前Review监控基线,看资源趋势是否异常
  3. 定期压测,验证极限情况下的表现

几个关键认知转变:
✅ JVM必须是“容器感知的JVM”
✅ limit不是JVM的可用内存,而是留给整个Pod的
✅ OOM是系统设计问题,不是偶发异常
✅ K8s负责重启,JVM负责体面退出
✅ 稳定性来自长期治理,不是一次调优

五、三年后的今天

这套治理体系跑下来:

  • OOM从“每周偶发” → “一年可能都没有一次”
  • 滚动升级502 → 彻底消失
  • 资源使用率下降20%+(因为不再盲目加副本)
  • 最重要的:再没有被OOMKilled半夜叫醒过

真正的成就感不是参数调得多完美,而是当你看到凌晨3点的手机,只有一条“发布成功”的通知,没有连环报警。

写在最后

Java上K8s,技术门槛其实不高,但治理门槛很高。它考验的不是你会不会写YAML,而是你能不能把JVM和容器这两个完全不同的设计哲学统一起来。

这套方法论,我们踩了三年坑才总结出来。希望能帮你少踩几个,早日睡个安稳觉。

你的Java服务在Kubernetes上遇到过什么奇葩问题?欢迎在云栈社区的运维板块一起交流探讨,共同避坑!




上一篇:智谱 GLM-5 技术报告解读:开源大模型如何攻克长上下文与 Agent 训练难题
下一篇:解析等级保护制度:揭秘等级保护、分级保护与密码管理的从属关系
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-25 18:34 , Processed in 0.460817 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表