Docker容器中怎么部署Java微服務(wù),針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
創(chuàng)新互聯(lián)公司專注于華寧網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供華寧營銷型網(wǎng)站建設(shè),華寧網(wǎng)站制作、華寧網(wǎng)頁設(shè)計、華寧網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造華寧網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供華寧網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
要確定 docker 容器內(nèi)存超限問題的直接原因并不難。直接進入docker容器,執(zhí)行 top
命令,我們發(fā)現(xiàn)宿主機是一臺8核16G的機器,而且 docker 并不會屏蔽這些信息,也就是 JVM 會認為自己工作于一臺 16G 內(nèi)存的機器上。而查看 demo 服務(wù)的 Dockerfile,發(fā)現(xiàn)運行服務(wù)時并沒有對 JVM 的內(nèi)存進行任何限制,于是 JVM 會根據(jù)默認的設(shè)置來工作 —— 最大堆內(nèi)存為物理內(nèi)存的1/4(這里的描述并不完全準(zhǔn)確,因為 JVM 的默認堆內(nèi)存大小限制比例其實是根據(jù)物理內(nèi)存有所變化的,具體內(nèi)容請自行搜索資料),而基于模板創(chuàng)建的 ServiceStage 流水線,在部署應(yīng)用堆棧的時候會把 docker 容器的內(nèi)存配額默認設(shè)置為 512M,于是容器就會在啟動的時候內(nèi)存超限了。至于以前沒有碰到過這種問題的原因,只是因為以前沒將這么高規(guī)格的 ECS 服務(wù)器用于流水線部署應(yīng)用堆棧。
在查詢過相關(guān)資料后,我們找到了兩種問題解決方案,一個是直接在 jar 包運行命令里加上 -Xmx
參數(shù)來指定最大堆內(nèi)存,不過這種方式只能將 JVM 堆內(nèi)存限制為一個固定的值;另一個方法是在執(zhí)行 jar 包時加上 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
參數(shù),讓 JVM 能夠感知到docker容器所設(shè)置的 cgroup
限制,相應(yīng)地調(diào)整自身的堆內(nèi)存大小,不過這個特性是 JDK 8u131 以上的版本才具備的。
最終,我們提醒 ServiceStage 流水線的同學(xué)將 CSEJavaSDK demo 的創(chuàng)建模板做了改進,在 Dockerfile 中將打包的基礎(chǔ)鏡像版本由原先的 java:8u111-jre-alpine
升級為了 openjdk:8u181-jdk-alpine
,并且在運行服務(wù) jar 包的命令中加上了 -Xmx256m
參數(shù)。問題至此已經(jīng)解決了。
雖然問題已經(jīng)解決,但是在好奇心的驅(qū)使下,我還是打算自己找個 demo 實際去觸發(fā)一下問題,另外看看從網(wǎng)上搜到的解決方法到底好不好用 : )
創(chuàng)建云上工程
首先需要在華為云 ServiceStage 創(chuàng)建一個云上工程。
在 ServiceStage -> 應(yīng)用開發(fā) -> 微服務(wù)開發(fā) -> 工程管理 -> 創(chuàng)建云上工程中,選擇“基于模板創(chuàng)建”,語言選擇 Java, 框架選擇 CSE-Java (SpringMVC)
,部署系統(tǒng)選擇“云容器引擎CCE”,給你的云上工程取一個名字,比如test-memo-consuming
,最后選擇存放代碼的倉庫,就可以完成云上工程的創(chuàng)建了。
之后云上工程會根據(jù)你的選項自動地生成腳手架代碼,上傳到你指定的代碼倉庫中,并且為你創(chuàng)建一條流水線,完成代碼編譯、構(gòu)建、打包、歸檔鏡像包的操作,并且使用打好的 docker 鏡像包在 CCE 集群中部署一個應(yīng)用堆棧。
創(chuàng)建云上工程和流水線不是本文的重點,我就不詳細講操作了 : )。同一個應(yīng)用堆棧的實例可以部署多個,在這里為了實驗方便就按照默認值1個來部署。
登錄到 demo 服務(wù)所部署的容器,使用curl
命令可以調(diào)用 demo 服務(wù)的 helloworld 接口,可以看到此時服務(wù)已經(jīng)可以正常工作。
增加實驗代碼
為了能夠觸發(fā)微服務(wù)實例消耗更多的內(nèi)存,我在項目代碼中增加了如下接口,當(dāng)調(diào)用/allocateMemory
接口時,微服務(wù)實例會不停申請內(nèi)存,直到 JVM 拋出 OOM 錯誤或者容器內(nèi)存超限被 kill 掉。
private HashMapcacheMap = new HashMap<>(); @GetMapping(value = "/allocateMemory") public String allocateMemory() { LOGGER.info("allocateMemory() is called"); try { for (long i = 0; true; ++i) { cacheMap.put("key">
此時用來打鏡像包的基礎(chǔ)鏡像是openjdk:8u181-jdk-alpine
,jar 包啟動命令中加上了-Xmx256m
參數(shù)。
執(zhí)行流水線,應(yīng)用堆棧部署成功后,調(diào)用/allocateMemory
接口觸發(fā)微服務(wù)實例消耗內(nèi)存,直到 JVM 拋出 OOM 錯誤,可以在 ServiceStage -> 應(yīng)用上線 -> 應(yīng)用管理中選擇相應(yīng)的應(yīng)用,點擊進入概覽頁面,查看應(yīng)用使用內(nèi)存的情況。
應(yīng)用使用的內(nèi)存從 800M+ 陡然下降的時間點就是我重新打包部署的時間,而之后由于調(diào)用/allocateMemory
接口,內(nèi)存占用量上升到了接近 400M,并且在這個水平穩(wěn)定了下來,顯示-Xmx256m
參數(shù)發(fā)揮了預(yù)期的作用。
現(xiàn)在將 demo 工程中的 Dockerfile 修改一下,將基礎(chǔ)鏡像改為 java:8u111-jre-alpine
,并且刪除啟動命令中的-Xmx256m
參數(shù),將其提交為noLimit_oldBase
分支,推送到代碼倉庫中。然后編輯流水線,將 source 階段的任務(wù)所使用的代碼分支改為noLimit_oldBase
分支,保存并重新運行流水線,將新的代碼打包部署到應(yīng)用堆棧中。
在微服務(wù)實例列表中查詢到新的微服務(wù)實例的 endpoint IP 后,調(diào)用/allocateMemory
接口,觀察內(nèi)存情況,內(nèi)存從接近 400M 突然掉下去一下,然后又上升到約 450M 的時間點就是修改代碼后的微服務(wù)實例部署成功的時間點,之后內(nèi)存占用量突然下跌就是因為調(diào)用/allocateMemory
接口導(dǎo)致容器內(nèi)存超限被 kill 掉了。
如果你事先使用docker logs -f
命令查看容器日志的話,那么日志大概是這個樣子的
2018-11-23 15:40:04,920 INFO SCBEngine:152 - receive MicroserviceInstanceRegisterTask event, check instance Id... 2018-11-23 15:40:04,920 INFO SCBEngine:154 - instance registry succeeds for the first time, will send AFTER_REGISTRY event. 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:116 - keyStore [server.p12] file not exist, please check! 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:136 - trustStore [trust.jks] file not exist, please check! 2018-11-23 15:40:04,928 INFO DataFactory:62 - Monitor data sender started. Configured data providers is {com.huawei.paas.cse.tcc.upload.TransactionMonitorDataProvider,com.huawei.paas.monitor.HealthMonitorDataProvider,} 2018-11-23 15:40:04,929 INFO ServiceCenterTask:51 - read MicroserviceInstanceRegisterTask status is FINISHED 2018-11-23 15:40:04,939 INFO TestmemoconsumingApplication:57 - Started TestmemoconsumingApplication in 34.81 seconds (JVM running for 38.752) 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:258 - find instances[1] from service center success. service=default/CseMonitoring/latest, old revision=null, new revision=28475010.1 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:266 - service id=8b09a7085f4011e89f130255ac10470c, instance id=8b160d485f4011e89f130255ac10470c, endpoints=[rest://100.125.0.198:30109?sslEnabled=true] 2018-11-23 15:40:34,937 INFO ServiceCenterTaskMonitor:39 - sc task interval changed from -1 to 30 2018-11-23 15:47:03,823 INFO SPIServiceUtils:76 - Found SPI service javax.ws.rs.core.Response$StatusType, count=0. 2018-11-23 15:47:04,657 INFO TestmemoconsumingImpl:39 - allocateMemory() is called Killed
可以看到allocateMemory
方法被調(diào)用,然后 JVM 還沒來得及拋出 OOM 錯誤,整個容器就被 kill 掉了。
這里也給大家提了一個醒:不要以為自己的服務(wù)容器能啟動起來就萬事大吉了,如果沒有特定的限制,JVM 會在運行時繼續(xù)申請堆內(nèi)存,也有可能造成內(nèi)存用量超過 docker 容器的配額!
前文提到還有另外一種方法解決 JVM 內(nèi)存超限的問題,這種方法可以讓 JVM 自動感知 docker 容器的 cgroup
限制,從而動態(tài)的調(diào)整堆內(nèi)存大小,感覺挺不錯的。我們也來試一下這種方法,看看效果如何 ; )
回到demo項目代碼的master
分支,將 Dockerfile 中啟動命令參數(shù)的-Xmx256m
替換為-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
,提交為useCGroupMemoryLimitForHeap
分支,推送到代碼倉庫里。再次運行流水線進行構(gòu)建部署。
等 demo 服務(wù)部署成功后,再次調(diào)用/allocateMemory
接口,容器的內(nèi)存占用情況如上圖所示(最右邊的那一部分連續(xù)曲線),內(nèi)存上升到一定程度后,JVM 拋出了 OOM 錯誤,沒有繼續(xù)申請堆內(nèi)存??磥磉@種方式也是有效果的。不過,仔細觀察容器的內(nèi)存占用情況,可以發(fā)現(xiàn)容器所使用的內(nèi)存僅為不到 300M,而我們對于這個容器的內(nèi)存配額限制為 512M,也就是還有 200M+ 是閑置的,并不會被 JVM 利用。這個利用率,比起上文中直接設(shè)置-Xmx256m
的內(nèi)存利用率要低 : ( 。推測是因為 JVM 并不會感知到自己是部署在一個 docker 容器里的,所以它把當(dāng)前的環(huán)境當(dāng)成一個物理內(nèi)存只有 512M 的物理機,按照比例來限制自己的最大堆內(nèi)存,另一部分就被閑置了。
關(guān)于Docker容器中怎么部署Java微服務(wù)問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。