Kubernetes中限制Java容器內存資源實踐

0x00 原由

公司最近部分應用要從 Docker Swarm 遷移到 Kubernetes,而遷移到新的 Kubernetes 上的應用都要作資源的限制,不然若是 Pod 不斷地佔用機器資源把整個節點都拖垮了那就很糟糕了。。因此我按照 Kubernetes 的文檔作了限制後,發現並無什麼卵用,容器不斷的被 OOMKIILED 而後又重啓,服務也一直沒法訪問,因此須要研究下Java 應用到底該怎麼限制內存資源。html

0x01 分析

當我在 google 搜索了一波後,發現這個問題就是JVM 沒法得知容器的資源限制,因此按照 JVM 的默認規則,它分配的 Max Heap Size 是系統內存的1/4,因此就很容易超出 resource limits 的限制,致使容器被 kill 掉。java

而形成這個問題的緣由是什麼呢,這就得說回 Docker 容器,咱們都知道 Docker 容器本質上就是一個被隔離的用戶態進程,而構成這個進程天然就少不了三駕馬車:web

  • cgroups 作進程的資源限制
  • namespace 作命名空間隔離
  • aufs 作聯合文件掛載實現文件系統

咱們要限制 Java 程序天然就與 cgroups 有關了,在 Linux 上,一切皆文件,因此係統的資源信息等也是以一種特殊的文件形式放在/proc目錄下的,像咱們經常使用的一些top,free,ps等查看系統資源的工具本質上也是從這個目錄下獲取的信息。可是 cgroups 限制資源不同,它是在/sys/fs/cgroups目錄下對指定 namespace 的作限制。docker

而咱們的 JVM 是怎麼獲取到的當前進程的內存信息的呢?是經過讀取掛載的/proc/meminfo文件了,那麼因爲/proc/meminfo裏面展現的是宿主機的內存資源,從而讓容器產生了本身是地主的感受,還覺得有大把的內存能夠給它用,其實本身只是一個長工。。shell

0x02 方法

瞭解了這個問題的成因後,並在網上搜集了一些資料,我發現解決這個問題的方式就是在 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 版本大於 10 : 恭喜你,這個特性是默認開啓的
  • 其餘下游 Java 分支: 不太清楚,請查看官方文檔

0x03 實踐

我司 Java 容器啓動方式有兩種,一種是經過 jar 包啓動,還有一種是用 Tomcat啓動,因此我會分別介紹這兩種 Java 應用的資源限制方式。安全

1. JAR

公司的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
...

2. Tomcat

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的差很少。至此,改造就所有完成了。

0x04 參考資料及延伸閱讀

  1. 容器中的JVM資源該如何被安全的限制?
  2. DOCKER基礎技術:LINUX CGROUP
相關文章
相關標籤/搜索