开篇词
该指南将引导你构建用于运行 Spring Boot 应用的 Docker 镜像。
你将创建的应用
Docker 是具有 “社交” 方面的 Linux 容器管理工具箱,允许用户发布容器镜像并使用其他人发布的镜像。Docker 镜像是运行容器化进程的秘诀,在该指南中,我们将为一个简单的 Spring Boot 应用构建一个镜像。
还有一个以 Docker 为主题的指南(尽请期待~),其中涵盖的选项比该处的多,并且更加详细。
你将需要的工具
- 大概 15 分钟左右;
- 你最喜欢的文本编辑器或集成开发环境(IDE)
- JDK 1.8 或更高版本;
- Gradle 4+ 或 Maven 3.2+
- 你还可以将代码直接导入到 IDE 中:
如果我们使用的不是 Linux 系统,则需要一个虚拟服务器。通过安装 VirtualBox,Mac 的 boot2docker 等其他工具可以为我们无缝管理它。访问 VirtualBox 的下载站点,然后为我们的计算机选择版本。下载并安装。不用担心实际运行它。
我们还需要只能在 64 位系统上运行的 Docker。有关为我们的机器搭建 Docker 的详细信息,请参见 https://docs.docker.com/installation/#installation。在继续进行之前,请确认我们可以从 Shell 运行 docker
命令。如果我们使用的是 boot2docker,则需要先运行它。
如何完成这个指南
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
- 要从头开始,移步至用 Gradle 来构建;
- 要跳过基础,执行以下操作:
- 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:
git clone https://github.com/spring-guides/gs-spring-boot-docker.git
- 切换至
gs-spring-boot-docker/initial
目录; - 跳转至该指南的搭建 Spring Boot 应用。
待一切就绪后,可以检查一下 gs-spring-boot-docker/complete
目录中的代码。
用 Gradle 来构建
首先,我们设置一个基本的构建脚本。在使用 Spring 构建应用时可以使用任何喜欢的构建系统,但此处包含使用 Gradle 和 Maven 所需的代码。如果你都不熟悉,请参阅使用 Gradle 构建 Java 项目或使用 Maven 构建 Java 项目。
创建目录结构
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
创建 Gradle 构建文件
以下是初始 Gradle 构建文件。build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'gs-spring-boot-docker'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
Spring Boot gradle 插件提供了许多方便的功能:
- 它收集类路径上的所有 jar,并构建一个可运行的单个超级 jar,这使执行和传输服务更加方便;
- 它搜索
public static void main()
方法并将其标记为可运行类; - 它提供了一个内置的依赖解析器,用于设置版本号以及匹配 Spring Boot 依赖。我们可以覆盖所需的任何版本,但默认为 Boot 选择的一组版本。
用 Maven 来构建
首先,我们搭建一个基本的构建脚本。使用 Spring 构建应用时,可以使用任何喜欢的构建系统,但是此处包含了使用 Maven 所需的弟阿玛。如果你不熟悉 Maven,请参阅使用 Maven 构建 Java 项目。
创建目录结构
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
创建 Maven 构建文件
以下是初始 Maven 构建文件。pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-spring-boot-docker</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Boot Maven 插件提供了许多方便的功能:
- 它收集类路径上的所有 jar,并构建一个可运行的单个超级 jar,这使执行和传输服务更加方便;
- 它搜索
public static void main()
方法并将其标记为可运行类; - 它提供了一个内置的依赖解析器,用于设置版本号以及匹配 Spring Boot 依赖。我们可以覆盖所需的任何版本,但默认为 Boot 选择的一组版本。
用 IDE 来构建
- 阅读如何将该指南直接导入 Spring Tool Suite;
- 阅读如何在 IntelliJ IDEA 尽情期待~ 中使用该指南。
搭建 Spring Boot 应用
现在,我们可以创建一个简单的应用。src/main/java/hello/Application.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class Application {
@RequestMapping("/")
public String home() {
return "Hello Docker World";
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
该类被标记为 @SpringBootApplication
和 @RestController
,这意味着 Spring MVC 已准备好使用该类来处理 Web 请求。@RequestMapping
将 /
映射到 home()
方法,该方法仅发送 “Hello World” 响应。main()
方法使用 Spring Boot 的 SpringApplication.run()
方法启动应用。
现在我们可以在没有 Docker 容器的情况下(即在主机 OS 中)运行应用。
如果我们使用的是 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” 文件格式,用于指定镜像的 “层”。因此,让我们继续并在我们的 Spring Boot 项目中创建一个 Dockerfile:Dockerfile
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
我们可以使用以下命令运行它(如果使用的是 Maven):
docker build -t springio/gs-spring-boot-docker .
或者(如果我们使用的是 Gradle):
docker build --build-arg JAR_FILE=build/libs/*.jar -t springio/gs-spring-boot-docker .
该命令生成一个镜像并将其标记为 springio/gs-spring-boot-docker
。
这个 Dockerfile 非常简单,但这就是运行 Spring Boot 应用所需的一切,没有多余的装饰:仅 Java 和 JAR 文件。构建将创建一个 spring 用户和一个 spring 组来运行该应用。然后,它将项目 JAR 文件作为 “app.jar” COPY
到容器中,该文件将在 ENTRYPOINT
中执行。使用 Dockerfile ENTRYPOINT
的数组形式,因此没有 shell 包装 Java 进程。有关 Docker 的主题指南(尽请期待~)对该主题进行了更详细的介绍。
为了减少 Tomcat 启动时间,我们预先添加了一个系统属性,该属性指向 “/dev/urandom” 作为熵的涞源。对于 JDK 8 或更高版本,这不再是必须的。
使用用户权限运行应用有助于减轻某些风险(例如,参见 StackExchange 上的一个线索)因此,对 Dockerfile
的一项重要改进是以非 root 用户身份运行该应用:Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
同样,为了利用 Spring Boot 的胖 Jar 文件中的依赖关系和应用资源之间的清晰分隔,我们将使用稍微不同的 Dockerfile 实现:Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
该 Dockerfile 有一个 DEPENDENCY 参数,该参数指向我们解开的胖 jar 的目录。从 Maven 构建:
$ mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
或者从 Gradle 构建:
$ mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)
如果我们做对了,则它已经包含一个包含依赖的 jar 的 BOOT-INF/lib
目录,以及一个其中包含应用类的 BOOT-INF/classes
目录。请注意,我们正在使用应用自己的主类 hello.Application
(这比使用胖 jar 启动器提供的间接调用要快)。
分解 jar 文件可能会导致类路径在运行时的顺序不同。行为良好且编写良好的应用不必理会该问题,但如果不仔细管理依赖,则可能会看到行为更改。
如果我们使用的是 boot2docker,则需要先运行它,然后再使用 Docker 命令行构建工具执行任何操作(它会运行一个守护进程,该进程为我们在虚拟机中处理工作)。
要构建镜像,我们可以使用 Docker 命令行。例如:
docker build -t springio/gs-spring-boot-docker .
从 Gradle 构建,添加显式构建参数:
docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .
当然,如果仅使用 Gradle,则只需更改
Dockerfile
,以使DEPENDENCY
的默认值与解压缩的归档文件位置匹配。
我们可能不想使用 Docker 命令行进行构建,而是要使用构建插件。Google 有一个名为 Jib 的开源工具,该工具有 Maven 和 Gradle 插件。可能最有趣的是我们不需要 docker - 它使用与从 docker build
获得的相同的标准输出来构建镜像,但除非我们要求,否则不使用 docker
- 因此在有无 docker 的环境中,它都能工作(在构建服务器中并不罕见)。
使用 Maven 构建 Docker 镜像
为了快速入门,我们可以运行 Jib 甚至不更改 pom.xml:
./mvnw com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=springio/gs-spring-boot-docker
要推送到 Docker 注册表,请使用 build 目标,而不是 dockerBuild,例即:
./mvnw com.google.cloud.tools:jib-maven-plugin:build -Dimage=springio/gs-spring-boot-docker
为此,我们将需要拥有推送到 Dockerhub 的权限,默认情况下我们没有该权限。将镜像前缀更改为我们自己的 Dockerhub ID,并 docker login
以确保在运行 Maven 之前已通过身份验证。
使用 Gradle 构建 Docker 镜像
如果我们使用的是 Gradle,则需要添加一个新的插件,如下所示:build.gradle
plugins {
...
id 'com.google.cloud.tools.jib' version '1.8.0'
}
或使用入门指南中使用的经典方式:build.gradle
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
mavenCentral()
}
dependencies {
classpath('org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE')
classpath('com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin:1.8.0')
}
}
apply plugin: 'com.google.cloud.tools.jib'
我们可以在一条命令中使用 Gradle 构建标记的 docker 镜像:
./gradlew jibDockerBuild --image=springio/gs-spring-boot-docker
与 Maven 构建一样,还有一个构建任务可以构建并推送到 Docker 注册表:
./gradlew jib --image=springio/gs-spring-boot-docker
如果我们已在命令上通过 docker
进行了身份验证,则镜像推送将从我们的本地 ~/.docker
配置进行身份验证。
推送之后
示例中的 “docker push”(或使用 “jib” 构建插件)将对我们失败(除非我们是 Dockerhub 的 “springio” 组织的一部分),但是如果我们更改配置以匹配我们自己的 docker ID,则它应该会成功,我们将拥有一个新的标记的已部署镜像。
我们无需向 docker 注册或发布任何内容即可运行在本地构建的 docker 镜像。如果我们是使用 Docker 构建的(通过命令行或 Jib),则仍然有一个本地标记的镜像,可以像这样运行它:
docker run -p 8080:8080 -t springio/gs-spring-boot-docker
2015-03-31 13:25:48.035 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037 INFO 1 --- [ main] hello.Application : Started Application in 5.613 seconds (JVM running for 7.293)
然后可以在 http://localhost:8080 上找到该应用(访问该应用,并显示 “Hello Docker World”)。
当将 Mac 与 boot2docker 结合使用时,通常会在启动时看到如下内容:
Docker client to the Docker daemon, please set:
export DOCKER_CERT_PATH=/Users/gturnquist/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://192.168.59.103:2376
要查看应用,我们必须访问 DOCKER_HOST 中的 IP 地址而不是 localhost。在这种情况下,https://192.168.59.103:8080,虚拟机面向公众的 IP。
当它运行时,我们可以在容器列表中看到,例如:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
81c723d22865 springio/gs-spring-boot-docker:latest "java -Djava.secur..." 34 seconds ago Up 33 seconds 0.0.0.0:8080->8080/tcp goofy_brown
并再次关闭它,我们可以 docker stop
后跟上面列表中的容器 ID(我们将有所不同):
docker stop goofy_brown
81c723d22865
如果我们愿意,还可以在完成后删除该容器(该容器保留在 /var/lib/docker
下的文件系统中):
docker rm goofy_brown
使用 Spring 配置
使用 Spring 配置运行刚创建的 Docker 镜像就像环境变量传递给 Docker run 命令一样容易:
docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t springio/gs-spring-boot-docker
或者
docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t springio/gs-spring-boot-docker
调试 Docker 容器中的应用
要调试应用,可以使用 JPDA Transport。因此,我们将容器视为远程服务器。要启用该功能,请在容器运行期间在 JAVA_OPTS 变量中传递 Java 代理设置,并将代理的端口映射到 localhost。使用 Docker for Mac 存在局限性,因为如果不使用黑魔法,我们就无法通过 IP 访问容器。
概述
恭喜你!我们刚刚为 Spring Boot 应用创建了 Docker 容器!Spring Boot 应用默认在容器内的 8080 端口上运行,我们在命令行上使用 “-p” 将其映射到主机上的同一端口。
参见
以下指南也可能会有所帮助:
- 使用 Spring MVC 服务 Web 内容
- 使用 Spring Boot 构建应用