開始使用 Cloud Run 工作

1. 簡介

1965fab24c502bd5.png

總覽

Cloud Run 服務適合會無限期監聽 HTTP 要求的容器,Cloud Run「工作」則更適合要執行完成 (目前最多 24 小時) 且不會處理要求的容器。例如,如果以 Cloud Run 工作的形式實作,就能處理資料庫記錄、處理 Cloud Storage 值區中的檔案清單,或計算 Pi 等長時間執行的作業。

工作無法提供要求或監聽通訊埠。這代表工作與 Cloud Run 服務不同,不應組合網路伺服器。工作容器應在完成後結束。

在 Cloud Run 工作中,您可以指定多個任務,並行執行多個容器副本。每個工作都代表一個執行中的容器副本。如果每項工作都能獨立處理部分資料,這時就適合使用多項工作。舉例來說,處理 Cloud SQL 中的 10,000 筆記錄或 Cloud Storage 中的 10,000 個檔案,可以分別處理 10 項工作或 10 項工作,加快處理速度。

使用 Cloud Run 工作分為兩個步驟:

  1. 建立工作:包含執行工作所需的所有設定,例如容器映像檔、區域、環境變數。
  2. 執行工作:這會建立新的工作執行作業。或者,您也可以使用 Cloud Scheduler 將工作設為按排程執行。

在本程式碼研究室中,您將先探索 Node.js 應用程式,用於擷取網頁的螢幕截圖,並儲存至 Cloud Storage。接著,您會建構應用程式的容器映像檔、在 Cloud Run 工作中執行、更新工作以處理更多網頁,並透過 Cloud Scheduler 按排程執行工作。

課程內容

  • 如何使用應用程式擷取網頁的螢幕截圖。
  • 如何建構應用程式的容器映像檔。
  • 如何為應用程式建立 Cloud Run 工作。
  • 如何將應用程式做為 Cloud Run 工作執行。
  • 如何更新工作。
  • 如何使用 Cloud Scheduler 排定工作時程。

2. 設定和需求

自修環境設定

  1. 登入 Google Cloud 控制台,建立新專案或重複使用現有專案。如果您還沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • 「專案名稱」是這項專案參與者的顯示名稱。這是 Google API 未使用的字元字串。您可以隨時更新付款方式。
  • 所有 Google Cloud 專案的專案 ID 均不得重複,而且設定後即無法變更。Cloud 控制台會自動產生一個不重複的字串。但通常是在乎它何在在大部分的程式碼研究室中,您必須參照專案 ID (通常為 PROJECT_ID)。如果您對產生的 ID 不滿意,可以隨機產生一個 ID。或者,您也可以自行嘗試,看看是否支援。在這個步驟後,這個名稱即無法變更,而且在專案期間內仍會保持有效。
  • 資訊中的第三個值是專案編號,部分 API 會使用這個編號。如要進一步瞭解這三個值,請參閱說明文件
  1. 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。執行本程式碼研究室不會產生任何費用 (如果有的話)。如要關閉資源,以免產生本教學課程結束後產生的費用,您可以刪除自己建立的資源或刪除專案。新使用者符合 $300 美元免費試用計畫的資格。

啟動 Cloud Shell

雖然 Google Cloud 可以從筆記型電腦遠端操作,但在本程式碼研究室中,您將使用 Google Cloud Shell,這是一種在 Cloud 中執行的指令列環境。

Google Cloud 控制台,按一下右上方的工具列上的 Cloud Shell 圖示:

84688aa223b1c3a2.png

佈建並連線至環境的作業只需幾分鐘的時間。完成後,您應該會看到類似下方的內容:

320e18fedb7fbe0.png

這部虛擬機器都裝載了您需要的所有開發工具。提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提高網路效能和驗證能力。本程式碼研究室的所有工作都可以在瀏覽器中完成。不必安裝任何程式。

設定 gcloud

在 Cloud Shell 中設定專案 ID 和要部署 Cloud Run 工作的區域。將其儲存為 PROJECT_IDREGION 變數。您日後可以從其中一個 Cloud Run 位置挑選區域。

PROJECT_ID=[YOUR-PROJECT-ID]
REGION=us-central1
gcloud config set core/project $PROJECT_ID

啟用 API

啟用所有必要服務:

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

3. 取得程式碼

首先探索 Node.js 應用程式,擷取網頁的螢幕截圖並儲存至 Cloud Storage。稍後,您會建構應用程式的容器映像檔,並在 Cloud Run 中以工作的形式執行。

在 Cloud Shell 中執行下列指令,從「這個」存放區複製應用程式程式碼:

git clone https://github.com/GoogleCloudPlatform/jobs-demos.git

前往包含應用程式的目錄:

cd jobs-demos/screenshot

您應該會看到下列檔案版面配置:

screenshot
 |
 ├── Dockerfile
 ├── README.md
 ├── screenshot.js
 ├── package.json

以下是每個檔案的簡短說明:

  • screenshot.js 包含應用程式的 Node.js 程式碼。
  • package.json 會定義程式庫依附元件。
  • Dockerfile 會定義容器映像檔。

4. 探索程式碼

如要查看程式碼,請點選 Cloud Shell 視窗頂端的 Open Editor 按鈕,使用內建的文字編輯器。

15a2cdc9b7f6dfc6.png

以下是每個檔案的簡短說明。

screenshot.js

screenshot.js 會先新增 Puppeteer 和 Cloud Storage 做為依附元件。Puppeteer 是一個 Node.js 程式庫,可用於擷取網頁的螢幕截圖:

const puppeteer = require('puppeteer');
const {Storage} = require('@google-cloud/storage');

initBrowser 函式可初始化 Puppeteer 和 takeScreenshot 函式,用於擷取指定網址的螢幕截圖:

async function initBrowser() {
  console.log('Initializing browser');
  return await puppeteer.launch();
}

async function takeScreenshot(browser, url) {
  const page = await browser.newPage();

  console.log(`Navigating to ${url}`);
  await page.goto(url);

  console.log(`Taking a screenshot of ${url}`);
  return await page.screenshot({
    fullPage: true
  });
}

接著是取得或建立 Cloud Storage 值區的函式,以及另一個可將網頁螢幕截圖上傳至值區的函式:

async function createStorageBucketIfMissing(storage, bucketName) {
  console.log(`Checking for Cloud Storage bucket '${bucketName}' and creating if not found`);
  const bucket = storage.bucket(bucketName);
  const [exists] = await bucket.exists();
  if (exists) {
    // Bucket exists, nothing to do here
    return bucket;
  }

  // Create bucket
  const [createdBucket] = await storage.createBucket(bucketName);
  console.log(`Created Cloud Storage bucket '${createdBucket.name}'`);
  return createdBucket;
}

async function uploadImage(bucket, taskIndex, imageBuffer) {
  // Create filename using the current time and task index
  const date = new Date();
  date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
  const filename = `${date.toISOString()}-task${taskIndex}.png`;

  console.log(`Uploading screenshot as '${filename}'`)
  await bucket.file(filename).save(imageBuffer);
}

最後,main 函式是進入點:

async function main(urls) {
  console.log(`Passed in urls: ${urls}`);

  const taskIndex = process.env.CLOUD_RUN_TASK_INDEX || 0;
  const url = urls[taskIndex];
  if (!url) {
    throw new Error(`No url found for task ${taskIndex}. Ensure at least ${parseInt(taskIndex, 10) + 1} url(s) have been specified as command args.`);
  }
  const bucketName = process.env.BUCKET_NAME;
  if (!bucketName) {
    throw new Error('No bucket name specified. Set the BUCKET_NAME env var to specify which Cloud Storage bucket the screenshot will be uploaded to.');
  }

  const browser = await initBrowser();
  const imageBuffer = await takeScreenshot(browser, url).catch(async err => {
    // Make sure to close the browser if we hit an error.
    await browser.close();
    throw err;
  });
  await browser.close();

  console.log('Initializing Cloud Storage client')
  const storage = new Storage();
  const bucket = await createStorageBucketIfMissing(storage, bucketName);
  await uploadImage(bucket, taskIndex, imageBuffer);

  console.log('Upload complete!');
}

main(process.argv.slice(2)).catch(err => {
  console.error(JSON.stringify({severity: 'ERROR', message: err.message}));
  process.exit(1);
});

請注意以下與 main 方法相關的內容:

  • 系統會將網址做為引數傳遞。
  • 值區名稱會以使用者定義的 BUCKET_NAME 環境變數的形式傳入。值區名稱在所有 Google Cloud 中都不得重複。
  • Cloud Run 工作會傳送 CLOUD_RUN_TASK_INDEX 環境變數。Cloud Run 工作可以將多個應用程式副本當做不重複的工作執行。CLOUD_RUN_TASK_INDEX 代表執行中工作的索引。如果程式碼在 Cloud Run 工作之外執行時,預設值為零。當應用程式以多項工作的形式執行時,每個工作/容器都會收到負責的網址並擷取螢幕截圖,然後將圖片儲存至值區。

package.json

package.json 檔案會定義應用程式,並指定 Cloud Storage 和 Puppeteer 的依附元件:

{
  "name": "screenshot",
  "version": "1.0.0",
  "description": "Create a job to capture screenshots",
  "main": "screenshot.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Google LLC",
  "license": "Apache-2.0",
  "dependencies": {
    "@google-cloud/storage": "^5.18.2",
    "puppeteer": "^13.5.1"
  }
}

Dockerfile

Dockerfile 定義了應用程式的容器映像檔,以及所有必要的程式庫和依附元件:

FROM ghcr.io/puppeteer/puppeteer:16.1.0
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENTRYPOINT ["node", "screenshot.js"]

5. 部署工作

建立工作之前,您必須建立用來執行工作的服務帳戶。

gcloud iam service-accounts create screenshot-sa --display-name="Screenshot app service account"

storage.admin 角色授予服務帳戶,以便用來建立值區和物件。

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --role roles/storage.admin \
  --member serviceAccount:screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

您現在可以部署 Cloud Run 工作,其中包含執行工作所需的設定。

gcloud beta run jobs deploy screenshot \
  --source=. \
  --args="https://example.com" \
  --args="https://cloud.google.com" \
  --tasks=2 \
  --task-timeout=5m \
  --region=$REGION \
  --set-env-vars=BUCKET_NAME=screenshot-$PROJECT_ID \
  --service-account=screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

這會使用以來源為基礎的部署方式,並在不執行的情況下建立 Cloud Run 工作。

請注意網頁是以引數的形式傳入。用來儲存螢幕截圖的值區名稱會以環境變數的形式傳入。

您可以使用 --tasks 旗標指定要執行的工作數量,藉此並行執行多個容器副本。每個工作都代表一個執行中的容器副本。如果每項工作都能獨立處理部分資料,這時就適合使用多項工作。為提升作業效率,每項工作都會留意儲存在 CLOUD_RUN_TASK_INDEX 環境變數中的索引。程式碼負責判斷哪個工作會處理哪些資料子集。請注意本範例中的 --tasks=2。這可確保 2 個容器會執行要處理的 2 個網址。

每項工作最長可執行 24 小時。您可以使用 --task-timeout 旗標來減少這項逾時,如以下範例所示。所有工作都必須成功,工作才能順利完成。根據預設,系統不會重試失敗的工作。您可以設定工作失敗時重試。如果任何工作超過重試次數,則整個工作都會失敗。

根據預設,您的工作會盡可能平行處理許多工作。這等同於工作的工作數量,上限為 100 個。對於需要擴充能力有限的後端工作,建議您調降平行處理級別。例如支援有限有效連線的資料庫。你可以使用 --parallelism 旗標降低平行處理量。

6. 執行工作

在執行工作前,列出工作是否已建立:

gcloud run jobs list

✔
JOB: screenshot
REGION: us-central
LAST RUN AT:
CREATED: 2022-02-22 12:20:50 UTC

使用下列指令執行工作:

gcloud run jobs execute screenshot --region=$REGION

這麼做會執行工作。您可以列出目前和過去的執行作業:

gcloud run jobs executions list --job screenshot --region=$REGION

...
JOB: screenshot
EXECUTION: screenshot-znkmm
REGION: $REGION
RUNNING: 1
COMPLETE: 1 / 2
CREATED: 2022-02-22 12:40:42 UTC

說明執行作業。您應該會看到綠色的勾號和 tasks completed successfully 訊息:

gcloud run jobs executions describe screenshot-znkmm --region=$REGION

✔ Execution screenshot-znkmm in region $REGION
2 tasks completed successfully


Image:           $REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot at 311b20d9...
Tasks:           2
Args:            https://example.com https://cloud.google.com
Memory:          1Gi
CPU:             1000m
Task Timeout:    3600s
Parallelism:     2
Service account: 11111111-compute@developer.gserviceaccount.com
Env vars:
  BUCKET_NAME    screenshot-$PROJECT_ID

您也可以前往 Cloud 控制台的 Cloud Run 工作頁面查看狀態:

1afde14d65f0d9ce.png

如果您查看 Cloud Storage 值區,應該會看到已建立的兩個螢幕截圖檔案:

7c4d355f6f65106.png

您有時可能需要在執行作業完成前就停止執行作業。這可能是因為您覺得必須使用不同參數來執行工作,或是程式碼發生錯誤,因此您不需要使用不必要的運算時間。

如要停止執行工作,您需要刪除執行作業:

gcloud run jobs executions delete screenshot-znkmm --region=$REGION

7. 更新工作

下次執行時,Cloud Run 工作並不會自動取得新版本的容器。如果您變更工作的程式碼,就必須重建容器並更新工作。使用標記圖片可協助您確認目前使用的映像檔版本。

同樣地,您也需要更新工作,才能更新部分設定變數。此工作的後續執行作業會使用新的容器和配置設定。

使用 --args 標記更新工作,並變更應用程式擷取螢幕截圖的頁面。並更新 --tasks 標記,以反映頁數。

gcloud run jobs update screenshot \
  --args="https://www.pinterest.com" \
  --args="https://www.apartmenttherapy.com" \
  --args="https://www.google.com" \
  --region=$REGION \
  --tasks=3

再次執行工作。這次請傳入 --wait 旗標,等待執行完成:

gcloud run jobs execute screenshot --region=$REGION --wait

幾秒後,畫面上應該還會新增 3 張螢幕截圖:

ed0cbe0b5a5f9144.png

8. 排定工作

到目前為止,您都是以手動方式執行工作。在現實生活中,您可能會想依照事件或排程執行工作。我們來看看如何使用 Cloud Scheduler 依排程執行螢幕截圖工作。

首先,請確認 Cloud Scheduler API 已啟用:

gcloud services enable cloudscheduler.googleapis.com

前往 Cloud Run 工作詳細資料頁面,然後按一下 Triggers 部分:

3ae456368905472f.png

選取 Add Scheduler Trigger 按鈕:

48cbba777f75e1eb.png

右側隨即會開啟面板。以這個設定建立每天 9:00 執行的排程器工作,然後選取 Continue

81fd098be0db216.png

在下一頁選取預設的運算服務帳戶,然後選取 Create

fe479501dfb91f9f.png

現在,您應該會看到新建立的 Cloud Scheduler 觸發條件:

5a7bc6d96b970b92.png

按一下 View Details 前往「Cloud Scheduler」頁面。

您可以等到上午 9 點再啟動排程器,也可以選取 Force Run,手動觸發 Cloud Scheduler:

959525f2c8041a6a.png

幾秒後,您應該會看到 Cloud Scheduler 工作已成功執行:

d64e03fc84d61145.png

您也會看到 Cloud Scheduler 呼叫新增了 3 張螢幕截圖:

56398a0e827de8b0.png

9. 恭喜

恭喜,您已完成程式碼研究室!

清除所用資源 (選用)

如要避免產生費用,建議您清除資源。

如果不需要專案,您可以直接刪除專案。

gcloud projects delete $PROJECT_ID

如果您需要這項專案,可以個別刪除資源。

刪除原始碼:

rm -rf ~/jobs-demos/

刪除 Artifact Registry 存放區:

gcloud artifacts repositories delete containers --location=$REGION

刪除服務帳戶:

gcloud iam service-accounts delete screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

刪除 Cloud Run 工作:

gcloud run jobs delete screenshot --region=$REGION

刪除 Cloud Scheduler 工作:

gcloud scheduler jobs delete screenshot-scheduler-trigger --location=$REGION

刪除 Cloud Storage 值區:

gcloud storage rm --recursive gs://screenshot-$PROJECT_ID

涵蓋內容

  • 如何使用應用程式擷取網頁的螢幕截圖。
  • 如何建構應用程式的容器映像檔。
  • 如何為應用程式建立 Cloud Run 工作。
  • 如何將應用程式做為 Cloud Run 工作執行。
  • 如何更新工作。
  • 如何使用 Cloud Scheduler 排定工作時程。