如果使用官方的Java鏡像,或者基于Java鏡像構建的Docker鏡像,都可以通過傳遞JAVA_OPTS環境變量來輕松地設置JVM的內存參數。比如,對于官方Tomcat鏡像,我們可以執行下面命令來啟動一個最大內存為512M的tomcat實例
dockerrun--rm-eJAVA_OPTS='-Xmx512m'tomcat:8
在日志中,我們可以清楚地發現設置已經生效“Commandlineargument:-Xmx512m”
02-Apr-201612:46:26.970INFO[main]org.apache.catalina.startup.VersionLoggerListener.logServerversion:ApacheTomcat/8.0.32
02-Apr-201612:46:26.974INFO[main]org.apache.catalina.startup.VersionLoggerListener.logServerbuilt:Feb2201619:34:53UTC
02-Apr-201612:46:26.975INFO[main]org.apache.catalina.startup.VersionLoggerListener.logServernumber:8.0.32.0
02-Apr-201612:46:26.975INFO[main]org.apache.catalina.startup.VersionLoggerListener.logOSName:Linux
02-Apr-201612:46:26.975INFO[main]org.apache.catalina.startup.VersionLoggerListener.logOSVersion:4.1.19-boot2docker
02-Apr-201612:46:26.975INFO[main]org.apache.catalina.startup.VersionLoggerListener.logArchitecture:amd64
02-Apr-201612:46:26.975INFO[main]org.apache.catalina.startup.VersionLoggerListener.logJavaHome:/usr/lib/jvm/java-7-openjdk-amd64/jre
02-Apr-201612:46:26.976INFO[main]org.apache.catalina.startup.VersionLoggerListener.logJVMVersion:1.7.0_95-b00
02-Apr-201612:46:26.976INFO[main]org.apache.catalina.startup.VersionLoggerListener.logJVMVendor:OracleCorporation
02-Apr-201612:46:26.977INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCATALINA_BASE:/usr/local/tomcat
02-Apr-201612:46:26.977INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCATALINA_HOME:/usr/local/tomcat
02-Apr-201612:46:26.978INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCommandlineargument:-Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
02-Apr-201612:46:26.978INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCommandlineargument:-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
02-Apr-201612:46:26.978INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCommandlineargument:-Xmx512m
...
然而在Docker集群上部署運行Java容器應用的時候,僅僅對JVM的heap參數設置是不夠的,我們還需要對Docker容器的內存資源進行限制:
1.限制容器使用的內存的最大量,防止對系統或其他應用造成傷害
2.能夠將Docker容器調度到擁有足夠空余的內存的節點,從而保證應用的所需運行資源
關于容器的資源分配約束,Docker提供了相應的啟動參數
對內存而言,最基本的就是通過-m參數來約束容器使用內存的大小
-m,--memory=""
Memorylimit(format:<number>[<unit>]).Numberisapositiveinteger.Unitcanbeoneofb,k,m,org.Minimumis4M.
那么問題就來了,為了正確設置Docker容器內存的大小,難道我們需要同時傳遞容器的內存限制和JAVA_OPTS環境變量嗎?如下所示:
dockerrun--rm-m512m-eJAVA_OPTS='-Xmx512m'tomcat:8
這個方法有兩個問題
1.需要管理員保證容器內存和JVM內存設置匹配,否則可能引發錯誤
2.當對容器內存限制調整時,環境變量也需要重新設定,這就需要重建一個新的容器
是否有一個方法,可以讓容器內部的JVM自動適配容器的內存限制?這樣可以采用更加統一的方法來進行資源管理,簡化配置工作。
大家知道Docker是通過CGroup來實現資源約束的,自從1.7版本之后,Docker把容器的localcgroups以只讀方式掛載到容器內部的文件系統上,這樣我們就可以在容器內部,通過cgroups信息來獲取系統對當前容器的資源限制了。
我創建了一個示例鏡像registry.aliyuncs.com/denverdino/tomcat:8-autoheap
,其源代碼可以從Github獲得。它基于Docker官方Tomcat鏡像創建,它的啟動腳本會檢查CGroup中內存限置,并計算JVM最大Heapsize來傳遞給Tomcat。其代碼如下
#!/bin/bash
limit_in_bytes=$(cat/sys/fs/cgroup/memory/memory.limit_in_bytes)
#Ifnotdefaultlimit_in_bytesincgroup
if["$limit_in_bytes"-ne"9223372036854771712"]
then
limit_in_megabytes=$(expr$limit_in_bytes\/1048576)
heap_size=$(expr$limit_in_megabytes-$RESERVED_MEGABYTES)
exportJAVA_OPTS="-Xmx${heap_size}m$JAVA_OPTS"
echoJAVA_OPTS=$JAVA_OPTS
fi
execcatalina.shrun
說明:
為了監控,故障排查等場景,我們預留了部分內存(缺省64M),其余容器內存我們都分配給JVM的堆。
這里沒有對邊界情況做進一步處理。在生產系統中需要根據情況做相應的設定,比如最大的堆大小等等。
現在我們啟動一個tomcat運行在512兆的容器中
dockerrun-d--nametest-m512mregistry.aliyuncs.com/denverdino/tomcat:8-autoheap
通過下列命令,從日志中我們可以檢測到相應的JVM參數已經被設置成448MB(512-64)
dockerlogstest
...
02-Apr-201614:18:09.870INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCommandlineargument:-Xmx448m
...
我們也可以方便的調整Java應用的內存.
Docker1.10提供了對容器資源限制的動態修改能力。但是由于JVM無法感知容器資源修改,我們依然需要重啟tomcat來變更JVM的內存設置,例如,我們可以通過下面命令把容器內存限制調整到1GB
dockerupdate-m1024mtest
dockerrestarttest
再次檢查日志,相應的JVMHeapSize最大值已被設置為960MB
dockerlogstest
...
02-Apr-201614:21:07.644INFO[main]org.apache.catalina.startup.VersionLoggerListener.logCommandlineargument:-Xmx960m