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