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