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

1. 概览

本系列 Codelab(自定进度的实操教程)旨在指导 Google App Engine(标准)Java 开发者完成一系列迁移,从而对应用进行现代化改造。按照这些步骤,您可以更新应用的可移植性,并决定针对 Cloud Run(Google Cloud 与 App Engine 的容器托管姊妹服务)和其他容器托管服务进行容器化。

本教程介绍了如何将 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. 背景

App Engine 和 Cloud Functions 等 PaaS 系统为您的团队和应用提供了许多便利,例如让 SysAdmin 和 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 上克隆示例应用,然后前往基准文件夹。

该示例是一个基于 Java 8、基于 Servlet 的数据存储区应用程序,旨在部署在 App Engine 上。按照自述文件中的说明为 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. 包括一个网络服务器

在 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,并按预期提供这些注解。

4. 将应用打包为 JAR

虽然可以从战争开始对应用进行容器化,但将应用打包为可执行的 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 如何实际构建应用的容器。使用此容器化方法时,不需要单独的构建配置文件 (cloudbuild.yaml)。我们可以直接定义一个最小的 Dockerfile 作为起点:

来自日食-铁马星

ARG JAR_FILE=JAR_FILE_must_BE_SPECIFIED_AS_BUILD_ARG

复制 ${JAR_FILE} app.jar

入口点 ["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:您的 Cloud 项目 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

其他 Cloud 信息

视频

许可

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