您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“如何在Docker里跑Java”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
背景:眾所周知,當我們執(zhí)行沒有任何調優(yōu)參數(shù)(如“java-jar mypplication-fat.jar”)的 Java 應用程序時,JVM 會自動調整幾個參數(shù),以便在執(zhí)行環(huán)境中具有最佳性能。
但是許多開發(fā)者發(fā)現(xiàn),如果讓 JVM ergonomics (即JVM人體工程學,用于自動選擇和行為調整)對垃圾收集器、堆大小和運行編譯器使用默認設置值,運行在 Linux 容器(docker,rkt,runC,lxcfs 等)中的 Java 進程會與我們的預期表現(xiàn)嚴重不符。
懶人超精簡閱讀版:
a.JVM 做不了內(nèi)存限制,一旦超出資源限制,容器就會出錯
b.即使你多給些內(nèi)存資源,也沒什么卵用,只會錯上加錯
c.解決方案:用 Dockfile 中的環(huán)境變量來定義 JVM 的額外參數(shù)
d.更進一步:使用由 Fabric8 社區(qū)提供的基礎 Docker 鏡像來定義 Java 應用程序,將始終根據(jù)容器調整堆大小
詳細全文:
我們往往把容器當虛擬機,讓它定義一些虛擬 CPU 和虛擬內(nèi)存。其實容器更像是一種隔離機制:它可以讓一個進程中的資源(CPU,內(nèi)存,文件系統(tǒng),網(wǎng)絡等)與另一個進程中的資源完全隔離。Linux 內(nèi)核中的 cgroups 功能用于實現(xiàn)這種隔離。
然而,一些從執(zhí)行環(huán)境收集信息的應用程序已經(jīng)在 cgroups 存在之前就被執(zhí)行了?!皌op”,“free”,“ps”,甚至 JVM 等工具都沒有針對在容器內(nèi)執(zhí)行高度受限的 Linux 進程進行優(yōu)化。
1.存在的問題
為了演示,我用“docker-machine create -d virtualbox –virtualbox-memory ‘1024’ docker1024”在1GB RAM 的虛擬機中創(chuàng)建了 docker daemon。接下來,在一個虛擬內(nèi)存為100MB 的容器里面跑三個不同的Linux distribution,執(zhí)行 “free -h”命令,結果是:它們都顯示了995MB 的總內(nèi)存。
即使在 Kubernetes / OpenShift 集群中,結果也類似。
我在一個15GB 內(nèi)存的集群中跑一個 Kubernetes Pod ,并將 Pod 的內(nèi)存限制為512M (通過“kubectl run mycentos –image=centos -it –limits=’memory=512Mi'”命令實現(xiàn)),最后顯示的總內(nèi)存卻是14GB。
如果想知道為什么會發(fā)生這種情況,建議您閱讀博客“Memoryinside Linux containers – Or why don’t free and top work in a Linux container?”(https://fabiokung.com/2014/03/13/memory-inside-linux-containers/)
docker switches(-m,-memory和-memory-swap)和kubernetes switch(–limits)在進程超過限制的情況下,會指示 Linux 內(nèi)核殺死該進程;但 JVM 是完全不知道限制,所以在進程超過限制的時候,糟糕的事情就發(fā)生了!
為了模擬在超過指定的內(nèi)存限制后被殺死的進程,我們可以通過“docker run -it –name mywildfly -m=50m jboss/wildfly” 命令在50MB 內(nèi)存限制的容器中跑WildFly應用 server,用 “dockerstats” 命令來檢查容器限制。
但是在幾秒鐘之后,Wildfly 的容器執(zhí)行將被中斷并顯示:*** JBossAS process (55) received KILL signal ***
“docker inspect mywildfly -f ‘{{json.State}}'” 命令顯示由于 OOM(內(nèi)存不足),該容器已被殺死。注意容器 “state” 中的OOMKilled = true。
2.JAVA的應用程序是如何被影響的?
在docker daemon里用 Dockerfile 中定義的參數(shù)-XX:+ PrintFlagsFinal和-XX:+ PrintGCDetails起一個 java 應用。
其中 machine:1GB RAM 容器內(nèi)存:限制為150M (對于這個Spring Boot應用,似乎夠用)
這些參數(shù)允許我們讀取初始JVM人機工程學參數(shù),并了解有關垃圾收集(GC)執(zhí)行的詳細信息。
動手試一下:
我已經(jīng)在“/ api / memory /”上準備了一個端點,它使用 String 對象加載 JVM 內(nèi)存來模擬消耗大量內(nèi)存的操作。我們來調用一次:
此端點將回復“分配超過80%(219.8 MiB)的最大允許 JVM 內(nèi)存大?。?41.7 MiB)”
在這里我們可以提至少兩個問題:
為什么JVM最大允許內(nèi)存241.7 MiB?
如果這個容器將內(nèi)存限制為150MB,那為什么它允許Java分配近220MB?
首先,我們需要回顧一下 JVM 人機工程學頁面上關于“最大堆大小”的內(nèi)容:是物理內(nèi)存的1/4。由于 JVM 不知道它在一個容器內(nèi)執(zhí)行,所以允許最大堆大小將接近260MB。鑒于我們在容器初始化期間添加了-XX:+ PrintFlagsFinal標志,我們可以檢查這個值:
其次,我們需要了解,當我們在 docker 命令行中使用參數(shù)“-m 150M”時,docker daemon將在RAM中限制150M ,在 Swap 中限制為150M。因此,該過程可以分配300M。這就解釋了為什么我們的進程沒有被殺死。
docker 命令行中的內(nèi)存限制(-memory)和swap(-memory-swap)之間的更多組合可以在這里(https://docs.docker.com/engine/reference/run/#example-run-htop-inside-a-container)找到。
3.提供更多內(nèi)存是否靠譜?
不了解問題的開發(fā)者往往認為環(huán)境不能為執(zhí)行 JVM 提供足夠的內(nèi)存。所以通常的解決辦法是提供更多內(nèi)存,這實際上會使事情變得更糟。
我們假設將 daemon 從1GB 更改為8GB (使用“docker-machinecreate -d virtualbox –virtualbox-memory ‘8192’ docker8192”創(chuàng)建),并將容器內(nèi)存從150M 更改為800M :
請注意這次, “curl http://`docker-machine ipdocker8192`:8080/api/memory” 命令甚至沒有執(zhí)行完,因為在8GB 環(huán)境中計算的 JVM 的MaxHeapSize 為2092957696字節(jié)(?2GB)。檢查 “docker logs mycontainer|grep -i MaxHeapSize”
該應用將嘗試分配超過1.6GB 的內(nèi)存,這超出了此容器的限制(RAM 中的800MB + Swap中的800MB),并且該進程將被殺掉。
很顯然,用增加內(nèi)存且讓 JVM 自定義參數(shù)的方式在容器里跑Java,不是什么好主意。 在容器內(nèi)部運行 Java 應用程序時,我們應該根據(jù)應用程序需求和容器限制設置最大堆大小(-Xmx參數(shù))。
4.解決方案
Dockerfile 的一個細微變化允許用戶指定一個環(huán)境變量來定義 JVM 的額外參數(shù)。 檢查以下行:
現(xiàn)在我們可以使用 JAVA_OPTIONS 環(huán)境變量來通知 JVM 堆的大小。對于這個應用程序,300M 就夠了。稍后可以檢查日志并獲取314572800字節(jié)(300MBi)的值
對于docker,您可以使用“-e”switch指定環(huán)境變量。
在Kubernetes中,您可以使用switch “-env = [key = value]”設置環(huán)境變量:
再進一步
如果可以根據(jù)容器限制自動計算堆的值,該怎么做?
使用由Fabric8社區(qū)提供的基礎Docker鏡像,就可以搞定。這個鏡像 fabric8 / java-jboss-openjdk8-jdk 使用一個腳本來計算容器限制,并使用50%的可用內(nèi)存作為上限。 請注意,這個50%的內(nèi)存比可以被復寫。 您還可以使用此鏡像來啟用/禁用調試,診斷等。
下面一起看看 Dockerfile 是如何作用于這個 Spring Boot 應用程序:
搞定!現(xiàn)在,無論容器內(nèi)存限制是多少,我們的 Java 應用程序將始終根據(jù)容器調整堆大小,而不是根據(jù) daemon 調整堆大小。
“如何在Docker里跑Java”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。