本文介绍如何构建Spring Boot应用的Docker镜像。Docker是一个具有“社交”特性的linux容器管理的工具箱,允许用户发布和使用他人发布的容器镜像,一个Docker镜像就是一个容器化进程,本文介绍如何构建一个Spring Boot应用镜像。
基本的Dockerfile
一个Spring Boot应用很容易制作一个可执行的JAR文件,比如Maven可以使用mvn install
,Gradle可以使用gradle build
构建,制作可执行JAR的一个基本的Dockerfile类似这样,文件放在项目的顶级目录:
1 | FROM openjdk:8-jdk-alpine |
JAR_FILE
可以作为docker
命令的一部分作为参数传入(对于Maven、Gradle是不同的),比如对于Maven:
$ docker build --build-arg=target/*.jar -t myorg/myapp .
对于Gradle:
$ docker build --build-arg=build/libs/*.jar -t myorg/myapp .
当然,一旦确定了构建系统,你可以不需要ARG
,直接硬编码。比如对于Maven:
1 | FROM openjdk:8-jdk-alpine |
然后可以简化构建命令:
$ docker build -t myorg/myapp .
运行镜像:
1 | $ docker -p 8080:8080 myorg/myapp |
如果你想看看镜像的内部结构,可以执行:
1 | docker run -ti --entrypoint /bin/sh myorg/myapp |
Entry Point
Dockerfile的执行方式使用ENTRYPOINT
而没有使用shell打包java进程,这样做的好处是java进程可以响应KILL
信号指令,比如在本例中使用CTRL-C
中止进程。如果ENTRYPOINT的命令比较长,可以单独制作一个shell脚本,并将脚本拷贝到镜像:
1 | FROM openjdk:8-jdk-alpine |
run.sh
1 |
|
ENTRYPOINT还可以注入环境变量,比如加入运行时java命令行参数:
1 | FROM openjdk:8-jdk-alpine |
$ docker build --build-arg JAR_FILE=./target/gs-spring-boot-docker-0.1.0.jar -t ljun51/docker .
$ docker run -p 8080:8080 -e "JAVA_OPTS=-Ddebug -Xmx128m" ljun51/docker
上面的示例以Spring Boot的-Ddebug
参数输出DEBUG
日志。
上面的示例使用ENTRYPOINT
并带有明确的shell可以传递环境变量的参数给java command,但是不能传递命令行参数给Spring Boot应用。下面这样修改端口不会生效:
$ docker run -p 9000:9000 ljun51/docker --server.port=9000
不生效的原因是docker命令的--server.port=9000
部分传给了ENTRYPOINT(sh),而没有传给它启动的java进程。要修复这个问题可以通过添加CMD
:
1 | FROM openjdk:8-jdk-alpine |
$ docker build --build-arg JAR_FILE=./target/gs-spring-boot-docker-0.1.0.jar -t ljun51/docker .
$ docker run -p 9000:9000 ljun51/docker --server.port=9000
${0}
表示“command”(第一个参数),${@}
表示“command arguments”(命令行其他参数)。如果使用shell脚本,则不需要${0}
.run.sh:
1 | !/bin/sh |
到目前为止,docker配置都比较简单,生成的镜像也不是非常高效。docker镜像在JAR中打包了一个单独的文件系统层,它的大小在10MB以上,对于某些应用甚至50MB以上,我们可以通过分离成多层来改进。
使用工具构建镜像
编写一个基本的Spring Boot应用
创建一个简单的应用,src/main/java/hello/Application.java
:
1 | package hello; |
现在可以运行这个应用,而不必有Docker容器,使用Gradle:
./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar
或是用Maven:
./mvnw package && java -jar target/gs-spring-boot-docker-0.1.0.jar
访问localhost:8080会返回”Hello Docker World”。
容器化应用
Docker使用Dockerfile
文件格式指定镜像的“layers”,在Spring Boot工程的顶级目录下创建一个Dockerfile文件,文件名就叫Dockerfile
:
1 | FROM openjdk:8-jdk-alpine |
这个Dockerfile非常简单但是包含了运行Spring Boot应用需要的内容:Java和JAR文件。项目JAR文件被COPY
到容器中,并叫”app.jar”,然后执行ENTRYPOINT
,没有shell包裹java进程。
文件中添加了一个指向”/tmp”的VOLUME
,是因为默认情况下Spring Boot应用在该目录中创建工作目录。实际结果是在主机上的“/var/lib/docker”下创建一个临时文件,并将其链接到“/tmp”下的容器。对于我们在此处编写的简单应用程序,此步骤是可选的,但对于其他Spring Boot应用程序,如果它们需要实际在文件系统中进行写操作,则可能是必需的。
为减少Tomcat启动时间,添加了一个系统属性指向了"/dev/urandom"作为熵的来源,如果使用的是较新的Spring Boot或Tomcat的标准版本,这不是必须的。
为了利用Spring Boot胖JAR文件中的依赖项和应用程序资源之间的明确分割,我们将只要稍微不同的Dockerfile实现:
1 | FROM openjdk:8-jdk-alpine |
使用Maven构建Docker镜像
在Maven的pom.xml
新增插件信息,更多信息:
1 | <properties> |
该配置指定一项强制性的内容:有镜像名的仓库,镜像以ljun51/gs-spring-boot-docker
命名。
其他可选属性:
- 解压的fat jar的目录名,作为构建docker镜像的参数可以通过
<buildArgs/>
插件配置指定。 - 镜像标签,如果未指定默认使用”latest”,可以通过
<tag/>
元素设置。
为了确保docker镜像创建之前JAR包被解压,添加下面的插件依赖配置:
1 | <plugin> |
使用命令行构建docker镜像:
$ ./mvnw install dockerfile:build
推送镜像到dockhub,./mvnw dockerfile:push
。Maven运行install时自动推送镜像的配置:
1 | <executions> |
使用Gradle构建Docker镜像
如果使用Gradle需要这样添加插件:
1 | buildscript { |
这个配置说明4个事情:
- 解压fat jar文件
- 创建的镜像名为
ljun51/gs-spring-boot-docker
- 解压jar file的位置,可以使用硬编码
- 指向jar file的构建参数
使用Gradle构建docker镜像并推送到dockerhub:
$ ./gradlew build docker
如果没有dockerhub的账号,推送应该会报错;推送的的步骤不是必须的,即使没有推送也是可以使用docker运行的:
1 | $ docker run -p 8080:8080 -t ljun51/gs-spring-boot-docker |
查看正在运行的docker容器:
1 | $ docker ps |
通过上面的容器ID停止运行:
1 | $ docker stop 01cae1671836 |
使用Spring Profiles
使用Spring配置文件运行刚创建的Docker镜像和将环境变量传递给Docker run命令一样比较容易:
$ docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t ljun51/gs-spring-boot-docker
或
$ docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t ljun51/gs-spring-boot-docker
在Docker容器中调试应用
可以使用JPDA Transport像调试远程服务一样。使用JAVA_OPTS环境变量传递java agent设置启用这个功能,映射agent端口到本机。使用Docker for Mac会有一些限制,可以通过一些黑魔法解决。
$ docker run -e "JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t ljun51/gs-spring-boot-docker
参考: