K8S集群内存资源限制实验

在 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中对应值
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容