將 Node.js 應用程式從 Heroku 遷移至 Cloud Run

本教學課程說明如何將在 Heroku 上執行的 Node.js 網路應用程式遷移至 Google CloudCloud Run。本教學課程適用於想將應用程式從 Heroku 遷移至 Google Cloud受管理服務的架構師和產品負責人。

Cloud Run 是一種代管運算平台,能夠讓您執行可透過 HTTP 要求叫用的無狀態容器。此平台建構於開放原始碼的 Knative,可跨平台攜帶,並支援容器工作流程和持續推送軟體更新標準。Cloud Run 平台與 Google Cloud 產品套件緊密整合,可讓您更輕鬆地設計及開發可攜式、可擴充且具備韌性的應用程式。

在本教學課程中,您將瞭解如何將應用程式遷移至 Google Cloud 。該應用程式以 Node.js 編寫,並使用 Heroku 上的Heroku Postgres 做為後端服務。網頁應用程式會以容器化形式託管在 Cloud Run 中,並使用 PostgreSQL 適用的 Cloud SQL 做為持續性層。

在本教學課程中,您將使用名為「Tasks」的簡單應用程式,查看及建立工作。這些工作會儲存在 Heroku Postgres 中,也就是目前在 Heroku 上部署的應用程式。

本教學課程假設您熟悉 Heroku 的基本功能,並且擁有 (或可存取) Heroku 帳戶。此外,本文也假設您熟悉 Cloud RunCloud SQLDockerNode.js

正在設定環境

  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 上的網頁應用程式架構 (現狀),以及您將建構的 Google Cloud 架構配置。

Heroku 上的現有架構。
圖 1:Heroku 現有架構

目前在 Heroku 中部署的 Tasks 應用程式包含一或多個網頁 dyno。Web dyno 可以接收及回應 HTTP 流量,這與 worker 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) 叢集中執行。
網頁應用程式 Heroku 應用程式:Dyno 是 Heroku 應用程式的建構區塊。應用程式通常由一或多個 Dyno 類型組成,通常是網頁和工作站 Dyno 的組合。 Cloud Run 服務:網頁應用程式可以模擬為 Cloud Run 服務。每項服務都有專屬的 HTTPS 端點,並可根據服務端點的流量,自動將資源從 0 擴充或縮減至 N。
資料庫 Heroku Postgres 是 Heroku 的資料庫即服務 (DaaS),以 PostgreSQL 為基礎。 Cloud SQL 是 Google Cloud上的關聯式資料庫代管資料庫服務。

將範例 Tasks 網頁應用程式部署至 Heroku

接下來的章節說明如何設定 Heroku 的指令列介面 (CLI)、複製 GitHub 來源存放區,以及將應用程式部署至 Heroku。

設定 Heroku 的指令列介面

本教學課程會在 Cloud Shell 中執行 Heroku CLI,且必須使用 Heroku API 金鑰進行驗證。在 Cloud Shell 中執行時,Heroku CLI 無法使用密碼或網頁式驗證進行驗證。

或者,如果您在本機終端機上執行範例,可以使用任何 Heroku CLI 驗證方法。在本機終端機上執行本教學課程時,您也必須安裝 Google Cloud CLIgitDocker

  1. 登入 Heroku 網頁控制台,然後前往帳戶設定頁面,複製 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 指令碼,內含網路應用程式提供的路徑程式碼。
    • package.jsonpackage-lock.json 檔案,其中列出網頁應用程式的依附元件。您必須安裝這些依附元件,應用程式才能執行。
    • Procfile 檔案,用於指定應用程式在啟動時執行的指令。您會建立 Procfile 檔案,將應用程式部署到 Heroku。
    • views 目錄,其中包含網頁應用程式在「/」路徑上提供的 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. 透過瀏覽器在應用程式中建立範例工作。確認工作已從資料庫擷取,並顯示在 UI 中。

準備網頁應用程式程式碼,以便遷移至 Cloud Run

本節詳細說明您必須完成的步驟,以便準備將網頁應用程式部署至 Cloud Run。

建構 Docker 容器並發布至 Container Registry

您需要 Docker 映像檔來建構應用程式容器,才能在 Cloud Run 中執行。您可以手動建構容器,也可以使用建構套件。

手動建構容器

  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"
    

使用 Buildpacks 建構容器

  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
    

建立 PostgreSQL 適用的 Cloud SQL 執行個體

您將建立 PostgreSQL 適用的 Cloud SQL 執行個體,做為網頁應用程式的後端。在本教學課程中,PostgreSQL 最適合做為部署在 Heroku 上的範例應用程式,因為該應用程式使用 Postgres 資料庫做為後端。就這個應用程式而言,從受管理 Postgres 服務遷移至 PostgreSQL 適用的 Cloud SQL 時,不需要變更結構定義。

  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 不支援外部副本 (追蹤者),因此在本教學課程中,您會使用開放原始碼工具遷移應用程式的結構定義。

在本教學課程中,您將使用 pg_dump 公用程式,將 Tasks 應用程式的資料從 Heroku Postgres 匯出至 Cloud Storage bucket,然後匯入 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. 以 SQL 格式備份 Heroku Postgres 資料庫:

    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 bucket:

    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 SQL 執行個體授予匯入 SQL 檔案的必要角色,從 Cloud Storage bucket 匯入檔案:

    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 的網路應用程式需要連線至 Heroku Postgres 的代管執行個體,Cloud Run 也需要存取 Cloud SQL,才能讀取及寫入資料。

Cloud Run 會使用 Cloud SQL Proxy 與 Cloud SQL 通訊。將容器部署至 Cloud Run 時,系統會自動啟用及設定 Cloud SQL Proxy。資料庫不需要核准外部 IP 位址,因為收到的所有通訊都是透過安全 TCP 從 Proxy 傳送。

您的程式碼需要透過 UNIX Socket 呼叫 Proxy,藉此叫用資料庫作業 (例如從資料庫擷取資料或寫入資料)。

由於這個網頁應用程式是以 Node.js 編寫,因此您可以使用 pg-connection-string 程式庫剖析資料庫 URL,並建立 config 物件。這種做法的優點是,在 Heroku 和 Cloud Run 之間,連線至後端資料庫時不會發生任何問題。

在下一個步驟中,您會在部署網頁應用程式時,將資料庫網址做為環境變數傳遞。

將範例應用程式部署至 Cloud Run

  1. 在 Cloud Shell 中設定無伺服器虛擬私有雲存取,允許 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 Proxy 的連線字串。

    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. 將網頁應用程式部署至 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 個要求。