实现制作一个springboot 的镜像,并且可以传递环境变量实现动态JVM参数和端口。
0. 准备 & cmd、entrypoint 区别
1. 准备
- springboot 项目
一个简单的springboot 项目,默认启动8001 端口,里面只有一个接口。
xxx % curl localhost:8081
index
- docker 环境
2. CMD、entrypoint 区别
在Docker中,CMD和ENTRYPOINT都是Dockerfile中用于指定容器启动时运行的命令,但它们之间有重要的区别:
CMD:
用途:CMD是可选的,它提供了一个默认的命令或参数集,可以被docker run命令中的参数覆盖。
执行方式:CMD可以以两种形式存在:
Shell形式:CMD command param1 param2 ...(内部使用/bin/sh -c 运行)
Exec形式:CMD ["executable", "param1", "param2"](不依赖shell,直接执行)
覆盖性:当使用docker run命令时,如果提供了命令,CMD的内容会被替换。
ENTRYPOINT:
用途:ENTRYPOINT是强制性的,它定义了一个容器启动时的入口点,类似于一个固定前缀,不会被docker run命令中的参数覆盖,而是将这些参数附加到ENTRYPOINT后面。
执行方式:ENTRYPOINT也支持两种形式,但通常推荐使用Exec形式,因为它更稳定且不依赖于shell。
Shell形式:ENTRYPOINT command param1 param2 ...(内部使用/bin/sh -c 运行)
Exec形式:ENTRYPOINT ["executable", "param1", "param2"]
组合性:即使docker run提供了命令,这些命令也会作为参数传递给ENTRYPOINT,而不是替换它。
举例来说,如果你有一个Dockerfile,其中设置了ENTRYPOINT ["java", "-jar", "app.jar"],然后运行docker run myimage arg1 arg2,容器将会启动并执行java -jar app.jar arg1 arg2。而如果只有CMD,docker run命令中的参数会替换CMD,不再是默认命令。
总结来说,ENTRYPOINT更像是容器的固定启动脚本,而CMD提供了默认参数,这两者结合使用可以创建出灵活且可配置的容器启动行为。
如果同一个Dockerfile 写了多个CMD或者ENTRYPOINT, 只有最后一个生效。
1. 第一版本: 构建基础能跑的
1. 编写dockerfile
# 使用官方的Java 8运行时作为基础镜像
FROM openjdk:8-jdk-alpine
# 维护者信息
LABEL maintainer="[email protected]"
# 设置工作目录
WORKDIR /app
# 将本地的JAR文件复制到容器中的工作目录
COPY app.jar /app/app.jar
# 暴露应用的端口,假设应用默认监听8081端口
EXPOSE 8081
# 使用exec形式的ENTRYPOINT以获得更好的信号处理能力
ENTRYPOINT ["/usr/bin/java", "-jar", "/app/app.jar"]
# 或者直接使用CMD,根据个人偏好和需求选择
# CMD ["java", "-jar", "app.jar"]
2. 构建和运行
xxx % docker build -t qlq_app:latest .
...
xxx % docker run -d -p 8081:8081 qlq_app
9c31ed3c0b3777a6fea2c855d1b8654e5dc949bc957f990d27a315b00384db32
2. 第二版本:增加一个sh 脚本验证cmd、entrypoint 区别
1. 操作-cmd
- 准备docker-entrypoint, 内容如下:
#!/bin/bash
# 定义日志文件路径
LOG_FILE="/app/app.log"
echo 'start~~~' >> "$LOG_FILE"
# 使用追加模式(>>)将所有参数输出到日志文件
echo "Parameters passed to the script: $*" >> "$LOG_FILE"
echo "Each parameter on a new line:" >> "$LOG_FILE"
for arg in "$@"; do
echo "$arg" >> "$LOG_FILE"
done
echo 'end~~~' >> "$LOG_FILE"
/usr/bin/java -jar /app/app.jar
- 编写Dockerfile
# 使用官方的Java 8运行时作为基础镜像
FROM openjdk:8-jdk-alpine
# 维护者信息
LABEL maintainer="[email protected]"
# 设置工作目录
WORKDIR /app
# 将本地的文件复制到容器中的工作目录
COPY app.jar /app/app.jar
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# 改用root启动
USER root
# 使用exec形式的ENTRYPOINT以获得更好的信号处理能力
# ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
# 或者直接使用CMD,根据个人偏好和需求选择
CMD ["/bin/sh", "/app/docker-entrypoint.sh"]
- 构建后执行-正常执行启动
# 构建
docker build -t app_cmd:latest .
# 启动
docker run -d -p 8081:8081 app_cmd
05f08bf21e7df4852b8e4bb146503280ec1f7fbbe1a3bf051883b0bd084ab022
# 测试
xxx % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
05f08bf21e7d app_cmd "/bin/sh /app/docker…" 4 seconds ago Up 3 seconds 0.0.0.0:8081->8081/tcp pedantic_merkle
17d8e50240b9 portainer/portainer-ce "/portainer" 6 weeks ago Up 4 hours 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp, 9443/tcp portainer
xxx % curl localhost:8081
index%
# 进入容器查看日志
/app # cat app.log
start~~~
Parameters passed to the script:
Each parameter on a new line:
end~~~
- 尝试启动传递其他命令
# 启动传递其他命令
xxx % docker run -d -p 8081:8081 app_cmd sh -c "cat /app/app.log"
bff4d7c8144db99664fb46f4c04fe1d2b8e448e8d1675dbdae77ebac61459f44
# 查看容器日志
xxx % docker logs bff4d7c8144db99664fb46f4c04fe1d2b8e448e8d1675dbdae77ebac61459f44
cat: can't open '/app/app.log': No such file or directory
# 查看容器,可以看到。 命令被覆盖, 也就是Dockerfile CMD 指定的命令没有执行,被自己的sh -c "cat /app/app.log" 覆盖
xxx % docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bff4d7c8144d app_cmd "sh -c 'cat /app/app…" 11 seconds ago Exited (1) 10 seconds ago interesting_agnesi
2. 操作-entrypoint
- 操作都相同,只是Dockerfile 启动缓存entrypoint
# 使用官方的Java 8运行时作为基础镜像
FROM openjdk:8-jdk-alpine
# 维护者信息
LABEL maintainer="[email protected]"
# 设置工作目录
WORKDIR /app
# 将本地的文件复制到容器中的工作目录
COPY app.jar /app/app.jar
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# 改用root启动
USER root
# 使用exec形式的ENTRYPOINT以获得更好的信号处理能力
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
- 正常启动-没问题
- 尝试启动传递其他命令
# 启动
xxx % docker run -d -p 8081:8081 app_entry sh -c "cat /app/app.log"
d644cb6be72757959296d97b56588d14c3aa6206f6eb2bfced2c2ff683fc4818
# 查看容器状态
xxx % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d644cb6be727 app_entry "/bin/sh /app/docker…" 3 seconds ago Up 2 seconds 0.0.0.0:8081->8081/tcp gracious_proskuriakova
# 进容器查看日志
/app # cat app.log
start~~~
Parameters passed to the script: sh -c cat /app/app.log
Each parameter on a new line:
sh
-c
cat /app/app.log
end~~~
3. 两者结合
- Dockerfile
# 使用官方的Java 8运行时作为基础镜像
FROM openjdk:8-jdk-alpine
# 维护者信息
LABEL maintainer="[email protected]"
# 设置工作目录
WORKDIR /app
# 将本地的文件复制到容器中的工作目录
COPY app.jar /app/app.jar
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# 改用root启动
USER root
# 使用exec形式的ENTRYPOINT以获得更好的信号处理能力
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
CMD ["default", "argument"]
- 构建
docker build -t app_entry_cmd:latest .
- 正常执行然后查看容器内日志 (可以看到cmd 作为 参数传进去了)
# 执行
docker run -d -p 8081:8081 app_entry_cmd
# 容器内查看日志
/app # cat app.log
start~~~
Parameters passed to the script: default argument
Each parameter on a new line:
default
argument
end~~~
- 增加外部参数调用(可以看到覆盖默认的cmd参数)
# 执行
docker run -d -p 8081:8081 app_entry_cmd arg1 arg2 arg3
# 容器内查看日志
/app # cat app.log
start~~~
Parameters passed to the script: arg1 arg2 arg3
Each parameter on a new line:
arg1
arg2
arg3
end~~~
4. 结论
容器的启动脚本可以用cmd,也可以用entrypoint, 也可以两者结合。
外部命令:(sh -c "cat /app/app.log" 可以理解为自己定义的新命令)
docker run -d -p 8081:8081 app_entry sh -c "cat /app/app.log"
如果是cmd,外部的命令会替换cmd 指定的脚本,也就是cmd 指定的将不会执行;
如果是 entrypoint :外部命令会作为参数传递给自己的entrypoint 命令;
也可以两者结合,两者结合的情况下:cmd 会作为参数传递给entrypoint的程序,如果有外部命令,外部命令会覆盖cmd然后继续传给entrypoint 作为参数。
3. 终版-传递环境变量、设置hostsname,脚本动态调整启动服务参数
1. 设置环境变量启动java 程序
- Dockerfile
# 使用官方的Java 8运行时作为基础镜像
FROM openjdk:8-jdk-alpine
# 维护者信息
LABEL maintainer="[email protected]"
# 设置工作目录
WORKDIR /app
# 将本地的文件复制到容器中的工作目录
COPY app.jar /app/app.jar
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# 改用root启动
USER root
# 使用exec形式的ENTRYPOINT以获得更好的信号处理能力
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
- docker-entrypoint.sh
读取环境变量,然后设置JVM 参数
#!/bin/bash
# 定义日志文件路径
LOG_FILE="/app/app.log"
echo 'start~~~' >> "$LOG_FILE"
# 1. 处理默认参数
# 使用追加模式(>>)将所有参数输出到日志文件
echo "Parameters passed to the script: $*" >> "$LOG_FILE"
echo "Each parameter on a new line:" >> "$LOG_FILE"
for arg in "$@"; do
echo "$arg" >> "$LOG_FILE"
done
echo 'end~~~' >> "$LOG_FILE"
# 2. 处理java 相关参数
# 2.1 默认的Java选项
DEFAULT_JAVA_OPTS="-Xms256m -Xmx256g"
# 检查JAVA_OPTS环境变量是否存在
if [ -z "${JAVA_OPTS}" ]; then
echo "JAVA_OPTS is not set. Using default settings: ${DEFAULT_JAVA_OPTS}" >> "$LOG_FILE"
JAVA_OPTS="${DEFAULT_JAVA_OPTS}"
else
echo "JAVA_OPTS is set to: ${JAVA_OPTS}" >> "$LOG_FILE"
fi
# 2.2 默认的端口
DEFAULT_SERVER_PORT='8081'
if [ -z "${SERVER_PORT}" ]; then
echo "SERVER_PORT is not set. Using default SERVER_PORT: ${DEFAULT_SERVER_PORT}" >> "$LOG_FILE"
SERVER_PORT="${DEFAULT_SERVER_PORT}"
else
echo "SERVER_PORT is set to: ${SERVER_PORT}" >> "$LOG_FILE"
fi
# 执行Java应用,使用最终确定的信息
/usr/bin/java -jar ${JAVA_OPTS} -Dserver.port=${SERVER_PORT} /app/app.jar
- 打包运行测试
# build
docker build -t app:latest .
# run 默认设置
xxx % docker run -d -p 8081:8081 app
aed44593836eca3e3f5e727bfe5abe8024214d8528311634158fb9a3b458c85d
xxx % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aed44593836e app "/bin/sh /app/docker…" 3 seconds ago Up 2 seconds 0.0.0.0:8081->8081/tcp confident_ardinghelli
xxx % curl localhost:8081
index
# 查看启动日志
/app # cat app.log
start~~~
Parameters passed to the script:
Each parameter on a new line:
end~~~
JAVA_OPTS is not set. Using default settings: -Xms256m -Xmx256g
SERVER_PORT is not set. Using default SERVER_PORT: 8081
/app # ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh /app/docker-entrypoint.sh
7 root 0:10 /usr/bin/java -jar -Xms256m -Xmx256g -Dserver.port=8081 /app/app.jar
52 root 0:00 sh
69 root 0:00 ps -ef
/app # jps -lv
7 /app/app.jar -Xms256m -Xmx256g -Dserver.port=8081
90 sun.tools.jps.Jps -Dapplication.home=/usr/lib/jvm/java-1.8-openjdk -Xms8m
- 自己设置环境变量、主机名称然后启动
# 启动
xxx tmp % docker run -d -p 8082:8082 \
-e JAVA_OPTS="-Xmx100m -Xms100m" \
-e SERVER_PORT=8082 \
--hostname 8082host \
app
9f2c71511097005bc523db236ce75b7952fac52470afb6d5cce592ebd10a0f2c
xxx tmp % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f2c71511097 app "/bin/sh /app/docker…" 3 seconds ago Up 3 seconds 0.0.0.0:8082->8082/tcp beautiful_kilby
17d8e50240b9 portainer/portainer-ce "/portainer" 6 weeks ago Up 5 hours 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp, 9443/tcp portainer
xxx tmp % curl localhost:8082
index
# 外部查看容器传递的环境变量
docker inspect 9f2c71511097
# 容器内部查看信息
xxx tmp % docker exec -it 9f2c71511097 sh
# 启动日志
/app # cat app.log
start~~~
Parameters passed to the script:
Each parameter on a new line:
end~~~
JAVA_OPTS is set to: -Xmx100m -Xms100m
SERVER_PORT is set to: 8082
# java 进程信息
/app # jps -lv
64 sun.tools.jps.Jps -Dapplication.home=/usr/lib/jvm/java-1.8-openjdk -Xms8m
7 /app/app.jar -Xmx100m -Xms100m -Dserver.port=8082
# 环境变量
/app # env
JAVA_ALPINE_VERSION=8.212.04-r0
HOSTNAME=8082host
SHLVL=1
HOME=/root
JAVA_VERSION=8u212
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin
JAVA_OPTS=-Xmx100m -Xms100m
LANG=C.UTF-8
JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk
PWD=/app
SERVER_PORT=8082
# 主机名称
/app # hostname
8082host
2. 设置网络,多个容器可以互相访问
1. 创建桥接网络
1. 创建自定义网络: 首先,使用 docker network create 命令创建一个自定义的Docker网络。例如,创建一个名为 my_network 的桥接网络:
# 创建
docker network create my_network
# 查看
xxx tmp % docker network ls
NETWORK ID NAME DRIVER SCOPE
8668a26df163 bridge bridge local
1135abf81b7e host host local
d462de8fcc37 my_network bridge local
2357d5b05bcd none null local
# 查看详细信息
xxx tmp % docker network inspect my_network
[
{
"Name": "my_network",
"Id": "d462de8fcc3734136d0ba33231de22123a98091c2d2bc7e10b1cbb938c92fe4a",
"Created": "2024-05-09T08:30:19.530915045Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
2. 启动两个容器&测试
- 启动
docker run -d -p 8082:8082 \
-e JAVA_OPTS="-Xmx100m -Xms100m" \
-e SERVER_PORT=8082 \
--hostname 8082host \
--network my_network \
app
docker run -d -p 8083:8083 \
-e JAVA_OPTS="-Xmx100m -Xms100m" \
-e SERVER_PORT=8083 \
--hostname 8083host \
--network my_network \
app
- 测试
1. 查看
xxx tmp % docker ps | grep app
4febe1c700fd app "/bin/sh /app/docker…" 13 seconds ago Up 13 seconds 0.0.0.0:8083->8083/tcp stupefied_hamilton
8667b58f2c43 app "/bin/sh /app/docker…" 50 seconds ago Up 49 seconds 0.0.0.0:8082->8082/tcp frosty_chebyshev
2. 查看容器详细信息
docker inspect 4febe1c700fd
# 输出
...
"Networks": {
"my_network": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"4febe1c700fd",
"8083host"
],
"MacAddress": "02:42:ac:12:00:03",
"NetworkID": "d462de8fcc3734136d0ba33231de22123a98091c2d2bc7e10b1cbb938c92fe4a",
"EndpointID": "b6118332fc47744d6321f07da2d289002fe28cd20c0126827d3d85d96ae96582",
"Gateway": "172.18.0.1",
"IPAddr
...
3. 容器内部ping
可以用主机名称互相ping 通,证明可以访问到
ping 8082host
ping 8083host
标签:Springboot,app,cmd,jar,sh,entrypoint,设置,docker,8081
From: https://www.cnblogs.com/qlqwjy/p/18183265