容器环境 JVM 内存动态配置

  在微服务架构中,JAVA 框架占用了绝大部分的市场,比如Spring CloudDubbo等,其中在使用容器化部署的时候经常碰到关于JVM的内存分配的大小的配置,以下来讲述自己所用到过的配置方式。

固定配置

此方式,顾名思义,就是将JVM参数进行固定化,比如在将JAR打包成容器镜像时

FROM openjdk:8-jdk-alpine
LABEL maintainer="Shuhui<shuhui@vqiu.cn>"

RUN set -xe \
    && sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
    && apk update \
    && apk add --no-cache ca-certificates ttf-dejavu fontconfig tzdata tini \
    && cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone

COPY app.jar /
VOLUME ["/apps/logs"]

ENTRYPOINT ["/sbin/tini", "--", "java", "-jar", "-Xms1g", "-Xmx2g", "/scheduler.jar"]

可以看到,在容器运行时将JVM参数固定死了,也就是后续需要改动的话需要重新操作镜像。Em。。。 这种牵一发动全身的方式不可取。不推荐使用

环境变量

环境变量的方式可理解为固定配置的升级版本,就是将JVM参数包装成一个专用的环境变量,比如"JAVA_OPTS":

FROM openjdk:8-jdk-alpine
LABEL maintainer="Shuhui<shuhui@vqiu.cn>"

RUN set -xe \
    && sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
    && apk update \
    && apk add --no-cache ca-certificates ttf-dejavu fontconfig tzdata tini \
    && cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone

ENV JAVA_OPTS "-server -Xms1024m -Xmx2048m"

COPY app.jar /
VOLUME ["/apps/logs"]

ENTRYPOINT exec /sbin/tini -- java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /app.jar

JVM参数直接引用一个叫JAVA_OPTS的环境变量,后续需要改动时直接改动这个参数即可,比如,使用configmap的方式:

$ cat xx-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: xx-config
data:
  JAVA_OPTS: '-Xms1024m -Xmx2048m'

编排文件中引入

      containers:
      - image: #IMAGE_NAME#:#IMAGE_TAG#
        name: xx
        imagePullPolicy: IfNotPresent
        env:
        - name: JAVA_OPTS
          valueFrom:
            configMapKeyRef:
              name: xx-config
              key: JAVA_OPTS

当然,若你跟我一样,不喜欢套来套去--嫌弃使用configmap繁琐,直接传参亦可:

        env:
        - name: JAVA_OPTS
          value: "-server -Xms2048m -Xmx4096m -Djava.security.egd=file:/dev/./urandom -Duser.timezone=GMT+08"

动态分配

jdk1.8.191的版本新增了以下JVM参数,可以按容器所分配的内存资源大小去动态分配,我们无须去固定指定为个值的大小,这也是目前比较合理的一种方式,同时也是推荐的一种方式。。

参数 说明
-XX:InitialRAMPercentage=N 将初始堆大小设置为总内存的百分比
-XX:MinRAMPercentage=N 将最小堆大小设置为总内存的百分比
-XX:MaxRAMPercentage=N 将最大堆大小设置为总内存的百分比

提示: 如果已经配置了 -Xms, 那么 -XX:InitialRAMPercentage 参数将忽略,如果已经配置了Xmx ,那么 -XX:MaxRAMPercentage 参数将忽略.

如果应用程序在容器中运行,并且指定了-XX:+UseContainerSupport,则容器的默认堆大小(默认4分1的内存容量)。

示例: 通用的启动脚本中指定80%(-XX:MaxRAMPercentage=80.0 -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0)。那么以pod为1G的内存为例,服务就相当于设置了-Xmx819m -Xms819m

  • Dockerfile 范例如下:
FROM openjdk:8-jdk-alpine
LABEL maintainer="Qiu Shuhui<shuhui@vqiu.cn>"

ARG user=shuhui
ARG group=shuhui
ARG uid=1000
ARG gid=1000
ARG APP_HOME=/app

ENV APP_HOME $APP_HOME
ENV JAVA_OPTS "-server -Djava.security.egd=file:/dev/./urandom"

RUN set -xe \
    && sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
    && apk update upgrade \
    && apk add --no-cache procps ca-certificates ttf-dejavu fontconfig curl tzdata tini bash \
    && cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && rm -rf /var/cache/apk/*

RUN set -xe \
    && mkdir -p $APP_HOME \
    && chown ${uid}:${gid} $APP_HOME \
    && addgroup -g ${gid} ${group} \
    && adduser -h "$APP_HOME" -u ${uid} -G ${group} -s /bin/bash -D ${user}

COPY --chown=$user xx.jar $APP_HOME/app.jar

USER $uid
WORKDIR $APP_HOME


ENTRYPOINT ["/sbin/tini", "--"]
CMD ["java -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0 -XX:MaxRAMPercentage=80.0 -jar /app.jar"]

此后,访问容器无需再设置Xmx为固定值,随着CGroup的资源去动态分配。

参考引用