将 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. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Cloud SQL, Cloud Build, Cloud Run, Container Registry, Service Networking, Serverless VPC Access APIs.

    Enable the APIs

  5. Make sure that you have the following role or roles on the project: Cloud Run > Cloud Run Admin, Cloud Storage > Storage Admin, Cloud SQL > Cloud SQL Admin, Compute Engine > Compute Network Admin, Resource Manager > Project IAM Admin, Cloud Build > Cloud Build Editor, Serverless VPC Access > Serverless VPC Access Admin, Logging > Logs Viewer, Service Accounts > Service Account Admin, Service Accounts > Service Account User, and Service Usage > Service Usage Consumer

    Check for the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. In the Principal column, find all rows that identify you or a group that you're included in. To learn which groups you're included in, contact your administrator.

    4. For all rows that specify or include you, check the Role colunn to see whether the list of roles includes the required roles.

    Grant the roles

    1. In the Google Cloud console, go to the IAM page.

      前往 IAM
    2. 选择项目。
    3. 点击 授予访问权限
    4. 新的主账号字段中,输入您的用户标识符。 这通常是 Google 账号的电子邮件地址。

    5. 选择角色列表中,选择一个角色。
    6. 如需授予其他角色,请点击 添加其他角色,然后添加其他各个角色。
    7. 点击保存
    8. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

      Go to project selector

    9. Make sure that billing is enabled for your Google Cloud project.

    10. Enable the Cloud SQL, Cloud Build, Cloud Run, Container Registry, Service Networking, Serverless VPC Access APIs.

      Enable the APIs

    11. Make sure that you have the following role or roles on the project: Cloud Run > Cloud Run Admin, Cloud Storage > Storage Admin, Cloud SQL > Cloud SQL Admin, Compute Engine > Compute Network Admin, Resource Manager > Project IAM Admin, Cloud Build > Cloud Build Editor, Serverless VPC Access > Serverless VPC Access Admin, Logging > Logs Viewer, Service Accounts > Service Account Admin, Service Accounts > Service Account User, and Service Usage > Service Usage Consumer

      Check for the roles

      1. In the Google Cloud console, go to the IAM page.

        Go to IAM
      2. Select the project.
      3. In the Principal column, find all rows that identify you or a group that you're included in. To learn which groups you're included in, contact your administrator.

      4. For all rows that specify or include you, check the Role colunn to see whether the list of roles includes the required roles.

      Grant the roles

      1. In the Google Cloud console, go to the IAM page.

        前往 IAM
      2. 选择项目。
      3. 点击 授予访问权限
      4. 新的主账号字段中,输入您的用户标识符。 这通常是 Google 账号的电子邮件地址。

      5. 选择角色列表中,选择一个角色。
      6. 如需授予其他角色,请点击 添加其他角色,然后添加其他各个角色。
      7. 点击保存

设置环境

  1. 打开 Cloud Shell。

    打开 Cloud Shell

  2. 在 Cloud Shell 中,为本教程中使用的 Google Cloud CLI 设置环境变量和默认值。

    gcloud config set project PROJECT_ID
    gcloud config set run/region us-central1
    

    PROJECT_ID 替换为您的项目 ID。

架构

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

将示例 Tasks Web 应用部署到 Heroku

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

为 Heroku 设置命令行界面

本教程在 Cloud Shell 中运行 Heroku CLI,并且必须使用 Heroku API 密钥进行身份验证。在 Cloud Shell 中运行时,Heroku CLI 无法使用密码或基于 Web 的身份验证进行身份验证。

或者,如果您在本地终端上运行该示例,则可以使用任何 Heroku CLI 身份验证方法。在本地终端上运行本教程时,您还必须安装 Google Cloud CLIgitDocker

  1. 登录 Heroku 的 Web 控制台,然后从账号设置页面中复制 API 密钥的值。

  2. 在 Cloud Shell 中,安装 Heroku CLI

  3. 在 Cloud Shell 中,对 Heroku CLI 进行身份验证。当系统提示您输入密码时,请输入您从 Heroku 控制台复制的 API 密钥的值,而不是您用于登录该控制台的密码。

    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 应用名称创建环境变量:

    export APP_NAME=APP_NAME
    

    APP_NAME 替换为由 heroku create 命令返回的应用名称。

  3. 添加 Heroku Postgres 插件以预配 PostgreSQL 数据库:

    heroku addons:create heroku-postgresql:mini
    
  4. 确保该插件已成功添加:

    heroku addons
    

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

    Add-on               Plan     Price       State
    -----------------    -----    --------    -----
    heroku-postgresql    mini     5$/month    created
    
  5. 将该应用部署到 Heroku:

    git push heroku master
    
  6. 运行以下命令以确认 DATABASE_URL 的值。

    heroku config
    

    记下检索到的 DATABASE_URL 值。下一步中您需要用到该值。

  7. 运行 Docker 容器。

    docker run -it --rm postgres psql "DATABASE_URL"
    

    DATABASE_URL 替换为您在上一步中记下的 Heroku Postgres 网址。

  8. 在该 Docker 容器中,使用以下命令创建 TASKS 表:

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

    exit
    
  10. 在 Cloud Shell 中,运行以下命令以获取 Heroku 应用的网址:

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

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

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

准备 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/PROJECT_ID/APP_NAME:1 \
      --gcs-log-dir=gs://PROJECT_ID_cloudbuild
    
  3. 创建环境变量来保存您创建的 Docker 映像的名称:

    export IMAGE_NAME="gcr.io/PROJECT_ID/APP_NAME:1"
    

使用 Buildpack 构建容器

  1. 在 Cloud Shell 中,安装 pack CLI

  2. 将 pack CLI 设置为默认使用 Heroku 构建器:

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

    export IMAGE_NAME=gcr.io/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 不需要更改架构。

  1. 使用专用 IP 地址为 Cloud SQL 准备网络。

    gcloud compute addresses create google-managed-services-default \
      --global \
      --purpose=VPC_PEERING \
      --prefix-length=16 \
      --description="peering range for CloudSQL Private Service Access" \
      --network=default
    
    gcloud services vpc-peerings connect \
      --service=servicenetworking.googleapis.com \
      --ranges=google-managed-services-default \
      --network=default \
      --project=PROJECT_ID
    
  2. 创建一个名为 CLOUDSQL_DB_NAME 的环境变量来保存您在下一步中创建的数据库实例的名称:

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

    gcloud sql instances create $CLOUDSQL_DB_NAME \
    --cpu=1 \
    --memory=4352Mib \
    --database-version=POSTGRES_15 \
    --region=us-central1 \
    --network default \
    --no-assign-ip
    

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

  4. 为 Postgres 用户设置密码:

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

    POSTGRES_PASSWORD 替换为您要用于 Postgres 数据库的密码。

将数据从 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 值:

    export HEROKU_PG_DBNAME=DATABASE_NAME
    export HEROKU_PG_HOST=FQDN
    export HEROKU_PG_USER=USER_NAME
    export HEROKU_PG_PASSWORD=PASSWORD_STRING
    

    替换以下内容:

    • DATABASE_NAME:信息字符串中显示的数据库名称。
    • FQDN:信息字符串中显示的 FQDN。
    • USER_NAME:信息字符串中显示的用户名。
    • PASSWORD_STRING:信息字符串中显示的密码字符串。
  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 存储桶的名称:

    export PG_BACKUP_BUCKET=gs://PROJECT_ID-pg-backup-bucket
    
  5. 创建 Cloud Storage 存储分区,请运行以下命令:

    gcloud storage buckets create $PG_BACKUP_BUCKET \
      --location=us-central1 \
      --public-access-prevention \
      --uniform-bucket-level-access
    
  6. 将 SQL 文件上传到此存储分区:

    gcloud storage cp herokudump.sql $PG_BACKUP_BUCKET/herokudump.sql
    
  7. 使用从 Cloud Storage 存储桶导入 SQL 文件所需的角色向 Cloud SQL 实例授权:

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:$(gcloud sql instances describe $CLOUDSQL_DB_NAME --format='get("serviceAccountEmailAddress")') \
      --role=roles/storage.objectAdmin
    
    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:$(gcloud sql instances describe $CLOUDSQL_DB_NAME --format='get("serviceAccountEmailAddress")') \
      --role=roles/cloudsql.editor
    
  8. 将 SQL 文件导入 Cloud SQL 实例:

    gcloud sql import sql $CLOUDSQL_DB_NAME $PG_BACKUP_BUCKET/herokudump.sql \
      --database=postgres \
      --user=postgres
    

    当系统提示 do you want to continue (y/n) 时,请输入“y”。

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 中,配置无服务器 VPC 访问通道,以允许从 Cloud Run 到 Cloud SQL 的专用流量:

    gcloud compute networks subnets create serverless-connector-subnet \
    --network=default \
    --range=10.0.0.0/28 \
    --region=us-central1
    
    gcloud compute networks vpc-access connectors create serverless-connector \
    --region=us-central1 \
    --subnet=serverless-connector-subnet
    
  2. 在 Cloud Shell 中,创建一个环境变量,用于保存您创建的 Cloud SQL 实例的连接名称:

    export DB_CONN_NAME=$(gcloud sql instances describe $CLOUDSQL_DB_NAME --format='value(connectionName)')
    
  3. 创建一个名为 DATABASE_URL 的环境变量来保存连接字符串,以通过 UNIX 端口连接到 Cloud SQL 代理。

    export DATABASE_URL="socket:/cloudsql/${DB_CONN_NAME}?db=postgres&user=postgres&password=POSTGRES_PASSWORD"
    
  4. 为 Cloud Run 创建具有 IAM 角色的服务账号,以连接到数据库:

    gcloud iam service-accounts create sa-run-db-client
    
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:sa-run-db-client@PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/cloudsql.client
    
  5. 将 Web 应用部署到 Cloud Run:

    gcloud run deploy tasksapp-PROJECT_ID \
        --image=$IMAGE_NAME \
        --service-account=sa-run-db-client@PROJECT_ID.iam.gserviceaccount.com \
        --set-env-vars=DATABASE_URL=$DATABASE_URL \
        --add-cloudsql-instances $DB_CONN_NAME \
        --vpc-connector serverless-connector \
        --allow-unauthenticated
    
    

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

测试应用

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

    gcloud run services list
    

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

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

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

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

清理

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

删除 Google Cloud 项目

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

删除 Heroku 应用

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

heroku apps:destroy -a APP_NAME

后续步骤