baeldung 2024-01-08
1. 概述
在本教程中,我们将重点介绍如何将 Spring Boot 应用程序容器化,以便在隔离的环境中(即容器)运行。
我们将学习如何创建一组相互依赖、通过虚拟私有网络互联的容器组合,并了解如何使用单条命令统一管理这些容器。
首先,我们创建一个简单的 Spring Boot 应用程序 docker-message-server,之后将其部署到基于 Alpine Linux 的轻量级基础镜像中。
2. 容器化独立的 Spring Boot 应用程序
作为示例,我们将创建一个名为 docker-message-server 的简单 Spring Boot 应用程序,它暴露一个端点并返回一条静态消息:
@RestController
public class DockerMessageController {
@GetMapping("/messages")
public String getMessage() {
return "Hello from Docker!";
}
}
配置好 Maven 文件后,我们可以构建一个可执行的 JAR 文件:
$> mvn clean package
接着启动 Spring Boot 应用程序:
$> java -jar target/docker-message-server-1.0.0.jar
现在,我们有了一个可运行的 Spring Boot 应用程序,可通过 localhost:8888/messages 访问。
要将其容器化,首先创建一个名为 Dockerfile 的文件,内容如下:
FROM openjdk:17-jdk-alpine
MAINTAINER baeldung.com
COPY target/docker-message-server-1.0.0.jar message-server-1.0.0.jar
ENTRYPOINT ["java","-jar","/message-server-1.0.0.jar"]
该文件包含以下信息:
- FROM:以 Java 支持的 Alpine Linux 镜像为基础(如前文所述)。
- MAINTAINER:镜像维护者信息。
- COPY:将我们的 JAR 文件复制到镜像中。
- ENTRYPOINT:容器启动时要执行的命令。必须以 JSON 数组格式定义,以便与
CMD结合使用(用于传递应用参数)。
接下来,使用 docker build 命令从 Dockerfile 构建镜像:
$> docker build --tag=message-server:latest .
最后,我们可以从该镜像运行容器:
$> docker run -p8887:8888 message-server:latest
这将在 Docker 中启动我们的应用程序,主机可通过 localhost:8887/messages 访问它。此处的关键是定义端口映射:将主机端口(8887)映射到容器内部端口(8888),后者是在 Spring Boot 应用配置中指定的。
注意:如果主机上的 8887 端口已被占用,映射将失败,此时需选择一个可用端口。
若以后台模式(detached mode)运行容器,可以使用以下命令查看其详情、停止或删除容器:
$> docker inspect message-server
$> docker stop message-server
$> docker rm message-server
2.1 更改基础镜像
我们可以轻松更换基础镜像以使用不同的 Java 版本。例如,若想使用 Amazon 提供的 Corretto 发行版,只需修改 Dockerfile 如下:
FROM amazoncorretto:17-alpine-jdk
MAINTAINER baeldung.com
COPY target/docker-message-server-1.0.0.jar message-server-1.0.0.jar
ENTRYPOINT ["java","-jar","/message-server-1.0.0.jar"]
此外,我们也可以使用自定义的基础镜像。稍后将在本教程中介绍具体方法。
3. 容器化组合式应用程序
Docker 命令和 Dockerfile 非常适合创建单个容器。但当我们需要管理多个相互隔离的应用程序组成的网络时,容器管理会迅速变得混乱。
为解决此问题,Docker 提供了 Docker Compose 工具。该工具使用 YAML 格式的配置文件,更适合管理多个容器。例如,它可以一键启停整个服务组合,或将多个服务的日志输出合并到同一个终端中。
3.1 第二个 Spring Boot 应用程序
我们来构建一个包含两个应用的示例,它们分别运行在不同的 Docker 容器中,并相互通信,对外表现为一个“整体”。为此,我们创建第二个 Spring Boot 应用 docker-product-server:
@RestController
public class DockerProductController {
@GetMapping("/products")
public String getMessage() {
return "A brand new product";
}
}
我们可以像 message-server 一样构建并启动该应用。
3.2 Docker Compose 文件
我们将两个服务的配置合并到一个名为 docker-compose.yml 的文件中:
version: '2'
services:
message-server:
container_name: message-server
build:
context: docker-message-server
dockerfile: Dockerfile
image: message-server:latest
ports:
- 18888:8888
networks:
- spring-cloud-network
product-server:
container_name: product-server
build:
context: docker-product-server
dockerfile: Dockerfile
image: product-server:latest
ports:
- 19999:9999
networks:
- spring-cloud-network
networks:
spring-cloud-network:
driver: bridge
- version:指定使用的格式版本(必填)。这里使用较新的
2版本,旧版为1。 - services:每个对象定义一个服务(即容器),此部分为必填。
- build:若提供,则 Docker Compose 可根据
Dockerfile构建镜像。- context:指定构建目录(
Dockerfile所在位置)。 - dockerfile:指定
Dockerfile的替代名称。
- context:指定构建目录(
- image:指定构建后镜像的名称;若未使用构建功能,则从本地或远程仓库拉取该镜像。
- networks:指定服务加入的网络名称,必须在
networks节中定义。 - networks(顶层):定义可用网络。本例中,Docker Compose 会自动创建一个名为
spring-cloud-network的桥接网络。若设置external: true,则使用已存在的同名网络。
在继续之前,先检查配置文件语法是否正确:
$> docker-compose config
然后,使用一条命令完成镜像构建、容器创建和启动:
$> docker-compose up --build
这将同时启动 message-server 和 product-server。
要停止容器、删除容器及关联网络,可使用相反命令:
$> docker-compose down
3.3 服务扩缩容(Scaling)
Docker Compose 的一大优势是支持服务扩缩容。例如,我们可以让 Docker 运行 3 个 message-server 容器和 2 个 product-server 容器。
但要实现这一点,需先从 docker-compose.yml 中移除 container_name(以便 Docker 自动命名),并调整端口配置以避免冲突。
对于端口,可让 Docker 将主机的一个端口范围映射到容器内的固定端口:
ports:
- 18800-18888:8888
之后即可进行扩缩容操作(注意:需使用修改后的 YAML 文件):
$> docker-compose --file docker-compose-scale.yml up -d --build --scale message-server=1 --scale product-server=1
该命令将启动 1 个 message-server 和 1 个 product-server。
若要扩展服务,可运行:
$> docker-compose --file docker-compose-scale.yml up -d --build --scale message-server=3 --scale product-server=2
此命令将额外启动 2 个 message-server 和 1 个 product-server,且不会停止已运行的容器。
4. 自定义基础镜像
我们之前使用的基础镜像 openjdk:17-jdk-alpine 是一个预装了 JDK 17 的 Alpine Linux 系统。我们也可以自行构建基础镜像(基于 Alpine 或其他操作系统)。
为此,可创建一个 Dockerfile,以 Alpine 为基础并安装所需 JDK:
FROM alpine:edge
MAINTAINER baeldung.com
RUN apk add --no-cache openjdk17
- FROM:指定基础镜像。若本地不存在,Docker 会从 DockerHub 或其他配置的远程仓库拉取。
- MAINTAINER:通常为维护者邮箱。
- RUN:在目标系统中执行 shell 命令。此处使用 Alpine 的包管理器
apk安装 OpenJDK 17。
构建并保存镜像到本地仓库:
docker build --tag=alpine-java:base --rm=true .
说明:
--tag指定镜像名称,--rm=true表示成功构建后删除中间层镜像。末尾的.表示构建上下文目录。
现在,我们就可以用自建的 alpine-java:base 镜像替代 openjdk:17-jdk-alpine。
5. Spring Boot 2.3 对 Buildpacks 的支持
Spring Boot 2.3 新增了对 Buildpacks 的支持。简而言之,无需手动编写 Dockerfile 并执行 docker build,只需运行以下命令:
$ mvn spring-boot:build-image
Gradle 用户则使用:
$ ./gradlew bootBuildImage
前提:已安装并运行 Docker。
Buildpacks 的核心理念是提供类似 Heroku 或 Cloud Foundry 等云平台的部署体验:只需运行 build-image 目标,平台会自动完成构建与部署。
此外,它还能提升构建效率。例如,Buildpacks 会生成分层的 Docker 镜像,并使用 JAR 文件的解压形式(exploded jar),从而优化缓存和构建速度。
更重要的是,当需要调整构建逻辑时,无需逐个修改多个项目的 Dockerfile,只需更新或调优 Buildpacks 的镜像构建器即可。
运行上述命令后,可通过以下命令查看生成的镜像:
docker image ls -a
输出中将包含类似如下条目:
docker-message-server 1.0.0 b535b0cc0079
其中,镜像名称和版本号与 Maven/Gradle 配置文件中定义的一致,b535b0cc0079 是镜像 ID 的简写。
启动容器的方式与之前类似,只需映射端口:
docker run -it -p9099:8888 docker-message-server:1.0.0
6. 结论
本文介绍了如何:
- 构建自定义 Docker 镜像
- 将 Spring Boot 应用作为 Docker 容器运行
- 使用 Docker Compose 管理多容器应用
通过这些方法,我们可以高效、可靠地将 Spring Boot 应用部署到容器化环境中。