当需要运行Java应用时,不管在机器上还是容器里,一般的做法是运行 java
命令。
一些 Java 应用会被打包成单个可执行的 JAR 文件,如 Spring Boot 应用。对于这样的 JAR 文件,只需要使用 java -jar /opt/app.jar
即可。
另外一些应用会打包成多个 JAR 文件的形式。对于这样的目录结构,只需要用 java -cp lib/* com.example.Main
指定类路径(classpath)和入口 Java 类即可。
在构建容器镜像时,一般使用 Dockerfile 的 ENTRYPOINT
来指定运行的 java
命令。
ENTRYPOINT [ "java", "-jar", "/opt/app.jar" ]
直接使用 java
命令的做法,在容器运行时并不灵活。如果想使用 -D
参数来添加新的系统属性,就必须要修改 Dockerfile 中的ENTRYPOINT
定义。这就意味要创建新的镜像,使用起来非常的不方便。
更好的做法是使用本文中介绍的 run-java.sh
。顾名思义,run-java.sh
是一个 shell 脚本。它的作用是运行Java程序。该脚本可以从 GitHub (fabric8io-images/run-java-sh) 或 Maven 仓库(io.fabric8:run-java-sh:1.3.8)下载。
基本用法
该脚本的特点是可以使用环境变量来配置调用 java
命令时的参数。这些环境变量大致可以分成如下几类:
第一类与Java应用相关。
JAVA_APP_DIR
设置应用文件的根目录,默认为 run-java.sh
所在的目录。
JAVA_APP_NAME
设置应用进程的名称。
JAVA_MAJOR_VERSION
设置 JVM 的版本号。默认从 java -version
命令的输出中解析。
第二类与 Java 应用的 classpath 相关。
JAVA_CLASSPATH
设置完整的 classpath。
JAVA_LIB_DIR
设置作为库的 JAR 文件的目录。默认为 JAVA_APP_DIR
的值。
JAVA_APP_JAR
设置可执行的 JAR 文件的路径。
JAVA_MAIN_CLASS
设置入口类的名称。
第三类与Java运行的参数相关。
JAVA_OPTIONS
设置额外的参数。
第四类与运行的内存和 CPU 相关。
JAVA_INIT_MEM_RATIO
设置起始的 heap 尺寸占全部内存的比例。转换成参数 -Xms
。值 20
表示 20%
。
JAVA_MAX_MEM_RATIO
设置最大的 heap 尺寸占全部内存的比例。转换成参数 -Xmx
。值 50
表示 50%
。
JAVA_MAX_CORE
设置最大的 CPU 核数。
第五类与调试相关。
JAVA_DEBUG
启用远程调试。
JAVA_DEBUG_SUSPEND
以暂停模式启用远程调试。
JAVA_DEBUG_PORT
设置远程调试的端口。默认为 5005
。
第六类与 HTTP 代理相关。
HTTP_PROXY
设置 HTTP 代理。转换成 Java 的系统属性 http.proxyHost
和 http.proxyPort
。
HTTPS_PROXY
设置 HTTPS 代理。转换成 Java 的系统属性 https.proxyHost
和 https.proxyPort
。
NO_PROXY
设置不使用代理的主机名。转换成 Java 的系统属性 http.nonProxyHosts
。
最后一类与应用诊断相关。
JAVA_DIAGNOSTICS
启用输出额外的诊断信息。
需要注意的是,JAVA_INIT_MEM_RATIO
和 JAVA_MAX_MEM_RATIO
要求设置容器的内存约束,也就是 Kubernetes 容器声明的 resources.limits.memory
的值。否则的话,这两个设置都无效。内存的约束值作为计算时的基础。
具体使用
下面我们看一下如何具体使用 run-java.sh
。
实际上,Quarkus 应用在以 JVM 模式运行时,默认的容器镜像使用的就是 run-java.sh
。下面是 Dockerfile 的内容。
在构建镜像时,从 Maven 仓库下载 run-java.sh
脚本,并保存为 /deployments/run-java.sh
文件。ENTRYPOINT
指令直接运行 run-java.sh
脚本。额外的参数以环境变量 JAVA_OPTIONS
来传递。
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
&& microdnf update \
&& microdnf clean all \
&& mkdir /deployments \
&& chown 1001 /deployments \
&& chmod "g+rwX" /deployments \
&& chown 1001:root /deployments \
&& curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
&& chown 1001 /deployments/run-java.sh \
&& chmod 540 /deployments/run-java.sh \
&& echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security
# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 target/quarkus-app/*.jar /deployments/
COPY --chown=1001 target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 1001
ENTRYPOINT [ "/deployments/run-java.sh" ]
需要注意的是,这里并没有使用 JAVA_APP_JAR
或 JAVA_MAIN_CLASS
来指定应用的入口 JAR 文件或 Java 类。在这种情况下,run-java.sh
会查找应用根目录下的唯一 JAR 文件,作为 JAVA_APP_JAR
的值。这个时候,应用根目录下必须有且仅有一个可执行的 JAR 文件。否则,脚本会出错。
下面介绍一个 run-java.sh
脚本的使用场景。当 Java 应用在 Kubernetes 上运行时,发现了一个只在 Kubernetes 上可以重现的 bug。这个时候需要启用 Java 的远程调试功能来进行调试。此时,可以修改应用的 Deployment,添加一个名为 JAVA_DEBUG
,值为 true
的环境变量。当新的 Pod 启动之后,应用已经启用了远程调试功能。用 kubectl port-forward
访问 Pod 的 5005
端口,就可以在 IDE 中进行调试。在我B站的视频中,有该场景的具体演示。
如果需要下载 run-java.sh
,可以关注我的公众号,发送消息 “run-java” 即可,会给出网盘下载地址。