公司最近部分应用要从 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的差很少。至此,改造就所有完成了。