借助 Docker 从 Google App Engine Java 应用迁移到 Cloud Run

1. 概览

这一系列的 Codelab(自定进度的动手教程)旨在帮助 Google App Engine(标准)Java 开发者通过一系列迁移来指导他们的应用现代化。按照这些步骤操作,您可以更新应用,使其更具可移植性,并决定将其容器化以支持 Cloud Run(App Engine 的 Google Cloud 容器托管姊妹服务)和其他容器托管服务。

本教程教您如何容器化 App Engine 应用,以便使用 Dockerfile 将其部署到 Cloud Run 全代管式服务。对于此迁移,Dockerfile 是最实用的部署方法,但它们也提供了最多的选项来定制构建流程。

除了介绍从 App Engine 迁移到 Cloud Run 所需的步骤之外,本文还将介绍如何将 Java 8 App Engine 应用升级到 Java 17。

如果您想迁移的应用大量使用 App Engine 旧版捆绑服务或其他 App Engine 特有的功能,那么访问 Java 11/17 版 App Engine 捆绑服务指南可能比此 Codelab 更适合作为起点。

在接下来的实验中

  • 使用 Cloud Shell
  • 启用 Cloud Run API、Artifact Registry API 和 Cloud Build API
  • 使用 Docker、Docker 和 Cloud Build 将应用容器化
  • 将容器映像部署到 Cloud Run

所需条件

调查问卷

您将如何使用本教程?

仅阅读教程内容 阅读并完成练习

您如何评价使用 Java 的体验?

新手水平 中等水平 熟练水平

您如何评价自己在使用 Google Cloud 服务方面的经验水平?

新手水平 中等水平 熟练水平

2. 背景

PaaS 系统(例如 App Engine 和 Cloud Functions)为您的团队和应用提供了许多便利,例如让系统管理员和 DevOps 能够专注于打造解决方案。借助无服务器平台,您的应用可根据需要自动扩缩,使用按用量计费的结算方式有助于缩容至零,并使用多种常见的开发语言。

不过,容器的灵活性也很有吸引力。容器能够选择任何语言、任何库和任何二进制文件,让您兼具无服务器的便利性和容器的灵活性。这正是 Google Cloud Run 的核心所在。

了解如何使用 Cloud Run 不在本 Codelab 的范围之内,而是在 Cloud Run 文档中介绍。这里的目标是让您熟悉如何为 Cloud Run(或其他容器托管服务)容器化 App Engine 应用。在继续前进之前,您需要了解一些事项,主要是您的用户体验会略有不同。

在此 Codelab 中,您将学习如何构建和部署容器。您将学习如何使用 Dockerfile 对应用进行容器化、从 App Engine 配置迁移,以及(可选)为 Cloud Build 定义构建步骤。这需要放弃某些 App Engine 专用功能。如果您不想遵循此路径,那么您仍然可以升级到 Java 11/17 运行时,同时将应用保持在 App Engine 上

3. 设置/准备工作

1. 设置项目

在本教程中,您将使用 appengine-java-migration-samples 代码库中的示例应用,并使用全新项目。确保该项目具有有效的结算账号。

如果您打算将现有的 App Engine 应用迁移到 Cloud Run,则可以使用该应用来完成本教程。

运行以下命令,为您的项目启用必要的 API:

gcloud services enable artifactregistry.googleapis.com cloudbuild.googleapis.com run.googleapis.com

2. 获取基准示例应用

在您自己的机器或 Cloud Shell 中克隆示例应用,然后前往 baseline 文件夹。

此示例是一个基于 Java 8 Servlet 的 Datastore 应用,旨在部署在 App Engine 上。按照 README 中的说明准备此应用以进行 App Engine 部署。

3. (可选)部署基准应用

如果您想在迁移到 Cloud Run 之前确认应用在 App Engine 上是否正常运行,则只需执行以下操作。

请参阅 README.md 中的步骤:

  1. 安装/重新熟悉 gcloud CLI
  2. 使用 gcloud init 为项目初始化 gcloud CLI
  3. 使用 gcloud app create 创建 App Engine 项目
  4. 将示例应用部署到 App Engine
./mvnw package appengine:deploy -Dapp.projectId=$PROJECT_ID
  1. 确认应用在 App Engine 上运行没有任何问题

4. 创建 Artifact Registry 仓库

将应用容器化后,您需要一个位置来推送和存储映像。在 Google Cloud 上,建议使用 Artifact Registry 来实现此目的。

使用 gcloud 创建名为 migration 的代码库,如下所示:

gcloud artifacts repositories create migration --repository-format=docker \
--description="Docker repository for the migrated app" \
--location="northamerica-northeast1"

请注意,此代码库使用 docker 格式类型,但还有多种代码库类型可供选择。

至此,您已拥有基准 App Engine 应用,并且 Google Cloud 项目已准备好将该应用迁移到 Cloud Run。

4. 修改应用文件

如果您的应用大量使用 App Engine 的旧版捆绑服务、配置或其他仅限 App Engine 的功能,我们建议您在升级到新运行时环境的同时继续访问这些服务。此 Codelab 演示了已使用独立服务或可以切实可行地重构为使用独立服务的应用的迁移途径。

1. 升级到 Java 17

如果您的应用基于 Java 8,请考虑升级到更新的 LTS 候选版本(例如 11 或 17),以便及时获取安全更新并使用新的语言功能。

首先,更新 pom.xml 中的属性,以包含以下内容:

<properties>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

这会将项目版本设置为 17,告知编译器插件您希望访问 Java 17 语言功能,并希望编译后的类与 Java 17 JVM 兼容。

2. 包括 Web 服务器

在 App Engine 和 Cloud Run 之间迁移时,需要考虑两者之间的许多差异。一个区别是,App Engine 的 Java 8 运行时环境为其托管的应用提供并管理 Jetty 服务器,而 Cloud Run 不会这样做。我们将使用 Spring Boot 为我们提供 Web 服务器和 servlet 容器。

添加以下依赖项:

<dependencies>
<!-- ... -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <version>2.6.6</version>
       <exclusions>
           <!-- Exclude the Tomcat dependency -->
           <exclusion>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-tomcat</artifactId>
           </exclusion>
       </exclusions>
   </dependency>
   <!-- Use Jetty instead -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jetty</artifactId>
       <version>2.6.6</version>
   </dependency>
<!-- ... -->
</dependencies>

Spring Boot 默认嵌入 Tomcat 服务器,但此示例将排除该制品,并坚持使用 Jetty,以最大限度地减少迁移后默认行为的差异。

3. Spring Boot 设置

虽然 Spring Boot 能够重新使用您的 servlet 而无需修改,但需要进行一些配置才能确保它们可被发现。

com.example.appengine 软件包中创建以下 MigratedServletApplication.java 类:

package com.example.appengine;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
@EnableAutoConfiguration
public class MigratedServletApplication {
    public static void main(String[] args) {
        SpringApplication.run(MigratedServletApplication.class, args);
    }
}

请注意,这包括 @ServletComponentScan 注释,该注释会(默认情况下在当前软件包中)查找任何 @WebServlets,并按预期方式提供这些 @WebServlets

4. 将应用打包为 JAR

虽然可以从 WAR 开始将应用容器化,但如果将应用打包为可执行 JAR,则会更轻松。这不需要太多配置,尤其是对于使用 Maven 作为构建工具的项目,因为 jar 打包是默认行为。

移除 pom.xml 文件中的 packaging 标记:

<packaging>war</packaging>

接下来,添加 spring-boot-maven-plugin

<plugins>
<!-- ... -->
  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.6.6</version>
  </plugin>
<!-- ... -->
</plugins>

5. 从 App Engine 配置、服务和依赖项迁移

如 Codelab 开头所述,Cloud Run 和 App Engine 旨在提供不同的用户体验。App Engine 开箱即用的一些功能(例如 Cron任务队列服务)需要手动重新创建,我们将在后续模块中详细介绍。

此示例应用未使用旧版捆绑服务,但如果您的应用使用了旧版捆绑服务,可以参阅以下指南:

由于您现在要部署到 Cloud Run,因此可以移除 appengine-maven-plugin

<plugin>
 <groupId>com.google.cloud.tools</groupId>
 <artifactId>appengine-maven-plugin</artifactId>
 <version>2.4.1</version>
 <configuration>
   <!-- can be set w/ -DprojectId=myProjectId on command line -->
   <projectId>${app.projectId}</projectId>
   <!-- set the GAE version or use "GCLOUD_CONFIG" for an autogenerated GAE version -->
   <version>GCLOUD_CONFIG</version>
 </configuration>
</plugin>

5. 将应用容器化

现在,您已准备好告知 Cloud Build 如何实际构建应用的容器。使用此容器化方法不需要单独的 build 配置文件 (cloudbuild.yaml)。我们可以简单地定义一个最基本的 Dockerfile 作为起点:

FROM eclipse-temurin

ARG JAR_FILE=JAR_FILE_MUST_BE_SPECIFIED_AS_BUILD_ARG

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-jar","/app.jar"]

此 Dockerfile 将 Spring Boot 服务的 uber-jar 版本捆绑在一个层中。这是最简单的 Dockerfile 容器化方法,但存在许多缺点,尤其是在重复多次且依赖项相对稳定的情况下。正是由于存在此类问题,这种容器化方法才被视为更高级的方法。不过,从好的方面来说,编写自己的 Dockerfile 可以完全控制基础映像,并获得精心分层映像带来的性能优势。

2**. 运行构建流程**

现在,您已告知 Cloud Build 所需的构建步骤,可以一键部署了。

运行以下命令:

gcloud builds submit --tag LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE_NAME

将上述命令中的占位值替换为以下内容:

  • LOCATION:代码库的单区域或多区域位置。
  • PROJECT_ID:您的云项目 ID。
  • REPOSITORY:您的 Artifact Registry 代码库的名称。
  • IMAGE_NAME:容器映像的名称。

该流程完成后,您的容器映像已构建完毕并存储在 Artifact Registry 中,且已部署到 Cloud Run。

完成此 Codelab 后,您的应用应与 mod4-migrate-to-cloud-run 文件夹中的应用相同。

好了,大功告成!您已成功将 Java 8 App Engine 应用迁移到 Java 17 和 Cloud Run,现在对切换和选择托管选项所涉及的工作有了更清晰的了解。

6. 总结/清理

恭喜,您已完成升级、容器化和迁移,并成功运行应用,本教程到此结束!

接下来,您可以详细了解现在可以通过 Cloud Build 部署的 CI/CD 和软件供应链安全功能:

可选:清理和/或停用服务

如果您在本教程中将示例应用部署到了 App Engine,请务必停用该应用,以免产生费用。准备好继续学习下一个 Codelab 后,您可以重新启用该功能。App Engine 应用被停用后,不会产生任何流量产生费用,但如果超出免费配额Datastore 使用量可能会计费,因此请删除足够的数据,使使用量低于该上限。

另一方面,如果您不打算继续迁移,并希望完全删除所有内容,则可以删除您的服务或完全关闭您的项目

7. 其他资源

App Engine 迁移模块 Codelab 问题/反馈

如果您发现本 Codelab 存在任何问题,请先搜索您的问题,然后再提交。用于搜索和创建新问题的链接:

迁移资源

在线资源

以下是一些可能与本教程相关的在线资源:

App Engine

其他云信息

视频

许可

此作品已获得 Creative Commons Attribution 2.0 通用许可授权。