部署到云

Spring Boot 的可执行 jar 包已为大多数流行的云 PaaS(平台即服务)提供商做好准备。这些提供商通常要求你“自带容器”。它们管理应用进程(而非特指 Java 应用),因此需要一个中间层,将你的应用适配到云对运行进程的概念。

两个流行的云提供商,Heroku 和 Cloud Foundry,采用“构建包”方法。构建包将你部署的代码包裹在启动应用所需的一切之中。这可能是一个 JDK 和对 java 的调用,一个嵌入式 Web 服务器,或者一个完整的应用服务器。构建包是可插拔的,但理想情况下,你应该尽可能少地对其进行定制。这减少了不受你控制的功能的足迹。它最大限度地减少了开发和生产环境之间的差异。

理想情况下,你的应用,就像 Spring Boot 可执行 jar 包一样,将运行所需的一切都打包在自身内部。

在本节中,我们将了解如何将我们在“快速入门”部分开发的应用程序在云中启动并运行。

Cloud Foundry

Cloud Foundry 提供了默认的构建包,如果在未指定其他构建包的情况下,它们会发挥作用。Cloud Foundry 的Java 构建包 对 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
       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/ 找到它。

绑定服务

默认情况下,运行应用的元数据以及服务连接信息会作为环境变量暴露给应用(例如:$VCAP_SERVICES)。这一架构决策源于 Cloud Foundry 的多语言特性(任何语言和平台都可以作为构建包支持)。进程范围的环境变量与语言无关。

环境变量并非总是最易用的 API,因此 Spring Boot 会自动提取它们,并将数据展平为可通过 Spring 的 Environment 抽象访问的属性,如下例所示:

  • Java

  • Kotlin

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");
	}

	// ...

}
import org.springframework.context.EnvironmentAware
import org.springframework.core.env.Environment
import org.springframework.stereotype.Component

@Component
class MyBean : EnvironmentAware {

	private var instanceId: String? = null

	override fun setEnvironment(environment: Environment) {
		instanceId = environment.getProperty("vcap.application.instance_id")
	}

	// ...

}

所有 Cloud Foundry 属性都以前缀 vcap 开头。你可以使用 vcap 属性访问应用信息(如应用的公共 URL)和服务信息(如数据库凭据)。有关完整详情,请参阅 CloudFoundryVcapEnvironmentPostProcessor API 文档。

Java CFEnv 项目更适合配置 DataSource 等任务。

Kubernetes

Spring Boot 通过检查环境变量 "*_SERVICE_HOST""*_SERVICE_PORT" 来自动检测 Kubernetes 部署环境。你可以使用 spring.main.cloud-platform 配置属性覆盖此检测。

Kubernetes 容器生命周期

当 Kubernetes 删除一个应用实例时,关机过程同时涉及多个子系统:关机钩子、服务注销、从负载均衡器中移除实例……​由于此关机处理并行发生(并且由于分布式系统的性质),因此存在一个时间窗口,期间流量可能被路由到已开始关机处理的 Pod。

你可以在 preStop 处理器中配置一个休眠执行,以避免将请求路由到已经开始关机的 Pod。此休眠时间应足够长,以便停止将新请求路由到该 Pod,其持续时间因部署而异。

如果你使用 Kubernetes 1.32 或更高版本,可以通过 Pod 配置文件中的 PodSpec 配置 preStop 处理器,如下所示:

spec:
  containers:
  - name: "example-container"
    image: "example-image"
    lifecycle:
      preStop:
        sleep:
          seconds: 10

如果你尚未升级到 Kubernetes 1.32,可以使用 exec 命令调用 sleep

spec:
  containers:
  - name: "example-container"
    image: "example-image"
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 10"]
容器需要有 shell 环境才能使其工作。

一旦 pre-stop 钩子完成,将会向容器发送 SIGTERM 信号,并开始优雅停机,允许任何剩余的进行中请求完成。

当 Kubernetes 向 Pod 发送 SIGTERM 信号时,它会等待一段指定的时间,称为终止宽限期(默认为 30 秒)。如果在宽限期过后容器仍在运行,则会发送 SIGKILL 信号并强制移除。如果 Pod 关机时间超过 30 秒(可能是因为你增加了 spring.lifecycle.timeout-per-shutdown-phase),请务必通过在 Pod YAML 中设置 terminationGracePeriodSeconds 选项来增加终止宽限期。

Heroku

Heroku 是另一个流行的 PaaS 平台。要定制 Heroku 构建,你需要提供一个 Procfile 文件,该文件提供部署应用所需的“咒语”。Heroku 会为 Java 应用分配一个要使用的 port,然后确保到外部 URI 的路由工作正常。

你必须配置你的应用监听正确的端口。以下示例展示了我们的 starter REST 应用的 Procfile

web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar

Spring Boot 将 -D 参数作为属性提供,可通过 Spring 的 Environment 实例访问。server.port 配置属性被馈送给嵌入式 Tomcat、Jetty 或 Undertow 实例,后者在启动时使用该端口。$PORT 环境变量由 Heroku PaaS 分配给我们。

这应该就是你需要的一切。Heroku 部署最常见的部署工作流程是将代码 git push 到生产环境,如下例所示:

$ git push heroku main

结果如下:

Initializing repository, done.
Counting objects: 95, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (78/78), done.
Writing objects: 100% (95/95), 8.66 MiB | 606.00 KiB/s, done.
Total 95 (delta 31), reused 0 (delta 0)

-----> Java app detected
-----> Installing OpenJDK... done
-----> Installing Maven... done
-----> Installing settings.xml... done
-----> Executing: mvn -B -DskipTests=true clean install

       [INFO] Scanning for projects...
       Downloading: https://repo.spring.io/...
       Downloaded: https://repo.spring.io/... (818 B at 1.8 KB/sec)
		....
       Downloaded: https://s3pository.heroku.com/jvm/... (152 KB at 595.3 KB/sec)
       [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/target/...
       [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/pom.xml ...
       [INFO] ------------------------------------------------------------------------
       [INFO] BUILD SUCCESS
       [INFO] ------------------------------------------------------------------------
       [INFO] Total time: 59.358s
       [INFO] Finished at: Fri Mar 07 07:28:25 UTC 2014
       [INFO] Final Memory: 20M/493M
       [INFO] ------------------------------------------------------------------------

-----> Discovering process types
       Procfile declares types -> web

-----> Compressing... done, 70.4MB
-----> Launching... done, v6
       https://agile-sierra-1405.herokuapp.com/ deployed to Heroku

To [email protected]:agile-sierra-1405.git
 * [new branch]      main -> main

你的应用现在应该已在 Heroku 上启动并运行。有关更多详细信息,请参阅将 Spring Boot 应用部署到 Heroku

OpenShift

OpenShift 有许多资源描述如何部署 Spring Boot 应用,包括:

Amazon Web Services (AWS)

Amazon Web Services 提供多种方法来部署基于 Spring Boot 的应用,无论是作为传统的 Web 应用 (war) 还是带嵌入式 Web 服务器的可执行 jar 文件。选项包括:

  • AWS Elastic Beanstalk

  • AWS Code Deploy

  • AWS OPS Works

  • AWS Cloud Formation

  • AWS Container Registry

每种方式都有不同的功能和定价模型。在本文档中,我们将介绍使用 AWS Elastic Beanstalk 的方法。

AWS Elastic Beanstalk

如官方的Elastic Beanstalk Java 指南所述,部署 Java 应用主要有两种选项。你可以使用“Tomcat 平台”或“Java SE 平台”。

使用 Tomcat 平台

此选项适用于生成 war 文件的 Spring Boot 项目。无需特殊配置。你只需要遵循官方指南即可。

使用 Java SE 平台

此选项适用于生成 jar 文件并运行嵌入式 Web 容器的 Spring Boot 项目。Elastic Beanstalk 环境在端口 80 上运行一个 nginx 实例,用于代理运行在端口 5000 上的实际应用。要进行配置,请将以下行添加到你的 application.properties 文件中:

  • Properties

  • YAML

server.port=5000
server:
  port: 5000
上传二进制文件而不是源代码

默认情况下,Elastic Beanstalk 会上传源代码并在 AWS 中编译。然而,最好是上传二进制文件。为此,请将类似于以下的行添加到你的 .elasticbeanstalk/config.yml 文件中:

deploy:
	artifact: target/demo-0.0.1-SNAPSHOT.jar
通过设置环境类型来降低成本

默认情况下,Elastic Beanstalk 环境是负载均衡的。负载均衡器会产生显著的成本。为了避免该成本,请将环境类型设置为“Single instance”(单实例),如Amazon 文档所述。你也可以使用 CLI 和以下命令创建单实例环境:

eb create -s

总结

这是进入 AWS 最简单的方法之一,但还有更多内容需要介绍,例如如何将 Elastic Beanstalk 集成到任何 CI / CD 工具中,使用 Elastic Beanstalk Maven 插件而非 CLI 等。有一篇博客文章更详细地介绍了这些主题。

CloudCaptain 和 Amazon Web Services

CloudCaptain 的工作方式是将你的 Spring Boot 可执行 jar 或 war 文件转换为一个最小化的 VM 镜像,该镜像可以在 VirtualBox 或 AWS 上不变地部署。CloudCaptain 与 Spring Boot 深度集成,并利用 Spring Boot 配置文件中的信息自动配置端口和健康检查 URL。CloudCaptain 利用这些信息不仅用于其生成的镜像,还用于其配置的所有资源(实例、安全组、弹性负载均衡器等)。

一旦你创建了 CloudCaptain 账户,将其连接到你的 AWS 账户,安装了最新版本的 CloudCaptain Client,并确保应用已通过 Maven 或 Gradle 构建(例如,使用 mvn clean package),你就可以使用类似于以下的命令将你的 Spring Boot 应用部署到 AWS:

$ boxfuse run myapp-1.0.jar -env=prod

有关更多选项,请参阅boxfuse run 文档。如果当前目录中存在 boxfuse.conf 文件,则会考虑该文件。

默认情况下,CloudCaptain 在启动时激活名为 boxfuse 的 Spring Profile。如果你的可执行 jar 或 war 文件包含一个 application-boxfuse.properties 文件,CloudCaptain 将基于该文件中的属性进行配置。

此时,CloudCaptain 会为你的应用创建镜像,上传,并在 AWS 上配置和启动必要的资源,产生类似于以下示例的输出:

Fusing Image for myapp-1.0.jar ...
Image fused in 00:06.838s (53937 K) -> axelfontaine/myapp:1.0
Creating axelfontaine/myapp ...
Pushing axelfontaine/myapp:1.0 ...
Verifying axelfontaine/myapp:1.0 ...
Creating Elastic IP ...
Mapping myapp-axelfontaine.boxfuse.io to 52.28.233.167 ...
Waiting for AWS to create an AMI for axelfontaine/myapp:1.0 in eu-central-1 (this may take up to 50 seconds) ...
AMI created in 00:23.557s -> ami-d23f38cf
Creating security group boxfuse-sg_axelfontaine/myapp:1.0 ...
Launching t2.micro instance of axelfontaine/myapp:1.0 (ami-d23f38cf) in eu-central-1 ...
Instance launched in 00:30.306s -> i-92ef9f53
Waiting for AWS to boot Instance i-92ef9f53 and Payload to start at https://52.28.235.61/ ...
Payload started in 00:29.266s -> https://52.28.235.61/
Remapping Elastic IP 52.28.233.167 to i-92ef9f53 ...
Waiting 15s for AWS to complete Elastic IP Zero Downtime transition ...
Deployment completed successfully. axelfontaine/myapp:1.0 is up and running at https://myapp-axelfontaine.boxfuse.io/

你的应用现在应该已在 AWS 上启动并运行。

有关在 EC2 上部署 Spring Boot 应用的博客文章以及CloudCaptain Spring Boot 集成的文档,可帮助你使用 Maven 构建运行应用。

Azure

这篇入门指南将引导你将 Spring Boot 应用部署到 Azure Spring CloudAzure App Service

Google Cloud

Google Cloud 提供多种选项来启动 Spring Boot 应用。最容易上手可能是 App Engine,但你也可以找到方法在 Container Engine 中通过容器或在 Compute Engine 中通过虚拟机运行 Spring Boot。

要将你的第一个应用部署到 App Engine 标准环境,请遵循此教程

另外,App Engine Flex 要求你创建一个 app.yaml 文件来描述应用所需的资源。通常,你会将此文件放在 src/main/appengine 目录下,它应类似于以下文件:

service: "default"

runtime: "java17"
env: "flex"

handlers:
- url: "/.*"
  script: "this field is required, but ignored"

manual_scaling:
  instances: 1

health_check:
  enable_health_check: false

env_variables:
  ENCRYPT_KEY: "your_encryption_key_here"

你可以通过将项目 ID 添加到构建配置中来部署应用(例如,使用 Maven 插件),如下例所示:

<plugin>
	<groupId>com.google.cloud.tools</groupId>
	<artifactId>appengine-maven-plugin</artifactId>
	<version>2.4.4</version>
	<configuration>
		<project>myproject</project>
	</configuration>
</plugin>

然后使用 mvn appengine:deploy 进行部署(你需要先进行认证,否则构建会失败)。