K8s Pod频繁被杀,排查发现是资源限制的这个坑
服务部署在K8s上,运行一段时间后Pod就会重启。看日志没有异常,但Pod状态显示OOMKilled。
一开始以为是代码内存泄漏,排查了一周,最后发现是K8s资源配置的问题。
问题现象
监控告警:Pod重启次数过多
kubectl get pods
NAME READY STATUS RESTARTS AGE
order-service-5d4f6c7b8-abc 1/1 Running 15 2d
2天重启了15次。
查看Pod详情:
kubectl describe pod order-service-5d4f6c7b8-abc
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
OOMKilled + Exit Code 137 = 被系统因为内存超限杀掉了。
排查过程
Step 1:先看资源配置
kubectl get pod order-service-5d4f6c7b8-abc -o yaml | grep -A 10 resources
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: "1"
memory: 1Gi
配置了limits.memory = 2Gi。
Step 2:看实际内存使用
kubectl top pod order-service-5d4f6c7b8-abc
NAME CPU(cores) MEMORY(bytes)
order-service-5d4f6c7b8-abc 500m 1950Mi
内存用了1950Mi,接近2Gi的限制了。
Step 3:进容器看详情
kubectl exec -it order-service-5d4f6c7b8-abc -- /bin/sh
# 查看容器看到的内存限制
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
# 2147483648 (2GB)
# 查看当前使用
cat /sys/fs/cgroup/memory/memory.usage_in_bytes
# 1900000000+ (约1.9GB)
确实快到上限了。
Step 4:分析JVM内存
这是个Java服务,看看JVM配置:
# 查看JVM参数
ps aux | grep java
java -Xms1g -Xmx2g -jar app.jar
问题来了:JVM的-Xmx设成了2G,和容器limits一样大!
问题根因
Java在容器中的内存计算
容器的内存限制 ≠ 只给JVM用的内存
容器总内存 = JVM堆内存 + JVM非堆内存 + 操作系统开销
具体来说:
- 堆内存(-Xmx)
- Metaspace
- 线程栈(每个线程1MB左右)
- 直接内存(DirectByteBuffer)
- JNI
- GC开销
- 容器内其他进程
如果-Xmx=2G,container limit也是2G,那堆刚满的时候,加上其他内存,总量就超过2G了,触发OOMKilled。
数据验证
我们服务的实际内存组成:
| 组成部分 | 大小 | 说明 |
|---|---|---|
| 堆内存(实际使用) | 1.5G | 没到-Xmx上限 |
| Metaspace | 150M | 类加载 |
| 线程栈 | 200M | 约200个线程 |
| 直接内存 | 100M | NIO使用 |
| 其他 | 100M | GC、JNI等 |
| 合计 | 约2G | 超过limit |
堆内存还没满,但总内存已经超限了。
解决方案
方案一:调整limits(推荐)
resources:
limits:
memory: 3Gi # 给足够的余量
requests:
memory: 2Gi
一般建议:limits.memory = Xmx + 500M ~ 1G
方案二:调整JVM参数
# 按容器限制的75%设置堆内存
java -Xms1g -Xmx1536m -jar app.jar
# 或者用容器感知参数(JDK 8u191+)
java -XX:MaxRAMPercentage=75.0 -jar app.jar
MaxRAMPercentage会自动读取容器的内存限制,按比例设置堆大小。
方案三:限制非堆内存
java \
-Xms1g -Xmx1536m \
-XX:MaxMetaspaceSize=256m \
-XX:MaxDirectMemorySize=256m \
-Xss512k \
-jar app.jar
-XX:MaxMetaspaceSize:限制Metaspace-XX:MaxDirectMemorySize:限制直接内存-Xss:减小线程栈大小
最终配置
# deployment.yaml
resources:
limits:
cpu: "2"
memory: 2560Mi # 2.5G
requests:
cpu: "1"
memory: 2Gi
# JVM参数
java \
-XX:MaxRAMPercentage=75.0 \
-XX:InitialRAMPercentage=50.0 \
-XX:MaxMetaspaceSize=256m \
-jar app.jar
改完后再也没重启过。
几个相关的坑
坑1:requests和limits差太多
# 不推荐
requests:
memory: 512Mi
limits:
memory: 4Gi
requests太小会被调度到资源紧张的节点,然后因为实际用量超过节点剩余资源被OOM。
建议:requests设成实际使用量,limits设成峰值+余量。
坑2:不设limits
# 危险
resources:
requests:
memory: 1Gi
# 没有limits
不设limits意味着可以无限使用,可能把节点撑爆,影响其他Pod。
坑3:老版本JDK不认容器限制
JDK 8u131之前的版本不认识cgroup的内存限制,会读取物理机的内存。
解决:升级到JDK 8u191+或JDK 11+,或手动设置-Xmx。
监控和告警
查看Pod历史事件
kubectl describe pod <pod-name> | grep -A 20 Events
查看节点内存压力
kubectl describe node <node-name> | grep -A 5 Conditions
Prometheus监控
# 告警规则
- alert: PodOOMKilled
expr: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
for: 0m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} OOMKilled"
远程排查
线上K8s集群通常在内网,出问题需要VPN或跳板机。
我用组网工具提前把笔记本和跳板机组好,在外面也能快速kubectl连上去看情况。比每次找运维开VPN快多了。
排查命令总结
# 查看Pod状态和重启次数
kubectl get pods
# 查看重启原因
kubectl describe pod <pod-name>
# 查看实时资源使用
kubectl top pod <pod-name>
# 进入容器看cgroup限制
kubectl exec -it <pod-name> -- cat /sys/fs/cgroup/memory/memory.limit_in_bytes
# 查看JVM内存(Java容器)
kubectl exec -it <pod-name> -- jcmd 1 VM.native_memory summary
# 查看OOMKilled事件
kubectl get events --field-selector reason=OOMKilling
总结
| 场景 | 配置建议 |
|---|---|
| Java服务 | limits = Xmx + 500M~1G |
| 推荐做法 | 用MaxRAMPercentage=75% |
| requests | 设成实际使用量 |
| limits | 设成峰值+余量 |
K8s的OOMKilled不一定是代码内存泄漏,很可能是资源配置不合理。先看limits和JVM参数是否匹配。
更多推荐




所有评论(0)