K8S集群內(nèi)存資源限制實(shí)驗(yàn)

在 Kubernetes 中限制 Pod占用的最大內(nèi)存資源,有兩種方式實(shí)現(xiàn),基于pod級(jí)的以及基于命名空間級(jí)的

一、兩種配置方式對(duì)比

image.png

二、創(chuàng)建Helloworld項(xiàng)目

2.1 創(chuàng)建java項(xiàng)目

新建一個(gè)普通的helloworld項(xiàng)目,在控制器中添加如下代碼

@GetMapping("/memory")
public String memory() {
    try {
        int gigabytes = 2;
        long startTime = System.currentTimeMillis();

        // 分配指定大小的內(nèi)存
        allocateMemory(gigabytes);

        long endTime = System.currentTimeMillis();
        System.out.printf("成功分配 %d GB 內(nèi)存,耗時(shí) %.2f 秒%n",
        gigabytes, (endTime - startTime) / 1000.0);

    } catch (Exception e) {
        e.printStackTrace();
    }finally {
         return "success";
    }
}

    /**
     * 分配指定GB大小的內(nèi)存
     */
    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<>();

        // 分段分配內(nèi)存,避免一次性分配過(guò)大導(dǎo)致OOM
        for (int i = 0; i < chunks; i++) {
            byte[] chunk = new byte[CHUNK_SIZE];

            // 填充數(shù)據(jù)確保內(nèi)存被實(shí)際使用
            for (int j = 0; j < CHUNK_SIZE; j += 4096) {
                chunk[j] = 42;
            }

            memoryChunks.add(chunk);

            // 打印進(jìn)度
            if ((i + 1) % 10 == 0) {
                System.out.printf("已分配 %.2f GB 內(nèi)存%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);
        }

    }

說(shuō)明:程序中添加了一個(gè)接口:/memory ,每次調(diào)用就會(huì)占用系統(tǒng)2G內(nèi)存空間

2.2 準(zhǔn)備部署文檔

$ 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

三、創(chuàng)建建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"      # 容器最大內(nèi)存限制
      min:
        memory: "100Mi"    # 容器最小內(nèi)存限制
      defaultRequest:  
        memory: "1Gi"      # 將默認(rèn)請(qǐng)求值改為 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啟動(dòng)參數(shù)

4.1 Xmx > LimitRange-Max

說(shuō)明:從步驟2.2中可以看到,這里傳入了java程序啟動(dòng)的環(huán)境變量值:-Xms8192m -Xmx8192m -Xss1024k

表明java程序初始與最大堆內(nèi)存都是8G > LimitRange中的Max限制:4G

現(xiàn)在查看pod狀態(tài)

$ kubectl apply -f helloworld.yml
$ kubectl  top pods -n dc-prod-ns | grep hello   # 啟動(dòng)后占用397Mi
helloworld-6f84dd79d4-vhdzn                             3m           397Mi
$ curl http://192.168.xx.xx:31510/memory        # K8S任意節(jié)點(diǎn)IP
success
# 稍等一會(huì)兒
$ kubectl  top pods -n dc-prod-ns | grep hello
helloworld-6f84dd79d4-rt9gn                             162m         3983Mi
$ curl http://192.168.xx.xx:31510/memory   # 再請(qǐng)求一次
curl: (52) Empty reply from server                  # 請(qǐng)求失敗
$ 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
# 此時(shí)可以看到pod因?yàn)閛om被kill了。
# 因此,由于此時(shí)探針無(wú)效,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

從上面實(shí)驗(yàn)可以看出,當(dāng)Xmx較大時(shí):

  • 不影響pod創(chuàng)建,因?yàn)閖ava啟動(dòng)時(shí)并沒有立即實(shí)際占用這么大的內(nèi)存
  • 但是當(dāng)程序占用內(nèi)存超過(guò)命名空間的limits-Max值時(shí),pod就會(huì)OOM

4.2 Xmx <= LimitRange-Max

修改java啟動(dòng)參數(shù)為:-Xms4096m -Xmx4096m -Xss1024k

此時(shí) Xmx <= limit-Max

$ kubectl  top pods -n dc-prod-ns | grep hello   # 剛啟動(dòng)后占用387M內(nèi)存
helloworld-6f84dd79d4-rt9gn                             2m           387Mi
$ curl http://192.168.xx.xx:31510/memory
success
# 稍等一會(huì)兒
$ kubectl  top pods -n dc-prod-ns | grep hello
helloworld-5dd89d9f68-qpfmw                             3m           3094Mi
# 此后多次重復(fù)請(qǐng)求 http://192.168.xx.xx:31510/memory,發(fā)現(xiàn)pod占用內(nèi)存在到達(dá)3996Mi后不再增長(zhǎng),且也未發(fā)生oom

可以看到,此時(shí)Xmx有效地抑制了程序?qū)τ趦?nèi)存的占用

因此,在pod中定義的java啟動(dòng)參數(shù),Xmx 一定不能大于LimitRange中定義的Max值,否則就有可能發(fā)生oom

五、Pod中顯式配置Limits

5.1 pod中的limits比LimitRange中定義的小

修改helloworld的部署文件,添加limits.memory 部分

$ cat helloworld.yml
image: helloworld:202507010334
resources:
    limits:
      memory: "512Mi"    限制最大內(nèi)存為 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    # 只調(diào)用一次就被killed了
curl: (52) Empty reply from server

因?yàn)樵趐od中定義的limits.memory比較小,因此很快就觸發(fā)了oom

5.2 Pod中的limits比LimitRange中定義的大

$ cat helloworld.yml
image: helloworld:202507010334
resources:
    limits:
      memory: "5Gi"    限制最大內(nèi)存為 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中定義的大,此時(shí)會(huì)無(wú)法創(chuàng)建pod

也即:個(gè)體要服從全局

六、總結(jié)

  • 使用LimitRange來(lái)統(tǒng)一定義資源限定比較簡(jiǎn)潔
  • 在pod部署文件中定義java啟動(dòng)參數(shù)時(shí),Xmx 一定不能大于LimitRange中定義的Max值,否則就有可能發(fā)生oom
  • 如果要實(shí)現(xiàn)精確的資源請(qǐng)求與限制,可以在pod部署文件中指定,但是要注意limit不能超過(guò)LimitRange中對(duì)應(yīng)值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容