在 Kubernetes 中限制 Pod占用的最大内存资源,有两种方式实现,基于pod级的以及基于命名空间级的
一、两种配置方式对比
image.png
二、创建Helloworld项目
2.1 创建java项目
新建一个普通的helloworld项目,在控制器中添加如下代码
@GetMapping("/memory")
public String memory() {
try {
int gigabytes = 2;
long startTime = System.currentTimeMillis();
// 分配指定大小的内存
allocateMemory(gigabytes);
long endTime = System.currentTimeMillis();
System.out.printf("成功分配 %d GB 内存,耗时 %.2f 秒%n",
gigabytes, (endTime - startTime) / 1000.0);
} catch (Exception e) {
e.printStackTrace();
}finally {
return "success";
}
}
/**
* 分配指定GB大小的内存
*/
public static void allocateMemory(int gigabytes) {
final int CHUNK_SIZE = 100 * 1024 * 1024; // 每次分配100MB
long totalBytes = (long) gigabytes * 1024 * 1024 * 1024;
int chunks = (int) (totalBytes / CHUNK_SIZE);
List<byte[]> memoryChunks = new ArrayList<>();
// 分段分配内存,避免一次性分配过大导致OOM
for (int i = 0; i < chunks; i++) {
byte[] chunk = new byte[CHUNK_SIZE];
// 填充数据确保内存被实际使用
for (int j = 0; j < CHUNK_SIZE; j += 4096) {
chunk[j] = 42;
}
memoryChunks.add(chunk);
// 打印进度
if ((i + 1) % 10 == 0) {
System.out.printf("已分配 %.2f GB 内存%n", (i + 1) * 0.1);
}
}
// 处理剩余的不足CHUNK_SIZE的部分
long remainingBytes = totalBytes % CHUNK_SIZE;
if (remainingBytes > 0) {
byte[] chunk = new byte[(int) remainingBytes];
for (int j = 0; j < remainingBytes; j += 4096) {
chunk[j] = 42;
}
memoryChunks.add(chunk);
}
}
说明:程序中添加了一个接口:/memory ,每次调用就会占用系统2G内存空间
2.2 准备部署文档
$ cat helloworld.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: helloworld
name: helloworld
namespace: dc-prod-ns
spec:
replicas: 1
selector:
matchLabels:
k8s-app: helloworld
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
k8s-app: helloworld
spec:
containers:
- env:
- name: APP_OPTIONS
value: '-Xms8192m -Xmx8192m -Xss1024k'
envFrom:
- configMapRef:
name: dc-appvar
image: helloworld:202507010334
imagePullPolicy: Never
name: helloworld
ports:
- containerPort: 8080
protocol: TCP
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: helloworld
name: helloworld
namespace: dc-prod-ns
spec:
ports:
- name: http-8080
nodePort: 31510
port: 31510
protocol: TCP
targetPort: 8080
selector:
k8s-app: helloworld
type: NodePort
三、创建建LimitRange
$ cat mem-limit.yml
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
namespace: dc-prod-ns
spec:
limits:
- type: Container
max:
memory: "4Gi" # 容器最大内存限制
min:
memory: "100Mi" # 容器最小内存限制
defaultRequest:
memory: "1Gi" # 将默认请求值改为 1Gi
$ kubectl apply -f mem-limit.yml
$ kubectl get limits -n dc-prod-ns
NAME CREATED AT
mem-limit-range 2025-07-01T02:10:48Z
$ kc describe limits mem-limit-range -n dc-prod-ns
Name: mem-limit-range
Namespace: dc-prod-ns
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Container memory 100Mi 4Gi 1Gi 4Gi -
四、LimitRange VS JAVA启动参数
4.1 Xmx > LimitRange-Max
说明:从步骤2.2中可以看到,这里传入了java程序启动的环境变量值:-Xms8192m -Xmx8192m -Xss1024k
表明java程序初始与最大堆内存都是8G > LimitRange中的Max限制:4G
现在查看pod状态
$ kubectl apply -f helloworld.yml
$ kubectl top pods -n dc-prod-ns | grep hello # 启动后占用397Mi
helloworld-6f84dd79d4-vhdzn 3m 397Mi
$ curl http://192.168.xx.xx:31510/memory # K8S任意节点IP
success
# 稍等一会儿
$ kubectl top pods -n dc-prod-ns | grep hello
helloworld-6f84dd79d4-rt9gn 162m 3983Mi
$ curl http://192.168.xx.xx:31510/memory # 再请求一次
curl: (52) Empty reply from server # 请求失败
$ kubectl get pods -n dc-prod-ns | grep hellow
helloworld-6f84dd79d4-rt9gn 1/1 Running 2 8m24s
$ kubectl logs helloworld-6f84dd79d4-rt9gn -n dc-prod-ns
......
./run.sh: line 6: 22 Killed java $APP_OPTIONS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djava.security.egd=file:/dev/./urandom -jar /app.jar
# 此时可以看到pod因为oom被kill了。
# 因此,由于此时探针无效,pod就被重启了
$ kg events -n dc-prod-ns
......
13m Normal Killing pod/helloworld-6f84dd79d4-vhdzn Stopping container helloworld
13m Normal SuccessfulCreate replicaset/helloworld-6f84dd79d4 Created pod: helloworld-6f84dd7
从上面实验可以看出,当Xmx较大时:
- 不影响pod创建,因为java启动时并没有立即实际占用这么大的内存
- 但是当程序占用内存超过命名空间的limits-Max值时,pod就会OOM
4.2 Xmx <= LimitRange-Max
修改java启动参数为:-Xms4096m -Xmx4096m -Xss1024k
此时 Xmx <= limit-Max
$ kubectl top pods -n dc-prod-ns | grep hello # 刚启动后占用387M内存
helloworld-6f84dd79d4-rt9gn 2m 387Mi
$ curl http://192.168.xx.xx:31510/memory
success
# 稍等一会儿
$ kubectl top pods -n dc-prod-ns | grep hello
helloworld-5dd89d9f68-qpfmw 3m 3094Mi
# 此后多次重复请求 http://192.168.xx.xx:31510/memory,发现pod占用内存在到达3996Mi后不再增长,且也未发生oom
可以看到,此时Xmx有效地抑制了程序对于内存的占用
因此,在pod中定义的java启动参数,Xmx 一定不能大于LimitRange中定义的Max值,否则就有可能发生oom
五、Pod中显式配置Limits
5.1 pod中的limits比LimitRange中定义的小
修改helloworld的部署文件,添加limits.memory 部分
$ cat helloworld.yml
image: helloworld:202507010334
resources:
limits:
memory: "512Mi" 限制最大内存为 512 MiB
......
$ kubectl apply -f helloworld.yml
deployment.apps/helloworld configured
service/helloworld unchanged
$ kubectl top pods -n dc-prod-ns | grep hello
helloworld-b799bbcd4-tsrzw 844m 379Mi
$ curl http://192.168.xx.xx:31510/memory # 只调用一次就被killed了
curl: (52) Empty reply from server
因为在pod中定义的limits.memory比较小,因此很快就触发了oom
5.2 Pod中的limits比LimitRange中定义的大
$ cat helloworld.yml
image: helloworld:202507010334
resources:
limits:
memory: "5Gi" 限制最大内存为 512 MiB
......
$ kubectl get events -n dc-prod-ns
Error creating: pods "helloworld-55f7b48d5c-zgqd8" is forbidden: maximum memory usage per Container is 4Gi, but limit is 5Gi
可以看到,若Pod中的limits比LimitRange中定义的大,此时会无法创建pod
也即:个体要服从全局
六、总结
- 使用LimitRange来统一定义资源限定比较简洁
- 在pod部署文件中定义java启动参数时,Xmx 一定不能大于LimitRange中定义的Max值,否则就有可能发生oom
- 如果要实现精确的资源请求与限制,可以在pod部署文件中指定,但是要注意limit不能超过LimitRange中对应值