Appearance
Spring In Action 6th:部署 Spring
1. 权衡部署选项
你可以通过以下几种方式构建和运行 Spring Boot 应用程序:
- 直接在 IDE 中运行应用程序,可以使用 Spring Tool Suite 或 IntelliJ IDEA;
- 使用 Maven 的
springboot:run目标或 Gradle 的bootRun任务从命令行运行应用程序; - 使用 Maven 或 Gradle 生成可在命令行运行或部署到云端的可执行 JAR 文件;
- 使用 Maven 或 Gradle 生成可以部署到传统 Java 应用服务器的 WAR 文件;
- 使用 Maven 或 Gradle 生成可以部署到任何支持容器的地方(包括 Kubernetes 环境)的容器镜像;
这些选择中的任何一个都适合在你还在开发应用程序时运行它。但是,当你准备将应用程序部署到生产或其他非开发环境时,该怎么办呢?
尽管从 IDE 或通过 Maven 或 Gradle 运行应用程序并不被认为是生产就绪选项,但可执行 JAR 文件和传统的 Java WAR 文件无疑是将应用程序部署到生产环境的有效选项。考虑到部署 WAR 文件、JAR 文件或容器镜像的选项,你该如何选择呢?一般来说,选择取决于你是否计划将应用程序部署到传统的 Java 应用服务器或云平台,如下所述:
- 部署到平台即服务(PaaS)云:如果你计划将应用程序部署到像 Cloud Foundry 这样的 PaaS 云平台,那么可执行 JAR 文件是一个不错的选择。即使云平台支持 WAR 部署,JAR 文件格式也比为应用服务器部署设计的 WAR 格式简单得多;
- 部署到 Java 应用服务器:如果你必须将应用程序部署到 Tomcat、WebSphere、WebLogic 或任何其他传统的 Java 应用服务器,你别无选择,只能将应用程序构建为 WAR 文件;
- 部署到 Kubernetes:现代云平台越来越多地基于 Kubernetes。当部署到 Kubernetes(本身就是一个容器编排系统)时,显然的选择是将你的应用程序构建成容器镜像;
在本章中,我们将重点关注以下三种部署场景:
- 将 Spring Boot 应用程序构建为可执行 JAR 文件,可能可以推送到 PaaS 平台;
- 将 Spring Boot 应用程序作为 WAR 文件部署到如 Tomcat 这样的 Java 应用服务器;
- 将 Spring Boot 应用程序打包为 Docker 容器镜像,以便部署到任何支持 Docker 部署的平台;
首先,让我们看看构建 Spring Boot 应用程序最常见的方式:作为可执行 JAR 文件。
2. 构建可执行 JAR 文件
将 Spring 应用程序构建成可执行的 JAR 文件是相当直接的。假设你在初始化项目时选择了 JAR 打包,那么你应该能够使用以下 Maven 命令生成一个可执行的 JAR 文件:
Bash
$ mvnw package在成功构建后,生成的 JAR 文件将被放置到 target 目录中,其名称和版本基于项目的 pom.xml 文件中的 <artifactId> 和 <version> 条目(例如,tacocloud-0.0.19-SNAPSHOT.jar)。
或者,如果你正在使用 Gradle:
Bash
$ gradlew build对于 Gradle 构建,生成的 JAR 文件将在 build/libs 目录中找到。JAR 文件的名称将基于 settings.gradle 文件中的 rootProject.name 属性以及 build.gradle 中的 version 属性。
一旦你有了可执行的 JAR 文件,你可以使用 java -jar 命令来运行,就像这样:
Bash
$ java -jar tacocloud-0.0.19-SNAPSHOT.jar应用程序将运行,假设它是一个网络应用程序,会启动一个嵌入式服务器(Netty 或 Tomcat,取决于项目是否是响应式网络项目)并开始在配置的 server.port(默认为 8080)上监听请求。这对于本地运行应用程序非常好。但是你如何部署一个可执行的 JAR 文件呢?
这真的取决于你将在哪里部署应用程序。但是,如果你正在部署到 Cloud Foundry 基础设施,你可以使用 cf 命令行工具按照以下方式推送 JAR 文件:
Bash
$ cf push tacocloud -p target/tacocloud-0.0.19-SNAPSHOT.jarcf push 的第一个参数是在 Cloud Foundry 中给应用程序的名称。这个名称被用来在 Cloud Foundry 和 cf CLI 中引用应用程序,同时也被用作托管应用程序的子域名。例如,如果你的 Cloud Foundry 基础设施的应用程序域名是 cf.myorg.com,那么 Taco Cloud 应用程序将可以在 https://tacocloud.cf.myorg.com 上访问。
部署可执行 JAR 文件的另一种方式是将它们打包到 Docker 容器中,并在 Docker 或 Kubernetes 中运行。接下来让我们看看如何做到这一点。
3. 构建容器镜像
Docker 已经成为分发各种应用程序以在云中部署的事实标准。包括 AWS、Microsoft Azure 和 Google Cloud Platform 等在内的许多不同的云环境都接受 Docker 容器来部署应用程序。
像 Docker 这样的容器化应用程序的概念,从现实世界中用于全球运输物品的集装箱中得到启示。集装箱都有一个标准的大小和格式,无论它们的内容是什么。因此,集装箱可以轻松地堆放在船上,装载在火车上,或由卡车拉动。同样,容器化应用程序共享一个通用的容器格式,可以在任何地方部署和运行,无论内部的应用程序是什么。
从你的 Spring Boot 应用程序创建一个镜像的最基本方法是使用 docker build 命令和一个 Dockerfile,该 Dockerfile 将可执行的 JAR 文件从项目构建复制到容器镜像中。以下极其简单的 Dockerfile 就是这样做的:
Dockerfile
FROM openjdk:11.0.12-jre
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]1
2
3
4
2
3
4
Dockerfile 描述了如何创建容器镜像。因为它非常简洁,让我们逐行检查这个 Dockerfile:
第 1 行:声明我们创建的镜像将基于一个预定义的容器镜像,该镜像提供了(包括但不限于)Open JDK 11 Java 运行时;
第 2 行:创建一个变量,该变量引用项目的
target/目录中的所有 JAR 文件。对于大多数 Maven 构建,那里应该只有一个 JAR 文件。然而,通过使用通配符,我们将 Dockerfile 定义与 JAR 文件的名称和版本解耦。JAR 文件的路径假设 Dockerfile 在 Maven 项目的根目录中;第 3 行:将 JAR 文件从项目的
target/目录复制到容器镜像中,命名为app.jar;第 4 行:定义一个入口点,也就是定义一个在从此镜像创建的容器启动时运行的命令,用
java -jar /app.jar运行 JAR 文件;
有了这个 Dockerfile,你可以使用 Docker 命令行工具创建镜像,就像这样:
Bash
$ docker build . -t habuma/tacocloud:0.0.19-SNAPSHOT这个命令中的 . 引用的是 Dockerfile 位置的相对路径。如果你从不同的路径运行 docker build,请将 . 替换为 Dockerfile 的路径(不包括文件名)。例如,如果你从项目的父目录运行 docker build,你将像这样使用 docker build:
Bash
$ docker build tacocloud -t habuma/tacocloud:0.0.19-SNAPSHOT-t 参数后面给出的值是镜像标签,由名称和版本组成。在这种情况下,镜像名称是 habuma/tacocloud,版本是 0.0.19-SNAPSHOT。如果你想试试,你可以使用 docker run 来运行这个新创建的镜像:
Bash
$ docker run -p8080:8080 habuma/tacocloud:0.0.19-SNAPSHOT-p8080:8080 将主机机器(例如,你正在运行 Docker 的机器)上的 8080 端口的请求转发到容器的 8080 端口(Tomcat 或 Netty 在此监听请求)。
如果你已经有一个可执行的 JAR 文件,那么这种方式构建 Docker 镜像是非常简单的,但这并不是从 Spring Boot 应用程序创建镜像的最简单方式。从 Spring Boot 2.3.0 开始,你可以在不添加任何特殊依赖或配置文件,或以任何方式编辑你的项目的情况下构建容器镜像。这是因为 Spring Boot 的 Maven 和 Gradle 构建插件都直接支持构建容器镜像。要将你的 Maven 构建的 Spring 项目构建成一个容器镜像,你可以使用 Spring Boot Maven 插件的 build-image 目标,就像这样:
Bash
$ mvnw spring-boot:build-image同样,一个 Gradle 构建的项目可以像这样构建成一个容器镜像:
Bash
$ gradlew bootBuildImage这将构建一个带有默认标签的镜像,该标签基于 pom.xml 文件中的 <artifactId> 和 <version> 属性。对于 Taco Cloud 应用程序,这将是类似于 library/tacocloud:0.0.19-SNAPSHOT 的东西。我们将在一会儿看到如何指定自定义的镜像标签。
Spring Boot 的构建插件依赖 Docker 来创建镜像。因此,你需要在构建镜像的机器上安装 Docker 运行时。一旦镜像被创建,你可以像这样运行它:
Bash
$ docker run -p8080:8080 library/tacocloud:0.0.19-SNAPSHOT这将运行镜像并将镜像的 8080 端口(嵌入式的 Tomcat 或 Netty 服务器正在监听)暴露给主机机器的 8080 端口。
标签的默认格式是 docker.io/library/${project.artifactId}:${project.version},这解释了为什么标签以 library 开头。如果你只在本地运行镜像,那么这是可以的。但是你最可能想要将镜像推送到像 DockerHub 这样的镜像仓库,并需要用一个引用你的镜像仓库名称的标签来构建镜像。
例如,假设你的组织在 DockerHub 中的仓库名称是 tacocloud。在这种情况下,你会希望镜像名称是 tacocloud/tacocloud:0.0.19-SNAPSHOT。要将 library 默认前缀替换为 tacocloud,你只需要在构建镜像时指定一个构建属性。对于 Maven,你将使用 spring-boot.build-image.imageName JVM 系统属性来指定镜像名称,就像这样:
Bash
$ mvnw spring-boot:build-image \
-Dspring-boot.build-image.imageName=tacocloud/tacocloud:0.0.19-SNAPSHOT1
2
2
对于一个 Gradle 构建的项目,这个过程稍微简单一些。你可以使用 --imageName 参数来指定镜像名称,就像这样:
Bash
$ gradlew bootBuildImage --imageName=tacocloud/tacocloud:0.0.19-SNAPSHOT注意,我们可以利用构建变量,而不是硬编码 artifact ID 和 version,使这些值引用构建中已经在其他地方指定的值。这消除了在项目演进过程中手动修改镜像名称中的版本号的需要。
在 Maven 中,你可以在 Spring Boot Maven 插件中作为配置项来指定镜像名称。例如,以下来自项目的 pom.xml 文件的片段展示了如何在 <configuration> 块中指定镜像名称:
XML
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>tacocloud/${project.artifactId}:${project.version}</name>
</image>
</configuration>
</plugin>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
对于一个 Gradle 构建的项目,build.gradle 中的以下条目可以达到同样的效果:
Gradle
bootBuildImage {
imageName = "tacocloud/${rootProject.name}:${version}"
}1
2
3
2
3
有了项目构建规范中的这个配置,你可以在命令行中构建镜像,而不需要指定镜像名称,就像我们之前做的那样。此时,你可以像之前那样使用 docker run 运行镜像(通过其新名称引用镜像),或者你可以使用 docker push 将镜像推送到像 DockerHub 这样的镜像仓库,如下所示:
Bash
$ docker push tacocloud/tacocloud:0.0.19-SNAPSHOT一旦镜像在镜像仓库中,任何有权访问该仓库的环境都可以拉取并运行它。运行镜像的一个越来越常见的地方是 Kubernetes。让我们看看如何在 Kubernetes 中运行一个镜像。
3.1. 部署到 Kubernetes
Kubernetes 是一个令人惊叹的容器编排平台,它运行镜像,根据需要对容器进行扩缩,并修复损坏的容器以增加鲁棒性,等等。
Kubernetes 是部署应用程序的强大平台,实际上,它如此强大,以至于我们无法在这一章中详细介绍它。相反,我们将专注于部署一个构建成容器镜像的 Spring Boot 应用程序到 Kubernetes 集群所需的任务。如果你想更深入地了解 Kubernetes,可以查看 Marko Lukša 的《Kubernetes 实战,第二版》。
Kubernetes 已经赢得了难以使用的名声,但是在 Kubernetes 中部署一个已经构建为容器镜像的 Spring 应用程序实际上非常简单,考虑到 Kubernetes 所提供的所有好处,这是值得的。
你需要一个 Kubernetes 环境来部署你的应用程序。有几个可用的选项,包括 Amazon 的 AWS EKS 和 Google Kubernetes Engine(GKE)。对于本地实验,你也可以使用其它 Kubernetes 的实现来运行 Kubernetes 集群,如 MiniKube、MicroK8s 和我个人最喜欢的 Kind。
你需要做的第一件事是创建一个部署清单。部署清单是一个 YAML 文件,描述了如何部署一个镜像。作为一个简单的例子,考虑以下部署清单,它在 Kubernetes 集群中部署了之前创建的 Taco Cloud 镜像:
YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: taco-cloud-deploy
labels:
app: taco-cloud
spec:
replicas: 3
selector:
matchLabels:
app: taco-cloud
template:
metadata:
labels:
app: taco-cloud
spec:
containers:
- name: taco-cloud-container
image: tacocloud/tacocloud:latest1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这个清单可以命名为你喜欢的任何名称。但是为了讨论,我们假设你将它命名为 deploy.yaml 并将它放在项目根目录下名为 k8s 的目录中。
不深入探讨 Kubernetes 部署规范的工作细节,这里需要注意的关键事项是我们的部署被命名为 taco-cloud-deploy,并且(在底部附近)设置为基于名为 tacocloud/tacocloud:latest 的镜像部署并启动一个容器。通过给出 latest 作为版本而不是 0.0.19-SNAPSHOT,我们可以知道将使用推送到容器注册表的最新镜像。
另一个需要注意的事情是,replicas 属性设置为 3。这告诉 Kubernetes 运行时应该有三个容器实例在运行。如果,由于任何原因,这三个实例中的一个失败了,那么 Kubernetes 将自动解决问题,通过启动一个新的实例来代替它。要应用部署,你可以像这样使用 kubectl 命令行工具:
Bash
$ kubectl apply -f deploy.yaml过一会儿,你应该能够使用 kubectl get all 来查看部署的情况,包括三个 pod,每一个都在运行一个容器实例。以下是你可能会看到的样本:
Bash
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/taco-cloud-deploy-555bd8fdb4-dln45 1/1 Running 0 20s
pod/taco-cloud-deploy-555bd8fdb4-n455b 1/1 Running 0 20s
pod/taco-cloud-deploy-555bd8fdb4-xp756 1/1 Running 0 20s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/taco-cloud-deploy 3/3 3 3 20s
NAME DESIRED CURRENT READY AGE
replicaset.apps/taco-cloud-deploy-555bd8fdb4 3 3 3 20s1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
第一部分显示了三个 pod,每一个都对应我们在 replicas 属性中请求的一个实例。中间部分是部署资源本身。最后一部分是 ReplicaSet 资源,这是 Kubernetes 用来记住应该维护多少应用程序副本的特殊资源。
如果你想试用应用程序,你需要从你机器上的一个 pod 中暴露一个端口。为此,接下来显示的 kubectl port-forward 命令会派上用场:
Bash
$ kubectl port-forward pod/taco-cloud-deploy-555bd8fdb4-dln45 8080:8080在这种情况下,我选择了从 kubectl get all 列出的三个 pod 中的第一个,并请求将主机(运行 Kubernetes 集群的机器)的 8080 端口的请求转发到 pod 的 8080 端口。有了这个,你应该能够将你的浏览器指向 http://localhost:8080 来查看在指定 pod 上运行的 Taco Cloud 应用程序。
3.2. 启用优雅关闭
我们有多种方式可以使 Spring 应用程序更适应 Kubernetes,但是你最需要做的两件事是启用优雅的关闭以及活性和就绪性探针。
无论何时,Kubernetes 都可能决定关闭你的应用程序正在运行的一个或多个 pod。这可能是因为它感知到了问题,或者可能是因为有人明确要求关闭或重启 pod。无论原因如何,如果该 pod 上的应用程序正在处理请求,那么 pod 立即关闭,留下未处理的请求,这是不好的形式。这样做将导致客户端收到错误响应,并要求客户端再次发出请求。
你可以通过简单地将 server.shutdown 属性设置为 graceful 来在你的 Spring 应用程序中启用优雅的关闭,而不是让客户端承担错误。如下所示:
YAML
server:
shutdown: graceful1
2
2
通过启用优雅的关闭,Spring 将推迟应用程序的关闭,最多可达 30 秒,以处理任何正在进行的请求。在所有待处理的请求都已完成或关闭超时后,应用程序将被允许关闭。
默认的关闭超时时间是 30 秒,但你可以通过设置 spring.lifecycle.timeout-per-shutdown-phase 属性来覆盖它。例如,要将超时时间更改为 20 秒,你可以像这样设置属性:
YAML
spring:
lifecycle.timeout-per-shutdown-phase: 20s1
2
2
在等待关闭的过程中,嵌入式服务器将停止接受新的请求。这允许在关闭发生之前处理所有处理中的请求。
关闭并不是应用程序可能无法处理请求的唯一时刻。例如,在启动期间,应用程序可能需要一段时间来准备处理流量。Spring 应用程序向 Kubernetes 表示它还未准备好处理流量的一种方式是使用就绪性探针。接下来,我们将看一下如何在 Spring 应用程序中启用活性和就绪性探针。
3.3. 应用存活性和可用性
如我们在《Spring In Action 6th:Actuator》中看到的,Actuator 的健康端点提供了关于应用程序健康状况的状态。但是,这个健康状况只与应用程序所依赖的任何外部依赖项的健康状况有关,比如数据库或消息代理。即使一个应用程序在其数据库连接方面完全健康,这并不一定意味着它已经准备好处理请求,或者它甚至健康到足以保持在当前状态下运行。
Kubernetes 支持所谓的存活性和就绪性探针的概念:这些是应用程序健康状况的指标,帮助 Kubernetes 确定是否应该将流量发送到应用程序,或者是否应该重新启动应用程序以解决某些问题。Spring Boot 通过 Actuator 健康端点支持存活性和就绪性探针,作为被称为健康组(health groups)的健康端点的子集。
存活性是一个指标,表示应用程序是否健康到足以继续运行而不需要重新启动。如果一个应用程序表明其存活性指标为 DOWN,那么 Kubernetes 运行时可以对此做出反应,终止应用程序正在运行的 pod,并在其位置上启动一个新的。
另一方面,就绪性告诉 Kubernetes 应用程序是否准备好处理流量。例如,在启动过程中,应用程序可能需要执行一些初始化操作才能开始处理请求。在此期间,应用程序的就绪性可能显示它为 DOWN。在此期间,应用程序仍然存活,所以 Kubernetes 不会重启它。但是 Kubernetes 会尊重就绪性指标,不向应用程序发送请求。一旦应用程序完成了初始化,它可以设置就绪性探针以表明它已经启动,然后 Kubernetes 就能够将流量路由到它。
3.3.1. 启用存活性和就绪性探针
要在你的 Spring Boot 应用程序中启用存活性和就绪性探针,你必须将 management.health.probes.enabled 设置为 true。在 application.yml 文件中,它看起来像这样:
YAML
management:
health:
probes:
enabled: true1
2
3
4
2
3
4
一旦启用了探针,对 Actuator 健康端点的请求将看起来像这样(假设应用程序完全健康):
JSON
{
"status": "UP",
"groups": [
"liveness",
"readiness"
]
}1
2
3
4
5
6
7
2
3
4
5
6
7
单独来看,基础健康端点并不能告诉我们太多关于应用程序的存活性或就绪性的信息。但是,对 /actuator/health/liveness 或 /actuator/health/readiness 的请求将提供应用程序的存活性和就绪性状态。在任何一种情况下,UP 状态看起来像这样:
JSON
{
"status": "UP"
}1
2
3
2
3
相反,如果就绪性或存活性任何一个为 DOWN,那么结果看起来会像这样:
JSON
{
"status": "DOWN"
}1
2
3
2
3
如果就绪性状态为 DOWN,那么 Kubernetes 将不会将流量引导到应用程序。如果存活性端点显示状态为 DOWN,那么 Kubernetes 将尝试通过删除 pod 并在其位置上启动一个新的实例来解决问题。
3.3.2. 在部署中配置存活性和就绪性探针
有了 Actuator 在这两个端点上产生的存活性和就绪性状态,我们现在需要做的就是在部署清单中告诉 Kubernetes 它们。以下部署清单的尾部显示了让 Kubernetes 知道如何检查存活性和就绪性所必需的配置:
YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: taco-cloud-deploy
labels:
app: taco-cloud
spec:
replicas: 3
selector:
matchLabels:
app: taco-cloud
template:
metadata:
labels:
app: taco-cloud
spec:
containers:
- name: taco-cloud-container
image: tacocloud/tacocloud:latest
livenessProbe:
initialDelaySeconds: 2
periodSeconds: 5
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
initialDelaySeconds: 2
periodSeconds: 5
httpGet:
path: /actuator/health/readiness
port: 80801
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这告诉 Kubernetes,对于每个探针,都要对端口 8080 上的给定路径发出 GET 请求,以获取存活性或就绪性状态。按照这里的配置,第一次请求应该在应用程序 pod 运行后的 2 秒钟发生,并且之后每隔 5 秒钟发生一次。
3.3.3. 管理存活性和就绪性
存活性和就绪性状态是如何设置的呢?在内部,Spring 本身或者应用程序依赖的某些库可以通过发布可用性更改事件来设置状态。但是,这种能力并不仅限于 Spring 及其库。你也可以在你的应用程序中编写代码来发布这些事件。
例如,假设你想要延迟你的应用程序的就绪性,直到一些初始化已经发生。在应用程序生命周期的早期,也许在一个 ApplicationRunner 或 CommandLineRunner bean 中,你可以发布一个就绪状态来拒绝流量,像这样。
Java
@Bean
public ApplicationRunner disableLiveness(ApplicationContext context) {
return args -> {
AvailabilityChangeEvent.publish(context,
ReadinessState.REFUSING_TRAFFIC);
};
}1
2
3
4
5
6
7
2
3
4
5
6
7
在这里,ApplicationRunner 被赋予了一个 Spring 应用程序上下文的实例,作为 @Bean 方法的参数。这是必要的,因为静态的 publish() 方法需要它来发布事件。一旦初始化完成,应用程序的就绪状态可以以类似的方式更新为接受流量,如下所示:
Java
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);存活性状态可以以非常相同的方式进行更新。关键的区别在于,你将发布的不是 ReadinessState.ACCEPTING_TRAFFIC 或 ReadinessState.REFUSING_TRAFFIC,而是 LivenessState.CORRECT 或 LivenessState.BROKEN。例如,如果在你的应用程序代码中检测到一个无法恢复的致命错误,你的应用程序可以通过发布 Liveness.BROKEN 来请求被杀死并重新启动,就像这样:
Java
AvailabilityChangeEvent.publish(context, LivenessState.BROKEN);在这个事件发布后不久,存活性端点将表明应用程序已经下线,Kubernetes 将通过重启应用程序来采取行动。这给你发布 LivenessState.CORRECT 事件的时间非常少。但是,如果你确定实际上应用程序是健康的,那么你可以通过发布一个新的事件来撤销损坏的事件,就像这样:
Java
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);只要 Kubernetes 在你将状态设置为损坏后还没有触及你的存活性端点,你的应用程序就可以将这视为一次危机,继续提供服务。
4. 构建并部署 WAR 文件
你已经开发了构成 Taco Cloud 应用的各个应用。你可以在 IDE 中运行它们,或者从命令行作为一个可执行的 JAR 文件来运行。无论哪种情况,一个嵌入式的 Tomcat 服务器(或者在 Spring WebFlux 应用的情况下是 Netty)总是在那里为应用提供服务。
很大程度上得益于 Spring Boot 的自动配置,你已经免于创建一个 web.xml 文件或者 Servlet 初始化类来声明 Spring MVC 的 DispatcherServlet。但是,如果你打算将应用部署到一个 Java 应用服务器,你需要构建一个 WAR 文件。并且,为了让应用服务器知道如何运行应用,你还需要在那个 WAR 文件中包含一个 Servlet 初始化器,来扮演 web.xml 文件的角色并声明 DispatcherServlet。
事实证明,将一个 Spring Boot 应用构建成一个 WAR 文件并不难。实际上,如果你在通过 Initializr 创建应用时选择了 WAR 选项,那么你就不需要做更多的事情了。
Initializr 确保生成的项目将包含一个 Servlet 初始化类,而且构建文件将被配置为生成一个 WAR 文件。然而,如果你选择从 Initializr 构建一个 JAR 文件(或者你对相关的差异感到好奇),那么请继续阅读。
首先,你需要一种方式来配置 Spring 的 DispatcherServlet。虽然这可以通过一个 web.xml 文件来完成,但是 Spring Boot 通过 SpringBootServletInitializer 使这个过程变得更加简单。SpringBootServletInitializer 是对 WebApplicationInitializer 接口的一个特殊实现。除了配置 Spring 的 DispatcherServlet,SpringBootServletInitializer 还会寻找 Spring 应用上下文中任何类型为 Filter、Servlet 或 ServletContextInitializer 的 bean,并将它们绑定到 Servlet 容器。
要使用 SpringBootServletInitializer,创建一个子类并重写 configure() 方法来指定 Spring 配置类。下一个代码列表显示了 TacoCloudServletInitializer,这是一个 SpringBootServletInitializer 的子类,你将在 Taco Cloud 应用中使用它。
Java
public class TacoCloudServletInitializer
extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder builder) {
return builder.sources(TacoCloudApplication.class);
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如你所见,configure() 方法接收一个 SpringApplicationBuilder 作为参数,并将其作为结果返回。在此过程中,它调用了 sources() 方法来注册 Spring 配置类。在这种情况下,它只注册了 TacoCloudApplication 类,该类既是引导类(用于可执行的 JAR 文件),也是 Spring 配置类。
尽管应用有其他的 Spring 配置类,但是并不需要将它们全部通过 sources() 方法注册。带有 @SpringBootApplication 注解的 TacoCloudApplication 类,隐式地启用了组件扫描。组件扫描会发现并引入它找到的任何其他配置类。
在大多数情况下,SpringBootServletInitializer 的子类就是一个模板式文件。除引用应用程序主配置类之外,完全都一样。对于每个你将构建 WAR 文件的应用都会是一样的。你几乎永远不需要对它进行任何更改。
现在你已经编写了一个 Servlet 初始化类,你必须对项目构建进行一些小的更改。如果你正在使用 Maven 构建,那么只需确保 pom.xml 中的 <packaging> 元素设置为 war 即可,如下所示:
XML
<packaging>war</packaging>对于 Gradle 构建所需的更改同样直接了当。你只需在 build.gradle 文件中应用 war 插件,如下所示:
Gradle
apply plugin: 'war'现在你已经准备好构建应用程序了。对于 Maven,您将使用 Maven 包装器执行打包脚本:
Bash
$ mvnw package如果构建成功,那么 WAR 文件可以在 target 目录中找到。
另一方面,如果你使用 Gradle 来构建项目,你可以使用 Gradle 包装器来执行 build 任务,如下所示:
Bash
$ gradlew build一旦构建完成,WAR 文件将位于 build/libs 目录中。剩下的就是部署应用程序。部署过程在各个应用服务器之间有所不同,因此请参阅你的应用服务器的特定部署过程文档。
值得注意的是,尽管你已经构建了一个适合部署到任何 Servlet 3.0(或更高版本)servlet 容器的 WAR 文件,但是 WAR 文件仍然可以在命令行中执行,就好像它是一个可执行的 JAR 文件一样,如下所示:
Bash
$ java -jar target/taco-cloud-0.0.19-SNAPSHOT.war实际上,你可以从一个单一的部署工件中获得两种部署选项!
5. 总结
Spring 应用程序可以部署在许多不同的环境中,包括传统的应用服务器和像 Cloud Foundry 这样的 PaaS 环境,或者作为 Docker 容器;
构建为可执行的 JAR 文件允许 Spring Boot 应用程序被部署到几个云平台,而无需 WAR 文件的开销;
在构建 WAR 文件时,你应该包含一个
SpringBootServletInitializer子类,以确保 Spring 的DispatcherServlet被正确配置;容器化 Spring 应用程序就像使用 Spring Boot 构建插件支持构建镜像一样简单。这些镜像然后可以部署到任何可以部署 Docker 容器的地方,包括 Kubernetes 集群;