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
相关文章
相关标签/搜索