Java应用Dockerfile编辑
# 一、概述
由于Docker容器的轻量化、便捷性特点,在工作成为中容器化解决方案,对于一些开源的中间件,我们部署的时候,都会直接使用官方镜像,我们pull
下来,配置启动参数即可,然而,我们工作中接触最多的还是自己开发的业务应用,常见的java
、python
、go
等语言程序,这些对于我们来说,都需要自己定义Dockerfile
生成镜像,本文先探究java
程序Dockerfile
的编辑开发。
# 二、Java应用传统启动
# 2.1 简单启动
对于非容器化的操作,当我们打包好之后,会生成业务jar
包,我们直接按照启动参数执行java -jar ${JAR_NAME} ${JAVA_OPTS}
即可,比如:
java -jar sal-toc-1.0-SNAPSHOT.jar --spring.profiles.active=dev
# 2.2 启动脚本
使用上面直接启动,虽然能够把应用跑起来,但是弊端也很明显,就是对于后期维护(重启发布)是非常不便的,因此我们大多会借助shell
脚本,对应用进行启动管理:
#!/bin/bash
# 应用名
APP_NAME=sal-toc
PROG_NAME=$0
ACTION=$1
APP_START_TIMEOUT=20 # 等待应用启动的时间 结合健康检查使用
APP_PORT=9201 # 应用端口
HEALTH_CHECK_URL=http://127.0.0.1:${APP_PORT} # 应用健康检查URL
APP_HOME=/data/webbacks/${APP_NAME} # 应用部署家目录
JAR_NAME="sal-toc-1.0-SNAPSHOT.jar" # jar包的名字
JAVA_OPTS="-Xms512M -Xmx512M --spring.profiles.active=prod"
# 判断目录是否存在
if [ ! -d "$APP_HOME" ]; then
mkdir -p ${APP_HOME}
fi
usage() {
echo "Usage: $PROG_NAME {start|stop|restart}"
exit 2
}
health_check() {
exptime=0
echo "checking ${HEALTH_CHECK_URL}"
while true
do
status_code=`/usr/bin/curl -L -o /dev/null --connect-timeout 5 -s -w %{http_code} ${HEALTH_CHECK_URL}`
if [ "$?" != "0" ]; then
echo -n -e "\rapplication not started"
else
echo "code is $status_code"
if [ "$status_code" == "200" ];then
break
fi
fi
sleep 1
((exptime++))
echo -e "\rWait app to pass health check: $exptime..."
if [ $exptime -gt ${APP_START_TIMEOUT} ]; then
echo 'app start failed'
exit 1
fi
done
echo "check ${HEALTH_CHECK_URL} success"
}
start_application() {
echo "starting java process"
nohup java -jar ${JAR_NAME} ${JAVA_OPTS} >/dev/bull 2>&1 &
echo "started java process"
}
stop_application() {
checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'run.sh'| awk '{print$2}'`
if [[ ! $checkjavapid ]];then
echo -e "\rno java process"
return
fi
echo "stop java process"
times=60
for e in $(seq 60)
do
sleep 1
COSTTIME=$(($times - $e ))
checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'run.sh'| awk '{print$2}'`
if [[ $checkjavapid ]];then
kill -9 $checkjavapid
echo -e "\r-- stopping java lasts `expr $COSTTIME` seconds."
else
echo -e "\rjava process has exited"
break;
fi
done
echo ""
}
start() {
start_application
health_check
}
stop() {
stop_application
}
case "$ACTION" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
usage
;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
注: 上面脚本是一个比较完善的生产可用脚本,如果感觉比较复杂,则可以自行修改,具体需要自己根据业务jar启动特性调整。
# 三、Dockerfile开发
# 3.1 简单示例
# 3.1.1 编辑Dockerfile
# 设置基础镜像
FROM openjdk:17-jdk-alpine3.14
LABEL maintainer="tchuaxiaohua@163.com"
# 设置环境变量
ARG JAVA_OPTIONS="-Xms512M -Xmx512M --spring.profiles.active=prod"
ENV appPort 9021
ENV appName sale-toc-1.0-SNAPSHOT.jar
# 设置工作目录
WORKDIR /apps
# 复制应用程序的JAR文件到容器中
ADD ${appName} /apps/
# 设置时区
RUN apk --update add tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata && \
rm -rf /var/cache/apk/*
# 暴露应用程序的端口
EXPOSE ${appPort}
# 运行Java应用程序
CMD java -jar /apps/${appName} ${JAVA_OPTIONS}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
相关参数说明
FROM
指定基础镜像构建版本,这里需要自己根据开发本地jdk版本去选择JAVA_TOOL_OPTIONS
设置Java运行时的默认选项,java -jar
启动时会自动应用该变量参数CMD
运行命令还可以使用ENTRYPOINT
# 3.1.2 构建镜像
需要确保
Dockerfile
引用的jar
包路径正确
docker build -t registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v1 .
# 3.1.3 启动
docker run -d --name v1 -p 9021:9021 registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v1
# 3.1.4 查看主进程
# 3.2 进阶示例
上面构建示例完全可以应用于生产环境,只是有一个问题,那就是容器中Java应用程序的进程ID(PID)为1,我们知道PID为1的进程在Linux系统中通常被用作init进程或主进程,由于该进程的特性,可能会对容器的信号处理,资源管理产生冲突到出现不可预期错误,因此我们可以借助一个启动脚本作为容器的主进程,我们使用开源第三方的软件时,也可以看到对于很多开源的官方镜像,也是都会借助其他工具运行,避免主进程pid为1。
# 3.2.1 Dockerfile示例
FROM openjdk:17-jdk-alpine3.14
LABEL maintainer="tchuaxiaohua@163.com"
ENV appPort 9021
ENV appName sale-toc-1.0-SNAPSHOT.jar
RUN apk --update add tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata && \
rm -rf /var/cache/apk/*
RUN echo '#!/bin/sh' > /usr/bin/entrypoint.sh && \
echo 'java -jar /apps/${appName}' >> /usr/bin/entrypoint.sh && \
chmod +x /usr/bin/entrypoint.sh
ADD ${appName} /apps/
EXPOSE ${appPort}
ENTRYPOINT ["entrypoint.sh"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
说明
我们可以看到,我们这里创建一个entrypoint.sh
脚本,并把启动命令防至该脚本中,在最后ENTRYPOINT
中我们执行该脚本即可
# 3.2.2 构建镜像
docker build -t registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v1 .
# 3.2.3 启动
docker run -d --name v1 -p 9021:9021 registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v1
# 3.2.4 查看进程
# 3.3 生产最佳实践
这里我们在进阶的基础上只是针对entrypoint.sh
脚本进行升级,根据我们传统启动方式,完善启动参数,启动步骤基本是一样的
# 3.3.1 完善entrypoint.sh
#!/bin/sh
# 应用名
APP_NAME=sale-toc
PROG_NAME=$0
ACTION=$1
APP_HOME=/apps # 应用部署家目录
JAR_NAME="sale-toc-1.0-SNAPSHOT.jar" # jar包的名字
JAVA_OPTIONS="--spring.profiles.active=prod"
JAVA_JVM_OPTIONS="-Xms512M -Xmx512M" # 该变量主要是用来运行其他agent 比如 监控 链路追踪
# 判断目录是否存在
if [ ! -d "$APP_HOME" ]; then
mkdir -p ${APP_HOME}
fi
usage() {
echo "Usage: $PROG_NAME {start|stop|restart}"
exit 2
}
start_application() {
echo "starting java process"
java -jar ${JAVA_JVM_OPTIONS} ${APP_HOME}/${JAR_NAME} ${JAVA_OPTS}
echo "started java process"
}
stop_application() {
checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'run.sh'| awk '{print$2}'`
if [[ ! $checkjavapid ]];then
echo -e "\rno java process"
return
fi
echo "stop java process"
times=60
for e in $(seq 60)
do
sleep 1
COSTTIME=$(($times - $e ))
checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'run.sh'| awk '{print$2}'`
if [[ $checkjavapid ]];then
kill -9 $checkjavapid
echo -e "\r-- stopping java lasts `expr $COSTTIME` seconds."
else
echo -e "\rjava process has exited"
break;
fi
done
echo ""
}
start() {
start_application
}
stop() {
stop_application
}
case "$ACTION" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
usage
;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
- 脚本移除监控检查,这是因为大多数基础镜像是没有
curl
命令的- 启动命令不能使用nohub,否则会直接退出
# 3.3.2 改造Dockerfile
FROM openjdk:17-jdk-alpine3.14
LABEL maintainer="tchuaxiaohua@163.com"
ENV appPort 9021
ENV appName sale-toc-1.0-SNAPSHOT.jar
ADD ${appName} /apps/
COPY entrypoint.sh /usr/bin/entrypoint.sh
RUN chmod +x /usr/bin/entrypoint.sh
RUN apk --update add tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata && \
rm -rf /var/cache/apk/*
EXPOSE ${appPort}
ENTRYPOINT ["entrypoint.sh","start"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.3.3 构建启动
# 镜像构建
docker build -t registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v1 .
# 启动
docker run -d --name v1 -p 9021:9021 registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v1
2
3
4
# 3.3.4 查看主进程
# 四、maven构建镜像
maven构建镜像需要借助jib
插件,jib 插件 (opens new window)是一个由 Google 开发的 Maven 和 Gradle 插件,用于简化构建和推送 Docker 镜像的过程。它不需要编写 Dockerfile,而是直接使用项目的构建配置来生成 Docker 镜像。
# 4.1 插件引用配置
在项目的
pom.xml
文件中添加 Jib 插件的依赖。
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<!-- 插件的配置选项 -->
</configuration>
</plugin>
<!-- 其他 Maven 插件和配置 -->
</plugins>
</build>
2
3
4
5
6
7
8
9
10
11
12
13
# 4.2 插件参数配置
插件配置是在插件的
configuration
字段中
<configuration>
<!-- 基础镜像 -->
<from>
<image>openjdk:17-jdk-alpine3.14</image>
</from>
<!-- 业务镜像配置 -->
<to>
<image>registry.cn-hangzhou.aliyuncs.com/tfgol-dev/${project.name}</image>
<auth>
<username>tfgol</username>
<password>******</password>
</auth>
</to>
<!-- 业务启动类 推荐在这里指定 -->
<!--
<container>
<mainClass>com.losinx.sale.SaleTocStart</mainClass>
</container>
-->
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
详细配置
......
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<!-- 基础镜像 -->
<from>
<image>openjdk:17-jdk-alpine3.14</image>
</from>
<!-- 业务镜像配置 -->
<to>
<image>registry.cn-hangzhou.aliyuncs.com/tfgol-dev/${project.name}</image>
<auth>
<username>tfgol</username>
<password>*****</password>
</auth>
</to>
</configuration>
</plugin>
</build>
......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
配置说明
<from></from>
用来定义基础镜像,类似于Dockerfile
中的FROM
如果我们使用的私有仓库需要认证,则可以使用<auth> </auth>
配置用户名密码
<auth>
<username>tfgol</username>
<password>******</password>
</auth>
2
3
4
<to></to>
用来定义业务镜像相关参数<image></image>
生成的镜像名称,镜像生产之后,会自动push到对应的仓库中<auth></auth>
使用的私有镜像仓库,所以需要认证
<container></container>
定义应用业务启动类,因为使用maven构建镜像的话,生成的就不在是jar包。
更多参数参考官方插件 (opens new window)说明,很多配置不仅可以在配置文件中指定,也可以在执行构建命令的时候通过
-Djib.xx
指定
# 4.3 编译
到
pom.xml
文件所在目录,执行mvn compile jib:build -Djib.to.tags=v2
[WARNING]
[WARNING] Some problems were encountered while building the effective settings
[WARNING] Unrecognised tag: 'blocked' (position: START_TAG seen ...tact/maven/nexus/content/repositories/ebi-repo/</url>\r\n\t\t<blocked>... @169:12) @ /usr/local/apache-maven-3.6.3/conf/settings.xml, line 169, column 12
[WARNING]
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for com.losinx:sale-toc:jar:1.0-SNAPSHOT
[WARNING] 'dependencyManagement.dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: com.alibaba:fastjson:jar -> version ${fastjson} vs ${fastJson.version} @ com.losinx:auto-sale:1.0-SNAPSHOT, /root/auto-sales/pom.xml, line 239, column 25
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] ------------------------< com.losinx:sale-toc >-------------------------
[INFO] Building sale-toc 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sale-toc ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 4 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sale-toc ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- jib-maven-plugin:3.1.1:build (default-cli) @ sale-toc ---
[INFO]
[INFO] Containerizing application to registry.cn-hangzhou.aliyuncs.com/tfgol-dev/sale-toc, registry.cn-hangzhou.aliyuncs.com/tfgol-dev/sale-toc:v2...
[WARNING] Base image 'openjdk:17-jdk-alpine3.14' does not use a specific image digest - build may not be reproducible
[INFO] Using credentials from <to><auth> for registry.cn-hangzhou.aliyuncs.com/tfgol-dev/sale-toc
[INFO] The base image requires auth. Trying again for openjdk:17-jdk-alpine3.14...
[INFO] Using credentials from Docker config (/root/.docker/config.json) for openjdk:17-jdk-alpine3.14
[INFO] Using base image with digest: sha256:4b6abae565492dbe9e7a894137c966a7485154238902f2f25e9dbd9784383d81
[INFO]
[INFO] Container entrypoint set to [java, -cp, @/app/jib-classpath-file, com.losinx.sale.SaleTocStart]
[INFO]
[INFO] Built and pushed image as registry.cn-hangzhou.aliyuncs.com/tfgol-dev/sale-toc, registry.cn-hangzhou.aliyuncs.com/tfgol-dev/sale-toc:v2
[INFO] Executing tasks:
[INFO] [============================ ] 91.7% complete
[INFO] > launching layer pushers
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.675 s
[INFO] Finished at: 2023-06-29T17:17:39+08:00
[INFO] ------------------------------------------------------------------------
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
说明
构建参数说明
mvn compile jib:build
镜像构建命令,构建成功后会自动把生成的镜像上传至仓库-Djib.to.tags=v2
镜像tag,也可以在插件配置处配置-Djib.container.jvmFlags
指定启动参数,比如: “-Xms256m,-Xmx512m”
构建过程说明(截图bu'fen)
- ① 说明使用仓库及镜像名称,这里可以看到会根据配置的auth自动登录该镜像仓库
- ② 这里是启动命令,maven构建的镜像,启动使用的是
应用类
,这个一般开发都会知道,这里推荐在插件配置中直接指定。 - ③ 对于生成的业务镜像,默认会生成一个tag为latest的镜像,v2是我们指定的指定的tag,这里两个都会上传至镜像仓库。
- ④ 打包后的文件,这里不再试
jar
包的模式,生成镜像的时候会把这些文件都copy到镜像的app目录下
# 4.4 仓库镜像查看
# 4.5 使用总结
通过上面配置我们可以看到,如果maven的jib插件使用比较熟练的话,那么我们可以省很多事情,不用再关注应用Dockerfile
,也不用关注镜像上传,这里构建后,会自动把经上传,并且有很多参数,可配置化,当然如果想要完全自定义,那么还是推荐使用自定义Dockerfile
的方式