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