20220526 Deploying Spring Boot Applications
前言
在部署应用程序时,Spring Boot 的灵活打包选项提供了很多选择。您可以将 Spring Boot 应用程序部署到各种云平台、虚拟机/真实机上,或者使它们在 Unix 系统上完全可执行。
1. 部署到云
Spring Boot 的可执行 jar 已为大多数流行的云 PaaS(平台即服务)供应商准备就绪。这些服务商往往要求您“自带容器”。他们管理应用程序进程(不是专门用于 Java 应用程序),因此他们需要一个中间层,使您的应用程序适应云中运行进程的概念。
两家流行的云供应商,Heroku 和 Cloud Foundry,采用了 “buildpack” 方法。buildpack 将部署的代码包装在启动应用程序所需的任何内容中。它可以是 JDK ,也可以是对 java ,嵌入式 Web 服务器或成熟的应用程序服务器的调用。一个 buildpack 是可插入的,但是理想情况下,您应该能够通过尽可能少的自定义来获得它。这减少了您无法控制的功能的占用空间。它使开发和生产环境之间的差异最小化。
理想情况下,您的应用程序像 Spring Boot 可执行 jar 一样,具有打包运行所需的一切。
在本节中,我们研究如何使在 “入门”部分中开发的简单应用程序 启动并在云中运行。
1.1. Cloud Foundry
如果未指定其他构建包,Cloud Foundry 会提供默认构建包。Cloud Foundry Java buildpack 对 Spring 应用程序(包括 Spring Boot)具有出色的支持。您可以部署独立的可执行 jar 应用程序以及传统的 .war
打包应用程序。
构建应用程序(例如,使用 mvn clean package
)并 安装 cf
命令行工具 后,使用 cf push
命令部署应用程序,将路径替换为已编译的 .jar
。在推送应用程序之前,请务必使用您的 cf
命令行客户端登录 。以下行显示使用 cf push
命令部署应用程序:
$ cf push acloudyspringtime -p target/demo-0.0.1-SNAPSHOT.jar
在前面的例子中,我们用
acloudyspringtime
替换您给cf
的任何值作为应用程序的名称
有关更多选项,请参阅 cf push
文档 。如果同一目录中存在 Cloud Foundry manifest.yml
文件,则会考虑该文件。
此时,cf
开始上传您的应用程序,产生类似于以下示例的输出:
Uploading acloudyspringtime... OK
Preparing to start acloudyspringtime... OK
-----> Downloaded app package (8.9M)
-----> Java Buildpack Version: v3.12 (offline) | https://github.com/cloudfoundry/java-buildpack.git#6f25b7e
-----> Downloading Open Jdk JRE 1.8.0_121 from https://java-buildpack.cloudfoundry.org/openjdk/trusty/x86_64/openjdk-1.8.0_121.tar.gz (found in cache)
Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.6s)
-----> Downloading Open JDK Like Memory Calculator 2.0.2_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-2.0.2_RELEASE.tar.gz (found in cache)
Memory Settings: -Xss349K -Xmx681574K -XX:MaxMetaspaceSize=104857K -Xms681574K -XX:MetaspaceSize=104857K
-----> Downloading Container Certificate Trust Store 1.0.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-certificate-trust-store/container-certificate-trust-store-1.0.0_RELEASE.jar (found in cache)
Adding certificates to .java-buildpack/container_certificate_trust_store/truststore.jks (0.6s)
-----> Downloading Spring Auto Reconfiguration 1.10.0_RELEASE from https://java-buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-1.10.0_RELEASE.jar (found in cache)
Checking status of app 'acloudyspringtime'...
0 of 1 instances running (1 starting)
...
0 of 1 instances running (1 starting)
...
0 of 1 instances running (1 starting)
...
1 of 1 instances running (1 running)
App started
恭喜!该应用程序现已上线!
应用程序上线后,您可以使用 cf apps
命令验证已部署应用程序的状态,如以下示例所示:
$ cf apps
Getting applications in ...
OK
name requested state instances memory disk urls
...
acloudyspringtime started 1/1 512M 1G acloudyspringtime.cfapps.io
...
一旦 Cloud Foundry 确认您的应用程序已部署,您应该能够在给定的 URI 上找到该应用程序。在前面的示例中,您可以在https://acloudyspringtime.cfapps.io/
找到它。
1.1.1. 绑定到服务
默认情况下,有关正在运行的应用程序的元数据以及服务连接信息作为环境变量公开给应用程序(例如:$VCAP_SERVICES
)。此架构决策是由于 Cloud Foundry 的多语言(任何语言和平台都可以作为构建包支持)性质。进程范围的环境变量与语言无关。
环境变量并不总是构成最简单的 API ,因此 Spring Boot 会自动提取它们并将数据扁平化为可以通过 Spring 的 Environment
抽象访问的属性,如下例所示:
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class MyBean implements EnvironmentAware {
private String instanceId;
@Override
public void setEnvironment(Environment environment) {
this.instanceId = environment.getProperty("vcap.application.instance_id");
}
// ...
}
所有 Cloud Foundry 属性都以 vcap
作为前缀。您可以使用 vcap
属性来访问应用程序信息(例如应用程序的公共 URL )和服务信息(例如数据库凭据)。有关完整详细信息,请参阅 CloudFoundryVcapEnvironmentPostProcessor
Java CFEnv 项目更适合诸如配置数据源之类的任务
1.2. Kubernetes
Spring Boot 通过检查环境 *_SERVICE_HOST
和 *_SERVICE_PORT
变量来自动检测 Kubernetes 部署环境。您可以使用 spring.main.cloud-platform
配置属性覆盖此检测。
Spring Boot 可帮助您 管理应用程序的状态 ,并 使用 Actuator 和 HTTP Kubernetes Probes 将其导出。
1.2.1. Kubernetes 容器生命周期
当 Kubernetes 删除一个应用程序实例时,关闭过程会同时涉及多个子系统:关闭钩子、注销服务、从负载均衡器中删除实例…… 因为这个关闭过程是并行发生的(并且由于分布式系统的性质),因此有一个窗口,在这个窗口中,流量可以被路由到一个已经开始关闭处理的 pod 。
您可以在 preStop 处理程序中配置休眠执行,以避免将请求路由到已经开始关闭的 pod 。这种休眠应该足够长,以使新请求停止路由到 pod ,并且其持续时间因部署而异。可以使用 Pod 配置文件中的 PodSpec 配置 preStop 处理程序,如下所示:
spec:
containers:
- name: "example-container"
image: "example-image"
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
一旦 pre-stop 钩子完成,SIGTERM 将被发送到容器并开始 优雅关闭 ,允许任何剩余的正在进行的请求完成。
当 Kubernetes 向 pod 发送 SIGTERM 信号时,它会等待一个称为终止宽限期的指定时间(默认为 30 秒)。如果容器在宽限期后仍在运行,则会向它们发送 SIGKILL 信号并强制删除。如果 pod 需要超过 30 秒才能关闭,这可能是因为您增加了
spring.lifecycle.timeout-per-shutdown-phase
,请确保通过在 Pod YAML 中设置terminationGracePeriodSeconds
选项来增加终止宽限期。
todo 1.3. Heroku
todo 1.4. OpenShift
todo 1.5. 亚马逊网络服务(AWS)
todo 1.6. Boxfuse 和 Amazon Web Services
todo 1.7. Azure
todo 1.8. Google Cloud
2. 安装 Spring Boot 应用程序
除了使用 java -jar
来运行 Spring Boot 应用程序外,还可以为 Unix 系统制作完全可执行的应用程序。完全可执行的 jar 可以像任何其他可执行二进制文件一样执行,也可以向 init.d
或 systemd
注册。这使得在普通生产环境中安装和管理 Spring Boot 应用程序变得非常容易。
完全可执行的 jar 文件通过在文件前面嵌入一个额外的脚本来工作。目前,一些工具不接受这种格式,因此您可能并不总是能够使用这种技术。例如,
jar -xf
可能无法提取已完全可执行的 jar 或 war。建议您仅在打算直接执行时才使 jar 或 war 完全可执行,而不是使用java -jar
运行它或将其部署到 servlet 容器中。
无法使 zip64 格式的 jar 文件完全可执行。尝试这样做将导致直接执行或使用
java -jar
运行时,jar 文件被报告为损坏。包含一个或多个 zip64 格式嵌套 jar 的标准格式 jar 文件可以是完全可执行的。
要使用 Maven
创建一个 “完全可执行” 的 jar ,请使用以下插件配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
以下示例显示了等效的 Gradle
配置:
tasks.named('bootJar') {
launchScript()
}
然后,您可以通过键入./my-application.jar
(my-application是artifact的名称)来运行应用程序。包含jar的目录用作应用程序的工作目录。
2.1. 支持的操作系统
默认脚本支持大多数 Linux 发行版,并在 CentOS 和 Ubuntu 上进行了测试。其他平台,例如 OS X 和 FreeBSD,需要使用自定义的 embeddedLaunchScript
2.2. Unix/Linux 服务
Spring Boot 应用程序可以通过使用 init.d
或 systemd
轻松启动为 Unix/Linux 服务。
2.2.1. 作为 init.d 服务安装(System V)
如果您配置 Spring Boot 的 Maven 或 Gradle 插件以生成 完全可执行的 jar ,并且您不使用自定义 embeddedLaunchScript
,则您的应用程序可以用作 init.d
服务。为此,将 jar 符号链接到 init.d
以支持标准 start
、stop
、restart
和 status
命令。
该脚本支持以下功能:
- 以拥有 jar 文件的用户身份启动服务
- 通过使用
/var/run/<appname>/<appname>.pid
跟踪应用程序的 PID - 将控制台日志写入
/var/log/<appname>.log
假设您在 /var/myapp
中安装了 Spring Boot 应用程序,要将 Spring Boot 应用程序安装为 init.d
服务,请创建一个符号链接,如下所示:
$ sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp
安装后,您可以按常规方式启动和停止服务。例如,在基于 Debian 的系统上,您可以使用以下命令启动它:
$ service myapp start
如果您的应用程序无法启动,请检查写入
/var/log/<appname>.log
的日志文件是否有错误。
您还可以使用标准操作系统工具将应用程序标记为自动启动。例如,在 Debian 上,您可以使用以下命令:
$ update-rc.d myapp defaults <priority>
保护 init.d 服务
以下是一组关于如何保护作为 init.d 服务运行的 Spring Boot 应用程序的指南。它并不打算详尽列出为强化应用程序及其运行环境而应采取的所有措施。
当以 root 身份执行时,就像使用 root 启动 init.d 服务一样,默认的可执行脚本以 RUN_AS_USER
环境变量中指定的用户身份运行应用程序。未设置环境变量时,将使用拥有 jar 文件的用户。您永远不应该以 root
身份运行 Spring Boot 应用程序,因此 RUN_AS_USER
永远不应该是 root 并且您的应用程序的 jar 文件永远不应该由 root 拥有。相反,创建一个特定用户来运行您的应用程序并设置 RUN_AS_USER
环境变量或使用 chown
以使其成为 jar 文件的所有者,如以下示例所示:
$ chown bootapp:bootapp your-app.jar
在这种情况下,默认的可执行脚本以 bootapp
用户身份运行应用程序。
为了减少应用程序的用户帐户被盗用的机会,您应该考虑阻止它使用登录 shell 。例如,您可以将帐户的 shell 设置为
/usr/sbin/nologin
您还应该采取措施防止修改应用程序的 jar 文件。首先,配置它的权限,使其不能被写入,只能被其所有者读取或执行,如下例所示:
$ chmod 500 your-app.jar
其次,如果您的应用程序或运行它的帐户遭到入侵,您还应该采取措施限制损失。如果攻击者确实获得了访问权限,他们可以使 jar 文件可写并更改其内容。防止这种情况的一种方法是使用 chattr
使其不可变,如以下示例所示:
$ sudo chattr +i your-app.jar
这将防止任何用户(包括 root)修改 jar。
如果 root 被用于控制应用程序的服务并且您 使用.conf
文件 来自定义其启动,则 .conf
文件将由 root 用户读取和评估。它应该得到相应的保护。使用 chmod
使得文件只能被所有者读取并使用 chown
使 root 成为所有者,如下例所示:
$ chmod 400 your-app.conf
$ sudo chown root:root your-app.conf
2.2.2. 作为 systemd 服务安装
systemd
是 System V init 系统的继承者,现在被许多现代 Linux 发行版使用。尽管您可以继续使用 systemd
作为 init.d
脚本,但也可以使用 systemd
“服务”脚本启动 Spring Boot 应用程序。
假设您在 /var/myapp
中安装了 Spring Boot 应用程序,要将 Spring Boot 应用程序安装为 systemd
服务,请创建一个名为 myapp.service
的脚本并将其放置在 /etc/systemd/system
目录中。以下脚本提供了一个示例:
[Unit]
Description=myapp
After=syslog.target
[Service]
User=myapp
ExecStart=/var/myapp/myapp.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
请记住为您的应用程序更改
Description
、User
和ExecStart
字段
ExecStart
字段未声明脚本操作命令,即默认使用run
命令。
请注意,与作为 init.d
服务运行时不同,运行应用程序的用户、PID 文件和控制台日志文件由 systemd
自身管理,因此必须使用“服务”脚本中的适当字段进行配置。有关更多详细信息,请参阅 服务单元配置手册页
要将应用程序标记为在系统启动时自动启动,请使用以下命令:
$ systemctl enable myapp.service
运行 man systemctl
以获取更多详细信息。
2.2.3. 自定义启动脚本
可以通过多种方式自定义由 Maven 或 Gradle 插件编写的默认嵌入式启动脚本。对于大多数人来说,使用默认脚本和一些自定义通常就足够了。如果您发现无法自定义您需要的内容,请使用 embeddedLaunchScript
选项来完全编写您自己的文件。
自定义写入时的启动脚本
在写入 jar 文件时自定义启动脚本的元素通常很有意义。例如,init.d 脚本可以提供“描述”。由于您预先知道描述(并且不需要更改),因此您最好在生成 jar 时提供它。
要自定义写入元素,请使用 Spring Boot Maven 插件的 embeddedLaunchScriptProperties
选项或 Spring Boot Gradle 插件的launchScript
的 properties
属性
默认脚本支持以下属性替换:
名称 | 描述 | Gradle 默认 | Maven 默认 |
---|---|---|---|
mode |
脚本模式 | auto |
auto |
initInfoProvides |
“INIT INFO” 的 Provides 部分 |
${task.baseName} |
${project.artifactId} |
initInfoRequiredStart |
“INIT INFO” 的 Required-Start 部分 |
$remote_fs $syslog $network |
$remote_fs $syslog $network |
initInfoRequiredStop |
“INIT INFO” 的 Required-Stop 部分 |
$remote_fs $syslog $network |
$remote_fs $syslog $network |
initInfoDefaultStart |
“INIT INFO” 的 Default-Start 部分 |
2 3 4 5 |
2 3 4 5 |
initInfoDefaultStop |
“INIT INFO” 的 Default-Stop 部分 |
0 1 6 |
0 1 6 |
initInfoShortDescription |
“INIT INFO” 的 Short-Description 部分 |
${project.description} (回退到${task.baseName} )的单行版本 |
${project.name} |
initInfoDescription |
“INIT INFO” 的 Description 部分 |
${project.description} (回退到${task.baseName} ) |
${project.description} (回退到${project.name} ) |
initInfoChkconfig |
“INIT INFO” 的 chkconfig 部分 |
2345 99 01 |
2345 99 01 |
confFolder |
默认值为 CONF_FOLDER |
包含罐子的文件夹 | 包含罐子的文件夹 |
inlinedConfScript |
对应在默认启动脚本中内联的文件脚本的引用。这可用于设置环境变量,例如 JAVA_OPTS ,在加载任何外部配置文件之前 |
||
logFolder |
LOG_FOLDER 的默认值。仅对 init.d 服务有效 |
||
logFilename |
LOG_FILENAME 的默认值。仅对 init.d 服务有效 |
||
pidFolder |
的默认值PID_FOLDER 。仅对 init.d 服务有效 |
||
pidFilename |
PID_FOLDER 中 PID 文件的名称的默认值。仅对 init.d 服务有效 |
||
useStartStopDaemon |
当 start-stop-daemon 命令可用时,是否应该使用该命令来控制进程 |
true |
true |
stopWaitTime |
以秒为单位的 STOP_WAIT_TIME 默认值。仅对 init.d 服务有效 |
60 | 60 |
在运行时自定义脚本
对于 jar 写入完成后需要自定义的脚本项,可以使用环境变量或者 配置文件
默认脚本支持以下环境属性:
变量 | 描述 |
---|---|
MODE |
操作的“模式”。默认值取决于 jar 的构建方式,但通常是 auto (这意味着它会尝试通过检查它是否是名为 init.d 的目录中的符号链接来猜测它是否是一个 init 脚本)。如果您想在前台运行脚本,您可以将其显式设置为 service 以便 `stop |
RUN_AS_USER |
将用于运行应用程序的用户。未设置时,将使用拥有 jar 文件的用户 |
USE_START_STOP_DAEMON |
当 start-stop-daemon 命令可用时,是否应该使用该命令来控制进程。默认为 true |
PID_FOLDER |
pid 文件夹的根名称(默认情况下 /var/run ) |
LOG_FOLDER |
放置日志文件的文件夹的名称(默认情况下 /var/run ) |
CONF_FOLDER |
从中读取 .conf 文件的文件夹的名称(默认情况下与 jar 文件相同的文件夹) |
LOG_FILENAME |
LOG_FOLDER (默认情况下 <appname>.log )中的日志文件的名称 |
APP_NAME |
应用程序的名称。如果 jar 是从符号链接运行的,则脚本会猜测应用程序名称。如果它不是符号链接或者您想显式设置应用程序名称,这可能很有用 |
RUN_ARGS |
要传递给程序(Spring Boot 应用程序)的参数 |
JAVA_HOME |
默认情况下,java 可执行文件的位置是通过 PATH 发现的,但是如果 $JAVA_HOME/bin/java 中有一个可执行文件,则可以显式地设置它 |
JAVA_OPTS |
启动时传递给 JVM 的选项 |
JARFILE |
jar 文件的显式位置,以防脚本用于启动实际上未嵌入的 jar |
DEBUG |
如果不为空,则在 shell 进程上设置 -x 标志,允许您查看脚本中的逻辑 |
STOP_WAIT_TIME |
在强制关闭之前停止应用程序时等待的时间(默认情况下 60 ) |
PID_FOLDER
、LOG_FOLDER
和LOG_FILENAME
变量仅对init.d
服务有效。对于systemd
,等效的自定义是通过使用“服务”脚本进行的。有关更多详细信息,请参阅 服务单元配置手册页
除了 JARFILE
和 APP_NAME
之外,上一节中列出的设置都可以通过 .conf
文件进行配置。该文件应位于 jar 文件旁边,并具有相同的名称但后缀为 .conf
而不是 .jar
。例如,一个名为 /var/myapp/myapp.jar
的 jar 使用名为 /var/myapp/myapp.conf
的配置文件,如下例所示:
myapp.conf
JAVA_OPTS=-Xmx1024M
LOG_FOLDER=/自定义/日志/文件夹
如果你不喜欢 jar 文件旁边有配置文件,你可以设置一个
CONF_FOLDER
环境变量来自定义配置文件的位置。
要了解如何正确保护此文件,请参阅 保护 init.d 服务的指南
2.3. Microsoft Windows Services
通过使用 winsw ,Spring Boot 应用程序可以作为 Windows 服务启动。
单独维护的示例 分步描述了如何为 Spring Boot 应用程序创建 Windows 服务。