SpringBoot系列: 制作Docker镜像的全过程
本文主要参考了 https://segmentfault.com/a/1190000016449865 , 感谢作者的付出. 另外, 在本文中, 演示了Windows+Maven+Docker Toolbox环境下的制作全过程.
和 CI 工具的集成, 可以参考下面文章:
https://spring.io/guides/topicals/spring-boot-docker/
https://spring.io/guides/gs/spring-boot-docker/
=======================================
Demo 性质的 Dockerfile 文件
=======================================
本 Dockerfile 仅仅适合简单的测试. 它不满足下面提及生产环境的几个要求.
FROM openjdk:-jdk-alpine ARG JAR_FILE COPY target/${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] # 指定暴露端口, 这样在容器运行时可以知道应该映射哪些端口
EXPOSE
=======================================
生产环境对 Docker 容器的要求
=======================================
1. 容器的时区应该是东 8 区
2. 容器中的程序不应该以 root 账号启动
3. 能传递 JVM 参数、Java System Properties、程序自定义参数等
=======================================
Docker 容器的规划
=======================================
默认情况下, docker 容器中的用户是 root, 该 root 就是 HostOS 的 root, 应用程序直接使用 root 账号存在较大的安全风险, 所以容器用户应该采用非 root 用户. 在我们的规划中, 容器将使用 java-app 用户, 对应的用户组为 java-app.
另外, 如果在 Dockerfile 中需要使用 sudo 命令, 推荐使用 gosu 而不是 sudo, sudo 会引起 TTY 和信号转发异常.
如果使用的是 openjdk:<version>-alpine, Dockerfile 新建用户的指令为:
RUN set -eux; \
addgroup --gid java-app; \
adduser -S -u -g java-app -h /home/java-app/ -s /bin/sh -D java-app;
如果使用的是 openjdk:<version>-slim 和标准 openjdk:<version>, Dockerfile 新建用户的指令为:
RUN set -eux; \
addgroup --gid java-app; \
adduser --system --uid --gid --home=/home/java-app/ --shell=/bin/sh --disabled-password java-app;
在创建用户 java-app 后, Dockerfile 可以使用 USER java-app 指令明确运行的用户.
容器中的目录规范如下:
/home/java-app
├── docker-entrypoint.sh
├── lib
│ └── app.jar
├── etc
├── logs
└── tmp
=======================================
功能完备 Dockerfile 文件
=======================================
-------------------------
Dockerfile 文件
-------------------------
存放位置: Dockerfile 文件应和 pom.xml 放在同一个目录下.
源码参考: https://github.com/chanjarster/dockerfile-examples/blob/master/Dockerfile
修改点有:
1. 增加了 VOLUME /tmp 指令, /tmp 目录是 Tomcat 的缺省工作目录, 加上 VOLUME /tmp 指令容器会自动映射一个目录到 Host OS 的 /var/lib/docker 下.
2. base 镜像从 openjdk:8-alpine 修改为 openjdk:8-jdk-alpine, 貌似后者是正式名称.
3. 增加 docker-entrypoint.sh 赋予执行权限, 不然会报 permission denied 错误.
FROM openjdk:-jdk-alpine ARG NAME
ARG VERSION
ARG JAR_FILE LABEL name=$NAME \
version=$VERSION # 设定时区
ENV TZ=Asia/Shanghai
RUN set -eux; \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime; \
echo $TZ > /etc/timezone # 新建用户 java-app
RUN set -eux; \
addgroup --gid java-app; \
adduser -S -u -g java-app -h /home/java-app/ -s /bin/sh -D java-app; \
mkdir -p /home/java-app/lib /home/java-app/etc /home/java-app/jmx-ssl /home/java-app/logs /home/java-app/tmp /home/java-app/jmx-exporter/lib /home/java-app/jmx-exporter/etc; \
chown -R java-app:java-app /home/java-app # 导入启动脚本
COPY --chown=java-app:java-app docker-entrypoint.sh /home/java-app/docker-entrypoint.sh
# 赋执行权限
RUN ["chmod", "+x", "/home/java-app/docker-entrypoint.sh"] # 导入 JAR
COPY --chown=java-app:java-app target/${JAR_FILE} /home/java-app/lib/app.jar USER java-app # 增加 sh 前导命令, 避免出现权限不足问题
ENTRYPOINT ["/home/java-app/docker-entrypoint.sh"] # 指定暴露端口, 这样在容器运行时可以知道应该映射哪些端口
EXPOSE #在容器运行时声明一个 volume, 在容器中的目录为 /tmp
VOLUME /tmp
-------------------------
docker-entrypoint.sh
-------------------------
存放位置: docker-entrypoint.sh 文件应和 pom.xml 放在同一个目录下.
源码参考: https://github.com/chanjarster/dockerfile-examples/blob/master/docker-entrypoint.sh
修改点有:
1. 为了减少 Tomcat 启动时间, java 启动参数中增加 /dev/urandom 作为随机数的熵.
2. 在 java 命令之前加上 exec 命令, 这样确保 pid 1是java , 而不是 sh .
#!/bin/sh set -ex; exec /usr/bin/java \
$JAVA_OPTS \
-Djava.io.tmpdir="/home/java-app/tmp" \
-Djava.security.egd=file:/dev/./urandom \
-jar \
/home/java-app/lib/app.jar \
"$@"
=======================================
pom.xml 增加 dockerfile-maven-plugin 插件
=======================================
Spotify 开源的 dockerfile-maven-plugin 插件, 可以在 maven build 的时候基于 Dockerfile 生成 docker 镜像, 需要说明的是, 该插件不是帮助我们生成 Dockerfile 文件的. 使用该插件的好处主要好处有:
1. 直接和 maven 集成;
2. 我们可以在 pom.xml 定义参数, 然后很方便第通过该插件将参数传到 Dockerfile 中.
注意:
pom.xml 目标的 artifactId 必须是全部为小写字母, 否则后续制作 docker 镜像会报网络错误, 错误内容为: Connection reset by peer: socket write error
设定 docker 镜像名的前缀和 registry 地址:
<properties>
<!--docker 镜像的组织名 -->
<docker.image.prefix>myorg</docker.image.prefix>
<!--docker registry 的路径, 如果是本地 registry, 取值为空 -->
<docker.registry>localhost:5000/</docker.registry>
</properties>
指定最终 jar 的生成规则, 并启用 dockerfile-maven-plugin 插件:
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.8</version>
<configuration>
<repository>${docker.registry}${docker.image.prefix}/${project.artifactId}</repository>
<!--指定 registry 服务器的用户和密码 -->
<!--
<username>repoUserName</username>
<password>repoPassword</password>
-->
<useMavenSettingsForAuth>true</useMavenSettingsForAuth>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>${project.build.finalName}.${project.packaging}</JAR_FILE>
<VERSION>${project.version}</VERSION>
<NAME>${project.artifactId}</NAME>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
=======================================
准备 Windows 的镜像编译环境
=======================================
docker 镜像编译需要连接一个 docker daemon, 我使用 Docker Toolbox for windows 准备环境, 下面是准备步骤:
(1) 创建一个 Docker2Boot 虚机, 名称为 vm1
docker-machine create --driver virtualbox vm1
(2) 检查所有 Docker2Boot 虚机, 会显示每个虚机是否有证书问题
docker-machine.exe ls
(3) 如果 vm1 证书有问题, 修复它
docker-machine.exe regenerate-certs vm1
(4) 设置 vm1 为缺省的 Docker2Boot 虚机
docker-machine.exe env vm1
然后照着该命令的输出, 将它们都增加 Windows 的环境变量中, 并重启机器.
(5) 验证 vm1 应该是当前 active 的 vm
docker-machine.exe active
镜像编译需要连接 docker daemon, 到底要连接哪一台机器上的 docker daemon, dockerfile-maven-plugin 插件是按下面的顺序确定目标 docker daemon 的:
1. 如果配置了 DOCKER_HOST 等一系列环境变量, 按照环境变量为准.
2. 如果没有设定环境变量, 会在本机的 ~/.docker/ 配置目录找相应的连接信息.
3. 如果是 jenkins 服务器的话, 配置目录应该是 C:\Windows\System32\config\systemprofile\.docker
因为我们已经设置了 Windows 环境变量, 不需要再关心 ~/.docker/ 目录中的配置.
=======================================
docker 镜像编译
=======================================
---------------------------------------
推荐: 使用 dockerfile-maven-plugin 插件
---------------------------------------
我是在 Windows Eclipse 中完成 maven 编译过程的.
构建 docker 镜像的 maven 命令为:
mvn clean package dockerfile:build -DskipTests
push 镜像到 docker 私服
mvn clean package dockerfile:push -DskipTests
---------------------------------------
使用 docker 命令直接编译
---------------------------------------
用 maven package 后, 会在 target 目录下生成最终项目 jar, 然后用下面命令制作 docker image
$ docker build --build-args=target/*.jar -t myorg/myapp:v1 .
docker build 的重要参数:
--build-args list , 如果 Dockerfile 中设定了 ARG, 用这个参数传入变量值
-t 设定镜像的 tag, 格式为 reps/name:version
-f 指定 Dockerfile 名称, 如果缺省, 文件名为 Dockerfile
=======================================
运行容器
=======================================
docker run -init -p 8080:8080 myorg/java-examples-1:1.0-SNAPSHOT
docker run -init -p 8080:8080 -e JAVA_OPTS='-Xmx128M -Xms128M -Dabc=xyz -Ddef=uvw' myorg/java-examples-1:1.0-SNAPSHOT
docker run -init -p 8080:8080 myorg/java-examples-1:1.0-SNAPSHOT --debug
对于 Java 8, 推荐增加下面的 JVM 参数, 用来开启容器内存使用的 hint, 防止 SpringBoot 超用内存, Java 11 之后会自动开启该选项.
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
============================
docker 微服务的优雅关闭
============================
使用 docker stop 关闭容器时, 只有 init(pid 1)进程能收到中断信号, 如果容器的pid 1 进程是 sh 进程, 它不具备转发结束信号到它的子进程的能力, 所以我们真正的java程序得不到中断信号, 也就不能实现优雅关闭. 解决思路是: 让pid 1 进程具备转发终止信号, 或者将 java 程序配成 pid 1 进程.
需要说明的是, docker stop 默认是等待10秒钟, 这个时间有点太短了, 可以加 -t 参数, 比如 -t 30 等待30秒钟.
----------------------------------
背景知识
----------------------------------
上面的 Dockerfile 的pid 1是一个 sh 命令,并不能实现优雅关闭, 需要再改进.
ENTRYPOINT/CMD 的几种写法, 会影响 pid 1 进程的产生:
写法1:
"shell" format 的 ENTRYPOINT/CMD, 不带方括号:
ENTRYPOINT top -b
#PID 1 是 /bin/sh -c shell top -b
#另外有个 pid 7 是 top -b
写法2:
"shell" format 的 ENTRYPOINT/CMD, 不带方括号, 但这次ENTRYPOINT后紧跟了一个 exec :
ENTRYPOINT exec top -b
#PID 1 是 top -b
写法3:
"exec" form 的 ENTRYPOINT/CMD, 方括号括着, 每个部分都是json字符串.
ENTRYPOINT ["top","-b"]
pid 1 进程就是 top -b
所以推荐使用"exec" form的命令, 而不是 "shell" 形式的命令.
----------------------------------
init 进程调整方案
----------------------------------
方案1: 自行确保 pid 1 是我们的java程序.
上面的 Dockerfile 可以确保 java 程序作为 pid 1进程.
方案评价: 有时候不太容易将我们的主程序调整为 pid 1 进程, 另外虽然 docker 容器推荐是单进程, 但实际情形往往不是这么理想. 本方案仅仅适合单进程容器.
方案2: 适合于 Docker 1.13 以上.
Docker 1.13以上的docker run 命令新增了 --init 参数, 加了该参数后, docker 会启用 tini 作为 init (pid 1) 进程, 该 tini 进程能够将终止信号转发给其子进程, 同时能reap 子进程, 不会出现因孤儿进程导致的线程句柄无法回收情形.
详见: https://github.com/krallin/tini
方案3(推荐): 在docker镜像中强制 tini 作为 init(pid 1) 进程, 该方案使用范围广, ENTRYPOINT 可以是任意sh脚本文件.
改造之前的 Dockerfile
ENTRYPOINT ["/docker-entrypoint.sh"]
改造后的 Dockerfile
# Add Tini
ENV TINI_VERSION v0.18.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini ENTRYPOINT ["/usr/local/bin/tini", "--", "/docker-entrypoint.sh"]
tini 文档:
https://github.com/krallin/tini
有关 docker run --init 参数的说明
http://stackoverflow.com/a/39593409/6309
===============================
更多推荐
===============================
https://efekahraman.github.io/2018/04/docker-awareness-in-java
SpringBoot系列: 制作Docker镜像的全过程的更多相关文章
- 体验SpringBoot(2.3)应用制作Docker镜像(官方方案)
关于<SpringBoot-2.3容器化技术>系列 <SpringBoot-2.3容器化技术>系列,旨在和大家一起学习实践2.3版本带来的最新容器化技术,让咱们的Java应用更 ...
- 详解SpringBoot(2.3)应用制作Docker镜像(官方方案)
关于<SpringBoot-2.3容器化技术>系列 <SpringBoot-2.3容器化技术>系列,旨在和大家一起学习实践2.3版本带来的最新容器化技术,让咱们的Java应用更 ...
- SpringBoot打包成Docker镜像
1. 本文环境 Maven:3.6.3(Maven配置参考) SpringBoot version:2.3.4.RELEASE Docker version: 19.03.11(Docker搭建参考) ...
- 制作Docker镜像的两种方式
此文已由作者朱笑天授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 一.使用docker commit命令制作docker镜像 1. pull一个centos6.6的基础镜像, ...
- [开源]制作docker镜像不依赖linux和Docker环境
背景 最近群友们经常反馈docker镜像制作起来有点麻烦,我开源的antdeploy工具虽然可以制作镜像但是必须有一个提前:有一台安装好docker的linux服务器.因为大家开发环境基本上都是win ...
- Dockerfile 自动制作 Docker 镜像(三)—— 镜像的分层与 Dockerfile 的优化
Dockerfile 自动制作 Docker 镜像(三)-- 镜像的分层与 Dockerfile 的优化 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云 ...
- Dockerfile自动制作Docker镜像(二)—— 其它常用命令
Dockerfile自动制作Docker镜像(二)-- 其它常用命令 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云服务器 c. 上一篇:Dockerf ...
- Dockerfile 自动制作 Docker 镜像(一)—— 基本命令
Dockerfile 自动制作 Docker 镜像(一)-- 基本命令 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云服务器 c. 上一篇:手动制作Do ...
- 手动制作Docker镜像
手动制作 Docker 镜像 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云服务器(用来用去感觉 Windows 的 Docker 出各种问题,比如使用 ...
随机推荐
- 【English】20190415
approximately大约 [əˈprɑ:ksɪmətli] This install will take + minutes and requires the download of appro ...
- 前端请求参数MD5加密发送后台
最近在项目开发中遇到前端发送参数加密的问题,网上查找半天也是很乱,小编自己在项目开发中总结了一下,写到博客中,希望能够帮助大家. 查看所有代码可到我的github上查看源文件,下载后在控制台查看结果即 ...
- vim之快速查找功能
vim有强大的字符串查找功能. 我们通常在vim下要查找字符串的时候, 都是输入 / 或者 ? 加 需要查找的字符串来进行搜索,比如想搜索 super 这个单词, 可以输入 /super 或者 ...
- [LeetCode]2. 两数相加
题目链接:https://leetcode-cn.com/problems/add-two-numbers/ 题目描述: 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 ...
- Django路由(url)
1.基本配置 from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', ...
- koa-router 源码由浅入深的分析(7.4.0版本的)
首先简单的介绍下什么koa-router,为什么要使用它,可以简单看下上一篇文章. 了解koa-router 首先我们来看下koa-router的源码的基本结构如下,它是由两部分组成的: ------ ...
- tensorboard 可视化网络运行过程
在 tf.summary 里设置好要查看保存的参数后运行会生成 events.out.tfevents.{time}.{machine-name} 的文件,这个就是用 tensorboard 来查看的 ...
- 转 HttpClient 设置连接超时时间
要: HttpClient 4.5版本升级后,设置超时时间的API又有新的变化,请大家关注. HttpClient升级到4.5版本后,API有很多变化,HttpClient 4之后,API一直没有太稳 ...
- Docker启动的问题解决笔记
一.错误信息1:解决VM 与 Device/Credential Guard 不兼容 错误原因: 1.出现此问题的原因是Device Guard或Credential Guard与Workstati ...
- 5行代码实现微信小程序图片上传与腾讯免费5G存储空间的使用
本文介绍了如何在微信小程序开发中使用腾讯官方提供的云开发功能快速实现图片的上传与存储,以及介绍云开发的 5G 存储空间的基本使用方法,这将大大提高微信小程序的开发效率,同时也是微信小程序系列教程的视频 ...