19.4 在Docker容器中运行Spring Boot

19.4 在Docker容器中运行Spring Boot

在分发云中部署的各种应用时,Docker已经成为事实标准。很多云环境都接受以Docker容器的形式部署应用,包括AWS、Microsoft Azure、Google CloudPlatform和Pivotal Web Services(简单举例)。

容器化应用程序(比如使用Docker创建的应用程序)的概念借鉴了现实世界中的联运集装箱。在运输过程中,不管里面的东西是什么,所有的联运集装箱都有一个标准的尺寸和格式。正因为如此,联运集装箱才能够很容易地堆放在船上、火车上或卡车上。按照类似的方式,容器化的应用程序遵循通用的容器格式,可以在任何地方部署和运行,而不必关心里面的应用是什么。

尽管创建Docker镜像并不困难,但是Spotify提供了一个Maven插件,借助它我们可以轻而易举地将Spring Boot的构建结果创建为Docker容器。要使用该Docker插件,需要将其添加到Spring Boot项目pom.xml文件的<build>/<plugins>代码块下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<build>
<plugins>
...
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.3</version>
<configuration>
<repository>
${docker.image.prefix}/${project.artifactId}
</repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>

<configuration>代码块下,我们设置了一些属性,用于指导如何创建Docker镜像。<repository>描述了在Docker仓库中该Docker镜像的名称。按照这里的设置,其名称是Maven项目的artifact ID,加上Maven属性docker.image.prefix的值作为前缀。项目的artifact ID是Maven已知的,而前缀属性则需要我们进行设置:

1
2
3
4
<properties>
...
<docker.image.prefix>tacocloud</docker.image.prefix>
</properties>

以Taco Cloud的配料服务来讲,所形成的Docker镜像在Docker仓库的名称为tacocloud/ingredient-service。

<buildArgs>元素下面,我们声明镜像要包含Maven构建所生成的JAR文件,在这里使用Maven属性project.build.finalName来确定target目录下JAR文件的名称。

除了提供给Maven构建文件的信息之外,Docker镜像的所有定义都位于一个名叫Dockerfile的文件中。这个文件指明了新镜像要基于哪个基础镜像、要设置的环境变量、要mount的卷以及最重要的入口点(entry point)。入口点也就是基于该镜像的容器在启动时要执行的命令。对于大多数Spring Boot应用来讲,如下的Dockerfile就是一个很好的起点:

1
2
3
4
5
6
7
8
9
FROM openjdk:8-jdk-alpine
ENV SPRING_PROFILES_ACTIVE docker
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java",\
"-Djava.security.egd=file:/dev/./urandom",\
"-jar",\
"/app.jar"]

我们将Docker文件逐行拆分,将会看到它包含如下内容。

  • FROM指令声明了新镜像要基于哪个基础镜像。新的镜像会扩展基础镜像。在本例中,基础镜像为openjdk:8-jdk- alpine,这是一个基于OpenJDK 8的容器镜像。
  • ENV指令设置了环境变量。我们可以基于激活状态的profile重写一些SpringBoot应用的配置属性,所以在本镜像中,我们将SPRING_PROFILES_ACTIVE环境变量设置为docker,从而确保Spring Boot应用启动时docker是处于激活状态的profile。
  • VOLUME指令在容器中创建了一个mount点。在本例中,它在“/tmp”创建了一个mount点,所以需要的话可以将数据写入“/tmp”目录下。
  • ARG指令声明了一个要在构建期传入的参数。在本例中,它声明了名为JAR_FILE的参数,与Maven插件<buildArgs>代码块中的参数是相同的。
  • COPY指令会将给定路径下的某个文件复制到另外一个路径下。在本例中,它会将Maven插件中声明的JAR文件复制为容器中名为app.jar的文件。
  • ENTRYPOINT描述了容器启动的时候要执行什么操作。它以数组的形式指定了要执行的命令行。在本例中,它使用java命令来运行可执行的app.jar。

我们着重介绍一下ENV指令。在任何包含Spring Boot应用程序的容器镜像中设置SPRING_PROFILES_ACTIVE环境变量通常都是一个好办法。这样的话,我们可以配置一些仅在Docker下运行应用时有效的bean和配置属性。

对于配料服务,我们需要将应用程序链接到运行在单独容器中的Mongo数据库。默认情况下,Spring Data会尝试连接localhost上监听端口27017的Mongo数据库。但是,这种做法只有在本地运行的时候才有效,并不适合容器。因此,我们需要配置spring.data.mongodb.host属性,告诉Spring Data要访问哪个主机上的Mongo。

虽然我们可能还不知道Mongo数据库运行在何处,但是我们可以通过application.yml文件配置Docker特定的配置,让它在docker profile处于激活状态时配置Spring Data连接名为mongo的主机上的Mongo:

---
spring:
  profiles: docker
  data:
    mongodb:
      host: mongo

随后,当我们启动Docker容器的时候,会将mongo主机映射到一个在不同容器中运行的Mongo数据库上。现在,我们先来构建容器镜像。借助Maven包装器,执行package和dockerfile:build goal来构建JAR文件,然后构建Docker镜像:

1
$ mvnw package dockerfile:build

此时,我们可以通过docker images来校验本地镜像仓库中的镜像(为了可读性和适应本书的宽度,这里将CREATED和SIZE列删减掉了):

1
2
3
$ docker images
REPOSITORY TAG IMAGE ID
tacocloud/ingredient-service latest 7e8ed20e768e

在启动容器之前,我们需要启动Mongo数据库的容器。如下的命令显示了运行一个名为tacocloud-mongo的新容器,其中包含Mongo 3.7.9数据库:

1
$ docker run --name tacocloud-mongo -d mongo:3.7.9-xenial

现在,我们终于可以运行配料服务容器了,并链接它到刚刚启动的Mongo容器上:

1
2
3
$ docker run -p 8080:8081 \
--link tacocloud-mongo:mongo \
tacocloud/ingredient-service

这里的docker run命令有多个值得介绍的重要组件。

  • 因为我们配置了容器中的Spring Boot应用运行在8081端口上,所以-p参数可以将内部端口映射到主机的8080端口上。
  • –link参数能够将我们的容器链接到名为tacocloud-mongo的容器上,并为其分配mongo主机名,这样Spring Data就可以使用该主机名连接它了。
  • 最后,我们指定了容器要运行的镜像名称(也就是tacocloud/ingredient-service)。

现在,Docker镜像构建完成并且已经证明可以作为本地容器运行。我们可以更进一步,将镜像推送至Dockerhub或其他Docker镜像仓库。如果你有Dockerhub账号并且已经登录,那么可以使用如下的Maven命令推送镜像:

1
$ mvnw dockerfile:push

这样的话,我们可以将镜像部署到几乎所有支持Docker容器的环境中,包括AWS、Microsoft Azure和Google Cloud Platform。你可以选择任意的环境并按照平台相关的指令部署Docker镜像。