将 Node.js 应用从 Heroku 迁移到 Cloud Run

本教程介绍如何将 Heroku 上运行的 Node.js Web 应用迁移到 Google Cloud 上的 Cloud Run。本教程适合希望将应用从 Heroku 迁移到 Google Cloud 上的托管式服务的架构师和产品所有者。

Cloud Run 是一个托管式计算平台,可让您运行可通过 HTTP 请求调用的无状态容器。它基于开源 Knative 构建而成,Knative 可实现跨平台的可移植性并支持容器工作流和持续交付标准。Cloud Run 平台已与 Google Cloud 产品套件完美集成,可让您更轻松地设计和开发可移植、可扩缩、弹性佳的应用。

在本教程中,您将学习如何将 Heroku 上以 Node.js 编写的并且使用 Heroku Postgres 作为支持性服务的应用迁移到 Google Cloud。该 Web 应用将实现容器化并托管于 Cloud Run 中,并使用 Cloud SQL for PostgreSQL 作为其持久层。

在本教程中,您将使用一个名为 Tasks 的简单应用,该应用可让您查看和创建任务。这些任务会存储在 Heroku 当前应用部署的 Heroku Postgres 中。

本教程假设您熟悉 Heroku 的基本功能,并且拥有 Heroku 帐号(或者有权使用 Heroku 帐号)。此外,本教程还假设您熟悉 Cloud RunCloud SQLDockerNode.js

目标

  • 构建 Docker 映像以将应用部署到 Cloud Run。
  • 创建 Cloud SQL for PostgreSQL 实例,以便在迁移到 Google Cloud 之后充当后端。
  • 查看 Node.js 代码,了解 Cloud Run 如何连接到 Cloud SQL,以及查看从 Heroku 迁移到 Cloud Run 所需的代码更改(如果有)。
  • 将数据从 Heroku Postgres 迁移到 Cloud SQL for PostgreSQL。
  • 将应用部署到 Cloud Run。
  • 测试已部署的应用。

费用

本教程使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

您可能还需要为自己在 Heroku 上使用的资源付费。

准备工作

  1. 登录您的 Google Cloud 帐号。如果您是 Google Cloud 新手,请创建一个帐号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能

  4. 启用 Cloud SQL and Cloud Run API。

    启用 API

  5. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  6. 确保您的 Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能

  7. 启用 Cloud SQL and Cloud Run API。

    启用 API

设置环境

  1. 打开 Cloud Shell。

    打开 Cloud Shell

  2. 在 Cloud Shell 中,为整个教程通篇使用的值(例如区域和可用区)分配默认设置。本教程使用 us-central1 作为默认区域,使用 us-central1-a 作为默认可用区。

    gcloud config set compute/region us-central1
    gcloud config set compute/zone us-central1-a
    
  3. 配置 Google Cloud CLI 以将 us-central1 用作 Cloud Run 的默认区域:

    gcloud config set run/region us-central1
    
  4. 创建环境变量以保存本教程的默认应用名称:

    export APP_NAME=tasks-web-app
    

架构

下图描绘了 Heroku 上的 Web 应用架构(原样)及其在 Google Cloud 上的架构布局(您将构建的架构布局)。

Heroku 上的原样架构。
图 1. Heroku 上的原样架构

目前在 Heroku 中部署的 Tasks 应用由一个或多个 Web Dyno 组成。Web Dyno 能够接收 HTTP 流量以及对 HTTP 流量作出响应,这一点与工作器 Dyno 不同,后者更适合后台作业和定时任务。该应用使用适用于 Node.js 的 Mustache 模板库提供索引页面,该页面显示 Postgres 数据库中存储的任务。

您可以通过 HTTPS 网址访问该应用。该网址的 /tasks 路由可让您创建新任务。

Heroku 上的原样架构。
图 2. 您在 Google Cloud 上构建的架构

在 Google Cloud 上,Cloud Run 用作部署 Tasks 应用的无服务器平台。Cloud Run 旨在运行由请求驱动的无状态容器。它非常适合以下场合:您需要托管式服务来支持能够自动扩缩并且还能够在不处理流量时缩减到零的容器化应用。

将 Heroku 中使用的组件映射到 Google Cloud

下表将 Heroku 平台中的组件映射到 Google Cloud。 此映射可帮助您将本教程中概述的 Heroku 架构转换为 Google Cloud 架构。

组件 Heroku 平台 Google Cloud
容器 Dyno:Heroku 使用容器模型构建和扩缩 Heroku 应用。这些 Linux 容器称为 Dyno,可以扩缩到您指定的数量以支持您的 Heroku 应用的资源需求。您可以根据应用的内存和 CPU 要求从一系列 dyno 类型中进行选择。 Cloud Run 容器:Google Cloud 支持在无状态容器中运行容器化工作负载,这些容器可以在全托管式环境或 Google Kubernetes Engine (GKE) 集群中运行。
网页应用 Heroku 应用:Dyno 是 Heroku 应用的构建块。 应用通常由一种或多种 Dyno 类型组成(通常是 Web Dyno 和工作器 Dyno 的组合)。 Cloud Run 服务:Web 应用可以建模为 Cloud Run 服务。每项服务都有自己的 HTTPS 端点,并且可以根据流向服务端点的流量自动在 0 到 N 的范围内进行扩缩。
数据库 Heroku Postgres 是基于 PostgreSQL 的 Heroku 数据库即服务 (DaaS)。 Cloud SQL 是适用于 Google Cloud 上的关系型数据库的托管式数据库服务。它提供了两种变体:PostgreSQL 和 MySQL。

将示例 Tasks Web 应用部署到 Heroku

后续部分将介绍如何为 Heroku 设置命令行界面 (CLI)、克隆 GitHub 源代码库以及将应用部署到 Heroku。

为 Heroku 设置命令行界面

  1. 在 Cloud Shell 中,安装 Heroku CLI

  2. 登录您的 Heroku 帐号:

    heroku login --interactive
    

克隆源代码库

  1. 在 Cloud Shell 中,克隆示例 Tasks 应用 GitHub 代码库:

    git clone https://github.com/GoogleCloudPlatform/migrate-webapp-heroku-to-cloudrun-node.git
    
  2. 将目录更改为通过克隆代码库创建的目录:

    cd migrate-webapp-heroku-to-cloudrun-node
    

    该目录包含以下文件:

    • 一个名为 index.js 的 Node.js 脚本,其中包含该 Web 应用所提供路由的代码。
    • 构成该 Web 应用依赖项的 package.jsonpackage-lock.json 文件。您必须安装这些依赖项才能运行该应用。
    • 一个 Procfile 文件,用于指定该应用在启动时执行的命令。您可以创建一个 Procfile 文件,以便将您的应用部署到 Heroku。
    • 一个 views 目录,其中包含该 Web 应用在“/”路由中提供的 HTML 内容。
    • .gitignore 文件。

将应用部署到 Heroku

  1. 在 Cloud Shell 中,创建一个 Heroku 应用:

    heroku create
    
  2. 添加 Heroku Postgres 插件以预配 PostgreSQL 数据库:

    heroku addons:create heroku-postgresql:hobby-dev
    
  3. 确保该插件已成功添加:

    heroku addons
    

    如果 Postgres 插件已成功添加,则您会看到类似如下的消息:

    Owning-App               Add-on                      Plan                          Price    State
    ----------------------   ------------------------    --------------------------    -----    -----
    sample-nodejs-todo-app   postgresql-adjacent-95585   heroku-postgresql:hobby-dev   free     created
    
  4. 将该应用部署到 Heroku:

    git push heroku master
    
  5. 从 Heroku 控制台检索 Heroku Postgres URI。或者,运行以下命令以检索该 URI:

    heroku config
    

    记下检索到的 URI。下一步操作将会用到它。

  6. 运行一个 Docker 容器。将 database-uri 替换为您在上一步中记下的 Heroku Postgres URI。

    docker run -it --rm postgres psql "database-uri"
    
  7. 在该 Docker 容器中,使用以下命令创建 TASKS 表:

    CREATE TABLE TASKS
    (DESCRIPTION TEXT NOT NULL);
    
  8. 退出该容器:

    exit
    
  9. 在 Cloud Shell 中,获取您的 Heroku 应用网址:

    heroku info
    
  10. 在浏览器窗口中打开该应用的网址。该应用如下所示(不过,您的版本不会列出任务):

    网络浏览器中的待办事项应用。

  11. 通过浏览器在您的应用中创建示例任务。确保系统从数据库中检索到这些任务,并且您可以在界面中看到这些任务。

准备 Web 应用代码以迁移到 Cloud Run

本部分详细介绍了准备 Web 应用以部署到 Cloud Run 所需完成的步骤。

构建 Docker 容器并将其发布到 Container Registry

您需要一个 Docker 映像来构建应用容器,以便它可以在 Cloud Run 中运行。您可以通过手动方式或使用 Buildpack 来构建容器。

手动构建容器

  1. 通过 Cloud Shell 在为本教程克隆代码库而创建的目录中创建一个 Dockerfile:

    cat <<EOF > Dockerfile
    # Use the official Node image.
    # https://hub.docker.com/_/node
    FROM node:10-alpine
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    RUN npm install
    
    # Copy local code to the container image.
    COPY . /app
    
    # Configure and document the service HTTP port.
    ENV PORT 8080
    EXPOSE $PORT
    
    # Run the web service on container startup.
    CMD ["npm", "start"]
    EOF
    
  2. 使用 Cloud Build 构建容器并将映像发布到 Container Registry

    gcloud builds submit --tag gcr.io/${DEVSHELL_PROJECT_ID}/$APP_NAME:1
    
  3. 创建环境变量来保存您创建的 Docker 映像的名称:

    export IMAGE_NAME="gcr.io/${DEVSHELL_PROJECT_ID}/$APP_NAME:1"
    

使用 Buildpack 构建容器

  1. 在 Cloud Shell 中,安装 pack CLI

    wget https://github.com/buildpack/pack/releases/download/v0.2.1/pack-v0.2.1-linux.tgz
    tar xvf pack-v0.2.1-linux.tgz
    rm pack-v0.2.1-linux.tgz
    sudo mv pack /usr/local/bin/
    
  2. 将 pack CLI 设置为默认使用 Heroku 构建器:

    pack set-default-builder heroku/buildpacks
    
  3. 创建环境变量以保存 Docker 映像名称:

    export IMAGE_NAME=gcr.io/${DEVSHELL_PROJECT_ID}/$APP_NAME:1
    
  4. 使用 pack 命令构建映像,然后将映像推送或发布到 Container Registry:

    pack build --publish $IMAGE_NAME
    

创建 Cloud SQL for PostgreSQL 实例

您可以创建一个 Cloud SQL for PostgreSQL 实例,以充当 Web 应用的后端。在本教程中,PostgreSQL 最适合 Heroku 上部署的示例应用,该应用使用 Postgres 数据库作为其后端。在此应用中,从托管式 Postgres 服务迁移到 Cloud SQL for PostgreSQL 不需要更改架构。

gcloud

  1. 创建一个名为 CLOUDSQL_DB_NAME 的环境变量来保存您在下一步中创建的数据库实例的名称:

    export CLOUDSQL_DB_NAME=tasks-db
    
  2. 创建数据库:

    gcloud sql instances create $CLOUDSQL_DB_NAME  \
        --cpu=1 \
        --memory=4352Mib \
        --database-version=POSTGRES_9_6 \
        --region=us-central1
    

    该实例可能需要几分钟的时间来进行初始化。

  3. 为 Postgres 用户设置密码:

    gcloud sql users set-password postgres \
        --instance=$CLOUDSQL_DB_NAME  \
        --password=postgres-password
    

控制台

  1. 在控制台中,转到 Cloud SQL 实例页面:

    转到“Cloud SQL 实例”页面

  2. 点击创建实例

  3. 选择 PostgreSQL,然后点击下一步

  4. 输入 tasks-db 作为实例名称。

  5. postgres 用户创建密码。

  6. 对于区域,请选择 us-central1

  7. 请勿修改其他默认设置。

将数据从 Heroku Postgres 导入 Cloud SQL

您可以使用多种迁移模式将数据迁移到 Cloud SQL。通常,几乎不需要停机的最佳方法是将 Cloud SQL 配置为要迁移的数据库的副本,并在迁移后将 Cloud SQL 作为主实例。Heroku Postgres 不支持外部副本(跟随数据库),因此在本教程中,您将使用开源工具迁移应用的架构。

对于本教程中的 Tasks 应用,您可以使用 pg_dump 实用程序将数据从 Heroku Postgres 导出到 Cloud Storage 存储分区,然后将其导入 Cloud SQL。此实用程序可以跨同类版本转移数据,也可以在目标数据库的版本高于源数据库时转移数据。

  1. 在 Cloud Shell 中,获取挂接到示例应用的 Heroku Postgres 数据库的数据库凭据。在下一步中您将需要这些凭据。

    heroku pg:credentials:url
    

    此命令会返回应用的连接信息字符串和连接网址。连接信息字符串采用以下格式:

    "dbname=database-name host=FQDN port=5432 user=user-name password=password-string sslmode=require"
    

    如需查看连接信息字符串中的 FQDN完全限定域名)值的示例,请参阅 Heroku 文档

  2. 设置环境变量以保存您在后续步骤中使用的 Heroku 值。将 FQDNuser-namepassword-stringdatabase-name 占位符替换为连接信息字符串中的相应值。

    export HEROKU_PG_HOST=FQDN
    export HEROKU_PG_USER=user-name
    export HEROKU_PG_PASSWORD=password-string
    export HEROKU_PG_DBNAME=database-name
    
  3. 创建 Heroku Postgres 数据库的 SQL 格式备份:

    docker run \
      -it --rm \
      -e PGPASSWORD=$HEROKU_PG_PASSWORD \
      -v $(pwd):/tmp \
      --entrypoint "pg_dump" \
      postgres \
      -Fp \
      --no-acl \
      --no-owner \
      -h $HEROKU_PG_HOST \
      -U $HEROKU_PG_USER \
      $HEROKU_PG_DBNAME > herokudump.sql
    
  4. 创建环境变量来保存 Cloud Storage 存储分区的名称。此名称应该是唯一的。该环境变量应采用 gs://bucket-name 格式。

    export PG_BACKUP_BUCKET=gs://bucket-name
    
  5. 使用 gsutil 命令行工具创建 Cloud Storage 存储分区:

    gsutil mb -c regional -l us-central1 $PG_BACKUP_BUCKET
    
  6. 将 SQL 文件上传到此存储分区:

    gsutil cp herokudump.sql $PG_BACKUP_BUCKET
    
  7. 在控制台中,转到 Cloud SQL 实例页面:

    转到“Cloud SQL 实例”页面

  8. 选择相应实例以打开其实例详情页面。

  9. 在按钮栏中,点击导入

    从 Cloud Storage 导入 SQL 转储文件。

  10. 对于 Cloud Storage 文件,请输入您上传到 Cloud Storage 的 SQL 转储文件的路径。

  11. 对于导入文件的格式,请选择 SQL

  12. 对于数据库,请选择 postgres

  13. 展开高级选项,然后,对于用户,请选择 postgres

  14. 点击导入以开始导入。

  15. 通过查看实例的操作标签页中的更新来验证导入操作是否成功。

Cloud Run 如何访问 Cloud SQL 数据库

正如部署到 Heroku 的 Web 应用需要连接到 Heroku Postgres 的托管实例一样,Cloud Run 需要访问 Cloud SQL 才能读取和写入数据。

Cloud Run 将使用 Cloud SQL 代理与 Cloud SQL 通信,该代理是您在将容器部署到 Cloud Run 时自动激活和配置的。该数据库不需要批准外部 IP 地址,因为它收到的所有通信都来自于使用安全 TCP 的代理。

您的代码需要调用数据库操作(例如从该数据库提取数据或者向该数据库写入数据),具体方法是通过 UNIX 套接字调用该代理。

由于此 Web 应用是使用 Node.js 编写的,因此您可以使用 pg-connection-string 库来解析数据库网址并创建 config 对象。这种方法的优势在于,它可以跨 Heroku 和 Cloud Run 无缝连接到后端数据库。

在下一步中,您将在部署该 Web 应用时将数据库网址作为环境变量来传递。

将示例应用部署到 Cloud Run

  1. 在 Cloud Shell 中,创建一个环境变量,用于保存您创建的 Cloud SQL 实例的连接名称:

    export DB_CONN_NAME=$(gcloud sql instances describe $CLOUDSQL_DB_NAME --format='value(connectionName)')
    
  2. 创建一个名为 DATABASE_URL 的环境变量,用于保存连接字符串,以便通过 UNIX 端口连接到 Cloud SQL 代理。将 your-db-password 替换为您为数据库实例创建的密码。

    export DATABASE_URL="socket:/cloudsql/${DB_CONN_NAME}?db=postgres&user=postgres&password=your-db-password"
    
  3. 将 Web 应用部署到 Cloud Run。

    gcloud run deploy tasksapp-$DEVSHELL_PROJECT_ID \
        --image=$IMAGE_NAME \
        --set-env-vars=DATABASE_URL=$DATABASE_URL \
        --add-cloudsql-instances $DB_CONN_NAME \
        --allow-unauthenticated
    

    上述命令还会将 Cloud Run 容器与您创建的 Cloud SQL 数据库实例相关联。该命令会为 Cloud Run 设置一个指向您在上一步中创建的 DATABASE_URL 字符串的环境变量。

测试应用

  1. 在 Cloud Shell 中,获取 Cloud Run 处理流量的网址:

    gcloud run services list
    

    您还可以在控制台中查看 Cloud Run 服务。

  2. 通过导航到您的 Cloud Run 服务网址,确保您的 Web 应用正在接受 HTTP 请求。

如果向服务端点发送 HTTP 请求并且容器尚未运行,则 Cloud Run 会创建或启动一个新容器。这意味着,导致新容器启动的请求可能需要较长时间才能得到处理。由于需要额外的时间,因此请务必考虑您的应用可以支持的并发请求的数量以及它可能需要满足的任何特定内存要求。

对于此应用,您可以使用默认的并发设置,这些设置允许 Cloud Run 服务从单个容器同时处理 80 个请求。

清理

为避免因本教程中使用的资源导致您的 Google Cloud 帐号产生费用,请执行以下操作。 您可能还需要删除您在 Heroku 中为本教程创建的资源。

删除 Google Cloud 项目

  1. 在控制台中,打开管理资源页面。

    打开“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除 Heroku 应用

如需删除您部署到 Heroku 的示例应用以及关联的 PostgreSQL 插件,请运行以下命令:

heroku apps:destroy -a $APP_NAME

后续步骤