當前位置:
首頁 >
Kubernetes之路 1 - Java应用资源限制的迷思
發(fā)布時間:2025/7/14
56
豆豆
生活随笔
收集整理的這篇文章主要介紹了
Kubernetes之路 1 - Java应用资源限制的迷思
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
摘要: 隨著容器技術(shù)的成熟,越來越多的企業(yè)客戶在企業(yè)中選擇Docker和Kubernetes作為應(yīng)用平臺的基礎(chǔ)。然而在實踐過程中,還會遇到很多具體問題。本文分析并解決了Java應(yīng)用在容器使用過程中關(guān)于Heap大小設(shè)置的一個常見問題。
隨著容器技術(shù)的成熟,越來越多的企業(yè)客戶在企業(yè)中選擇Docker和Kubernetes作為應(yīng)用平臺的基礎(chǔ)。然而在實踐過程中,還會遇到很多具體問題。本系列文章會記錄阿里云容器服務(wù)團隊在支持客戶中的一些心得體會和最佳實踐。我們也歡迎您通過郵件和釘釘群和我們聯(lián)系,分享您的思路和遇到的問題。
問題
有些同學反映:自己設(shè)置了容器的資源限制,但是Java應(yīng)用容器在運行中還是會莫名奇妙地被OOM Killer干掉。
這背后一個非常常見的原因是:沒有正確設(shè)置容器的資源限制以及對應(yīng)的JVM的堆空間大小。我們拿一個tomcat應(yīng)用為例,其實例代碼和Kubernetes部署文件可以從Github中獲得。
git clone https://github.com/denverdino/system-info cd system-info`
下面是一個Kubernetes的Pod的定義描述:1.Pod中的app是一個初始化容器,負責把一個JSP應(yīng)用拷貝到 tomcat 容器的 “webapps”目錄下。注: 鏡像中JSP應(yīng)用index.jsp用于顯示JVM和系統(tǒng)資源信息。 2.tomcat 容器會保持運行,而且我們限制了容器最大的內(nèi)存用量為256MB內(nèi)存。apiVersion: v1 kind: Pod metadata: name: test spec: initContainers: - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info name: app imagePullPolicy: IfNotPresent command: - "cp" - "-r" - "/system-info" - "/app" volumeMounts: - mountPath: /app name: app-volume containers: - image: tomcat:9-jre8 name: tomcat imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /usr/local/tomcat/webapps name: app-volume ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "256Mi" cpu: "500m" volumes: - name: app-volume emptyDir: {} 我們執(zhí)行如下命令來部署、測試應(yīng)用
$ kubectl create -f test.yaml pod "test" created $ kubectl get pods test NAME READY STATUS RESTARTS AGE test 1/1 Running 0 28s $ kubectl exec test curl http://localhost:8080/system-info/ ... 我們可以看到HTML格式的系統(tǒng)CPU/Memory等信息,我們也可以用 html2text 命令將其轉(zhuǎn)化成為文本格式。注意:本文是在一個 2C 4G的節(jié)點上進行的測試,在不同環(huán)境中測試輸出的結(jié)果會有所不同
$ kubectl exec test curl http://localhost:8080/system-info/ | html2text Java version Oracle Corporation 1.8.0_162 Operating system Linux 4.9.64 Server Apache Tomcat/9.0.6 Memory Used 29 of 57 MB, Max 878 MB Physica Memory 3951 MB CPU Cores 2 **** Memory MXBean **** Heap Memory Usage init = 65011712(63488K) used = 19873704(19407K) committed = 65536000(64000K) max = 921174016(899584K) Non-Heap Memory Usage init = 2555904(2496K) used = 32944912(32172K) committed = 33882112(33088K) max = -1(-1K)
我們可以發(fā)現(xiàn),容器中看到的系統(tǒng)內(nèi)存是 3951MB,而JVM Heap Size最大是 878MB。納尼?!我們不是設(shè)置容器資源的容量為256MB了嗎?如果這樣,當應(yīng)用內(nèi)存的用量超出了256MB,JVM還沒對其進行GC,而JVM進程就會被系統(tǒng)直接OOM干掉了。問題的根源在于:
了解了問題的根源,我們就可以非常簡單地解決問題了
解決思路
開啟CGroup資源感知
Java社區(qū)也關(guān)注到這個問題,并在JavaSE8u131+和JDK9 支持了對容器資源限制的自動感知能力 blogs.oracle.com/java-platfo…
其用法就是添加如下參數(shù)
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap … 我們在上文示例的tomcat容器添加環(huán)境變量 “JAVA_OPTS”參數(shù)apiVersion: v1 kind: Pod metadata: name: cgrouptest spec: initContainers: - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info name: app imagePullPolicy: IfNotPresent command: - "cp" - "-r" - "/system-info" - "/app" volumeMounts: - mountPath: /app name: app-volume containers: - image: tomcat:9-jre8 name: tomcat imagePullPolicy: IfNotPresent env: - name: JAVA_OPTS value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" volumeMounts: - mountPath: /usr/local/tomcat/webapps name: app-volume ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "256Mi" cpu: "500m" volumes: - name: app-volume emptyDir: {}
我們部署一個新的Pod,并重復相應(yīng)的測試
$ kubectl create -f cgroup_test.yaml pod "cgrouptest" created $ kubectl exec cgrouptest curl http://localhost:8080/system-info/ | html2txt Java version Oracle Corporation 1.8.0_162 Operating system Linux 4.9.64 Server Apache Tomcat/9.0.6 Memory Used 23 of 44 MB, Max 112 MB Physica Memory 3951 MB CPU Cores 2 **** Memory MXBean **** Heap Memory Usage init = 8388608(8192K) used = 25280928(24688K) committed = 46661632(45568K) max = 117440512(114688K) Non-Heap Memory Usage init = 2555904(2496K) used = 31970840(31221K) committed = 32768000(32000K) max = -1(-1K) 我們看到JVM最大的Heap大小變成了112MB,這很不錯,這樣就能保證我們的應(yīng)用不會輕易被OOM了。隨后問題又來了,為什么我們設(shè)置了容器最大內(nèi)存限制是256MB,而JVM只給Heap設(shè)置了112MB的最大值呢?
這就涉及到JVM的內(nèi)存管理的細節(jié)了,JVM中的內(nèi)存消耗包含Heap和Non-Heap兩類;類似Class的元信息,JIT編譯過的代碼,線程堆棧(thread stack),GC需要的內(nèi)存空間等都屬于Non-Heap內(nèi)存,所以JVM還會根據(jù)CGroup的資源限制預留出部分內(nèi)存給Non Heap,來保障系統(tǒng)的穩(wěn)定。(在上面的示例中我們可以看到,tomcat啟動后Non Heap占用了近32MB的內(nèi)存)在最新的JDK 10中,又對JVM在容器中運行做了進一步的優(yōu)化和增強。
容器內(nèi)部感知CGroup資源限制
如果無法利用JDK 8/9的新特性,比如還在使用JDK6的老應(yīng)用,我們還可以在容器內(nèi)部利用腳本來獲取容器的CGroup資源限制,并通過設(shè)置JVM的Heap大小。
Docker1.7開始將容器cgroup信息掛載到容器中,所以應(yīng)用可以從 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件獲取內(nèi)存、 CPU等設(shè)置,在容器的應(yīng)用啟動命令中根據(jù)Cgroup配置正確的資源設(shè)置 -Xmx, -XX:ParallelGCThreads等參數(shù) 在 yq.aliyun.com/articles/18… 一文中已經(jīng)有相應(yīng)的示例和代碼,本文不再贅述
總結(jié)
本文分析了Java應(yīng)用在容器使用中一個常見Heap設(shè)置的問題。容器與虛擬機不同,其資源限制通過CGroup來實現(xiàn)。而容器內(nèi)部進程如果不感知CGroup的限制,就進行內(nèi)存、CPU分配可能導致資源沖突和問題。
我們可以非常簡單地利用JVM的新特性和自定義腳本來正確設(shè)置資源限制。這個可以解決絕大多數(shù)資源限制的問題。
關(guān)于容器應(yīng)用中資源限制還有一類問題是,一些比較老的監(jiān)控工具或者free/top等系統(tǒng)命令,在容器中運行時依然會獲取到宿主機的CPU和內(nèi)存,這導致了一些監(jiān)控工具在容器中運行時無法正常計算資源消耗。社區(qū)中常見的做法是利用 lxcfs 來讓容器在資源可見性的行為和虛機保持一致,后續(xù)文章會介紹其在Kubernetes上的使用方案。
阿里云Kubernetes服務(wù) 全球首批通過Kubernetes一致性認證,簡化了Kubernetes集群生命周期管理,內(nèi)置了與阿里云產(chǎn)品集成,也將進一步簡化Kubernetes的開發(fā)者體驗,幫助用戶關(guān)注云端應(yīng)用價值創(chuàng)新。
原文鏈接干貨好文,請關(guān)注掃描以下二維碼:
隨著容器技術(shù)的成熟,越來越多的企業(yè)客戶在企業(yè)中選擇Docker和Kubernetes作為應(yīng)用平臺的基礎(chǔ)。然而在實踐過程中,還會遇到很多具體問題。本系列文章會記錄阿里云容器服務(wù)團隊在支持客戶中的一些心得體會和最佳實踐。我們也歡迎您通過郵件和釘釘群和我們聯(lián)系,分享您的思路和遇到的問題。
問題
有些同學反映:自己設(shè)置了容器的資源限制,但是Java應(yīng)用容器在運行中還是會莫名奇妙地被OOM Killer干掉。
這背后一個非常常見的原因是:沒有正確設(shè)置容器的資源限制以及對應(yīng)的JVM的堆空間大小。我們拿一個tomcat應(yīng)用為例,其實例代碼和Kubernetes部署文件可以從Github中獲得。
git clone https://github.com/denverdino/system-info cd system-info`
下面是一個Kubernetes的Pod的定義描述:
$ kubectl create -f test.yaml pod "test" created $ kubectl get pods test NAME READY STATUS RESTARTS AGE test 1/1 Running 0 28s $ kubectl exec test curl http://localhost:8080/system-info/ ... 我們可以看到HTML格式的系統(tǒng)CPU/Memory等信息,我們也可以用 html2text 命令將其轉(zhuǎn)化成為文本格式。注意:本文是在一個 2C 4G的節(jié)點上進行的測試,在不同環(huán)境中測試輸出的結(jié)果會有所不同
$ kubectl exec test curl http://localhost:8080/system-info/ | html2text Java version Oracle Corporation 1.8.0_162 Operating system Linux 4.9.64 Server Apache Tomcat/9.0.6 Memory Used 29 of 57 MB, Max 878 MB Physica Memory 3951 MB CPU Cores 2 **** Memory MXBean **** Heap Memory Usage init = 65011712(63488K) used = 19873704(19407K) committed = 65536000(64000K) max = 921174016(899584K) Non-Heap Memory Usage init = 2555904(2496K) used = 32944912(32172K) committed = 33882112(33088K) max = -1(-1K)
我們可以發(fā)現(xiàn),容器中看到的系統(tǒng)內(nèi)存是 3951MB,而JVM Heap Size最大是 878MB。納尼?!我們不是設(shè)置容器資源的容量為256MB了嗎?如果這樣,當應(yīng)用內(nèi)存的用量超出了256MB,JVM還沒對其進行GC,而JVM進程就會被系統(tǒng)直接OOM干掉了。問題的根源在于:
- 對于JVM而言,如果沒有設(shè)置Heap Size,就會按照宿主機環(huán)境的內(nèi)存大小缺省設(shè)置自己的最大堆大小。
- Docker容器利用CGroup對進程使用的資源進行限制,而在容器中的JVM依然會利用宿主機環(huán)境的內(nèi)存大小和CPU核數(shù)進行缺省設(shè)置,這導致了JVM Heap的錯誤計算。
了解了問題的根源,我們就可以非常簡單地解決問題了
解決思路
開啟CGroup資源感知
Java社區(qū)也關(guān)注到這個問題,并在JavaSE8u131+和JDK9 支持了對容器資源限制的自動感知能力 blogs.oracle.com/java-platfo…
其用法就是添加如下參數(shù)
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap … 我們在上文示例的tomcat容器添加環(huán)境變量 “JAVA_OPTS”參數(shù)apiVersion: v1 kind: Pod metadata: name: cgrouptest spec: initContainers: - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info name: app imagePullPolicy: IfNotPresent command: - "cp" - "-r" - "/system-info" - "/app" volumeMounts: - mountPath: /app name: app-volume containers: - image: tomcat:9-jre8 name: tomcat imagePullPolicy: IfNotPresent env: - name: JAVA_OPTS value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" volumeMounts: - mountPath: /usr/local/tomcat/webapps name: app-volume ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "256Mi" cpu: "500m" volumes: - name: app-volume emptyDir: {}
我們部署一個新的Pod,并重復相應(yīng)的測試
$ kubectl create -f cgroup_test.yaml pod "cgrouptest" created $ kubectl exec cgrouptest curl http://localhost:8080/system-info/ | html2txt Java version Oracle Corporation 1.8.0_162 Operating system Linux 4.9.64 Server Apache Tomcat/9.0.6 Memory Used 23 of 44 MB, Max 112 MB Physica Memory 3951 MB CPU Cores 2 **** Memory MXBean **** Heap Memory Usage init = 8388608(8192K) used = 25280928(24688K) committed = 46661632(45568K) max = 117440512(114688K) Non-Heap Memory Usage init = 2555904(2496K) used = 31970840(31221K) committed = 32768000(32000K) max = -1(-1K) 我們看到JVM最大的Heap大小變成了112MB,這很不錯,這樣就能保證我們的應(yīng)用不會輕易被OOM了。隨后問題又來了,為什么我們設(shè)置了容器最大內(nèi)存限制是256MB,而JVM只給Heap設(shè)置了112MB的最大值呢?
這就涉及到JVM的內(nèi)存管理的細節(jié)了,JVM中的內(nèi)存消耗包含Heap和Non-Heap兩類;類似Class的元信息,JIT編譯過的代碼,線程堆棧(thread stack),GC需要的內(nèi)存空間等都屬于Non-Heap內(nèi)存,所以JVM還會根據(jù)CGroup的資源限制預留出部分內(nèi)存給Non Heap,來保障系統(tǒng)的穩(wěn)定。(在上面的示例中我們可以看到,tomcat啟動后Non Heap占用了近32MB的內(nèi)存)在最新的JDK 10中,又對JVM在容器中運行做了進一步的優(yōu)化和增強。
容器內(nèi)部感知CGroup資源限制
如果無法利用JDK 8/9的新特性,比如還在使用JDK6的老應(yīng)用,我們還可以在容器內(nèi)部利用腳本來獲取容器的CGroup資源限制,并通過設(shè)置JVM的Heap大小。
Docker1.7開始將容器cgroup信息掛載到容器中,所以應(yīng)用可以從 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件獲取內(nèi)存、 CPU等設(shè)置,在容器的應(yīng)用啟動命令中根據(jù)Cgroup配置正確的資源設(shè)置 -Xmx, -XX:ParallelGCThreads等參數(shù) 在 yq.aliyun.com/articles/18… 一文中已經(jīng)有相應(yīng)的示例和代碼,本文不再贅述
總結(jié)
本文分析了Java應(yīng)用在容器使用中一個常見Heap設(shè)置的問題。容器與虛擬機不同,其資源限制通過CGroup來實現(xiàn)。而容器內(nèi)部進程如果不感知CGroup的限制,就進行內(nèi)存、CPU分配可能導致資源沖突和問題。
我們可以非常簡單地利用JVM的新特性和自定義腳本來正確設(shè)置資源限制。這個可以解決絕大多數(shù)資源限制的問題。
關(guān)于容器應(yīng)用中資源限制還有一類問題是,一些比較老的監(jiān)控工具或者free/top等系統(tǒng)命令,在容器中運行時依然會獲取到宿主機的CPU和內(nèi)存,這導致了一些監(jiān)控工具在容器中運行時無法正常計算資源消耗。社區(qū)中常見的做法是利用 lxcfs 來讓容器在資源可見性的行為和虛機保持一致,后續(xù)文章會介紹其在Kubernetes上的使用方案。
阿里云Kubernetes服務(wù) 全球首批通過Kubernetes一致性認證,簡化了Kubernetes集群生命周期管理,內(nèi)置了與阿里云產(chǎn)品集成,也將進一步簡化Kubernetes的開發(fā)者體驗,幫助用戶關(guān)注云端應(yīng)用價值創(chuàng)新。
原文鏈接干貨好文,請關(guān)注掃描以下二維碼:
總結(jié)
以上是生活随笔為你收集整理的Kubernetes之路 1 - Java应用资源限制的迷思的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开发工具 | git、github使用场
- 下一篇: Java面试通关要点汇总集之工程篇参考答