公司最近部分應用要從 Docker Swarm 遷移到 Kubernetes,而遷移到新的 Kubernetes 上的應用都要作資源的限制,不然若是 Pod 不斷地佔用機器資源把整個節點都拖垮了那就很糟糕了。。因此我按照 Kubernetes 的文檔作了限制後,發現並無什麼卵用,容器不斷的被 OOMKIILED 而後又重啓,服務也一直沒法訪問,因此須要研究下Java 應用到底該怎麼限制內存資源。html
當我在 google 搜索了一波後,發現這個問題就是JVM 沒法得知容器的資源限制,因此按照 JVM 的默認規則,它分配的 Max Heap Size 是系統內存的1/4,因此就很容易超出 resource limits 的限制,致使容器被 kill 掉。java
而形成這個問題的緣由是什麼呢,這就得說回 Docker 容器,咱們都知道 Docker 容器本質上就是一個被隔離的用戶態進程,而構成這個進程天然就少不了三駕馬車:web
咱們要限制 Java 程序天然就與 cgroups 有關了,在 Linux 上,一切皆文件,因此係統的資源信息等也是以一種特殊的文件形式放在/proc
目錄下的,像咱們經常使用的一些top,free,ps
等查看系統資源的工具本質上也是從這個目錄下獲取的信息。可是 cgroups 限制資源不同,它是在/sys/fs/cgroups
目錄下對指定 namespace 的作限制。docker
而咱們的 JVM 是怎麼獲取到的當前進程的內存信息的呢?是經過讀取掛載的/proc/meminfo
文件了,那麼因爲/proc/meminfo
裏面展現的是宿主機的內存資源,從而讓容器產生了本身是地主的感受,還覺得有大把的內存能夠給它用,其實本身只是一個長工。。shell
瞭解了這個問題的成因後,並在網上搜集了一些資料,我發現解決這個問題的方式就是在 Java 啓動命令前加上JVM_OPTS
參數,而具體加什麼參數和 Java 的版本有關,總的來講呢,規則以下:tomcat
Java < Java8_u131
: 若是低於這個版本,那麼在 Java 容器的 CMD 命令裏得加上具體的內存分配大小,如"-Xms64M -Xmx256M"
,注意-Xms
最好不超過 Pod 限制資源3/4,由於不止是 JVM 要使用內存,容器自己也是須要內存的。Java < 10
: 若是 Java 版本在這個區間,那麼咱們就不須要明確地指定最大堆的大小了,這幾個版本實際上已經能夠從 cgroups 獲取資源限制信息,只不過這個特性須要手動開啓,須要加上參數"-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1"
我司 Java 容器啓動方式有兩種,一種是經過 jar 包啓動,還有一種是用 Tomcat啓動,因此我會分別介紹這兩種 Java 應用的資源限制方式。安全
公司的Java 版本有的用的是 1.7,還有的用的是 1.8 小於 131的版本,鏡像也是用的 CentOS 或者 Ubuntu 的基礎鏡像作的,體積大得慘不忍睹。。因此我決定使用 alpine 的鏡像從新構建,而且只保留 jre,這個基礎鏡像的 Dockerfile 可參考jeanblanchard/java,至於其餘的步驟就很簡單了,個人 Dockerfile 以下:oracle
FROM 18.16.200.10:5000/oracle-jre8:u231 WORKDIR /home COPY xxxx.jar . RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" >> /etc/timezone CMD java $JVM_OPTS -Duser.timezone=GMT+08 -jar /home/xxxx.jar
個人 Yaml 文件大體以下,加上 env 的環境變量和資源限制:app
... containers: - name: xxxxx image: 18.16.200.191:5000/xxx:201910310754 env: - name: JVM_OPTS value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Xms256M" resources: requests: cpu: 0.1 limits: cpu: 1 memory: 1.5Gi ...
Dockerfile 文件以下:webapp
FROM tomcat:8.5-jdk8 WORKDIR /usr/local/tomcat COPY xxx.war webapps/ROOT.war RUN unzip webapps/ROOT.war -d webapps/ROOT/ && rm -f webapps/ROOT.war ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Xms256M" EXPOSE 8080 CMD ["/usr/local/tomcat/bin/catalina.sh", "run"]
在tomcat 的啓動文件 catalina.sh
中能夠經過環境變量JAVA_OPTS
傳入參數。
Yaml 文件則與 JAR的差很少。至此,改造就所有完成了。