Cloud Run 作业使用入门

1. 简介

1965fab24c502bd5

概览

Cloud Run 服务非常适合运行无限期监听 HTTP 请求的容器,而 Cloud Run 作业则更适合运行完成(目前最长 24 小时)且不处理请求的容器。例如,如果以 Cloud Run 作业的形式实现,则处理数据库中的记录、处理 Cloud Storage 存储桶中的文件列表或长时间运行的操作(例如计算 Pi)的效果会比较好。

作业无法处理请求或侦听端口。这意味着 Cloud Run 作业与 Cloud Run 服务不同,应该不会捆绑 Web 服务器。作业容器应在完成后退出。

在 Cloud Run 作业中,您可以通过指定多个任务来并行运行容器的多个副本。每个任务代表容器的一个正在运行的副本。如果每个任务都可以独立处理您的一部分数据,则使用多个任务非常有用。例如,当 10 项任务逐个并行处理 1000 个记录或文件时,可以更快速地处理来自 Cloud SQL 的 10000 条记录或来自 Cloud Storage 的 10000 个文件。

使用 Cloud Run 作业分为两个步骤:

  1. 创建作业:这包含运行作业所需的所有配置,例如容器映像、区域、环境变量。
  2. 运行作业:这将创建新的作业执行。(可选)使用 Cloud Scheduler 将作业设置为按计划运行。

在此 Codelab 中,您首先要探索 Node.js 应用,了解如何截取网页屏幕截图并将其存储到 Cloud Storage 中。然后,您可以为应用构建容器映像,在 Cloud Run 作业上运行应用,更新作业以处理更多网页,然后使用 Cloud Scheduler 按计划运行作业。

学习内容

  • 如何使用应用来截取网页屏幕截图。
  • 如何为应用构建容器映像。
  • 如何为应用创建 Cloud Run 作业。
  • 如何以 Cloud Run 作业的形式运行应用。
  • 如何更新作业。
  • 如何使用 Cloud Scheduler 安排作业。

2. 设置和要求

自定进度的环境设置

  1. 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个

295004821bab6a87

37d264871000675d

96d86d3d5655cdbe.png

  • 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串。您可以随时对其进行更新。
  • 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Cloud 控制台会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(通常用 PROJECT_ID 标识)。如果您不喜欢生成的 ID,可以再随机生成一个 ID。或者,您也可以尝试自己的项目 ID,看看是否可用。完成此步骤后便无法更改该 ID,并且此 ID 在项目期间会一直保留。
  • 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档
  1. 接下来,您需要在 Cloud 控制台中启用结算功能,以便使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有的话)。若要关闭资源以避免产生超出本教程范围的结算费用,您可以删除自己创建的资源或删除项目。Google Cloud 新用户符合参与 300 美元免费试用计划的条件。

启动 Cloud Shell

虽然可以通过笔记本电脑对 Google Cloud 进行远程操作,但在此 Codelab 中,您将使用 Google Cloud Shell,这是一个在云端运行的命令行环境。

Google Cloud 控制台 中,点击右上角工具栏中的 Cloud Shell 图标:

84688aa223b1c3a2

预配和连接到环境应该只需要片刻时间。完成后,您应该会看到如下内容:

320e18fedb7fbe0

这个虚拟机已加载了您需要的所有开发工具。它提供了一个持久的 5 GB 主目录,并且在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。您在此 Codelab 中的所有工作都可以在浏览器中完成。您无需安装任何程序。

设置 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

以下是对每个文件的简要说明。

screenshot.js

screenshot.js 首先将 Puppeteer 和 Cloud Storage 添加为依赖项。Puppeteer 是一个用于截取网页屏幕截图的 Node.js 库:

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

系统提供了一个用于初始化 Puppeteer 的 initBrowser 函数,以及一个用于截取给定网址屏幕截图的 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_TASK_INDEX 环境变量由 Cloud Run 作业传递。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 Console 的 Cloud Run 作业页面上查看状态:

1afde14d65f0d9ce

如果您检查 Cloud Storage 存储桶,应该会看到已创建的两个屏幕截图文件:

7c4d355f6f65106

有时,您可能需要在执行作业完成之前将其停止,原因可能是您已经意识到需要使用不同的参数运行作业,或者代码中存在错误,以及您不想使用不必要的计算时间。

要停止执行作业,您需要删除执行作业:

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

选择 Add Scheduler Trigger 按钮:

48cbba777f75e1eb

系统会在右侧打开一个面板。使用以下配置创建一个在每天 9:00 运行的 Scheduler 作业,并选择 Continue

81fd098be0db216

在下一页中,选择默认计算服务账号,然后选择 Create

fe479501dfb91f9f.png

您现在应该会看到已创建新的 Cloud Scheduler 触发器:

5a7bc6d96b970b92

点击 View Details 进入 Cloud Scheduler 页面。

您可以等到上午 9 点让调度器启动,也可以选择 Force Run 来手动触发 Cloud Scheduler:

959525f2c8041a6a

几秒钟后,您应该会看到 Cloud Scheduler 作业已成功执行:

d64e03fc84d61145.png

您还应该看到通过 Cloud Scheduler 中的调用添加的另外 3 个屏幕截图:

56398a0e827de8b0

9. 恭喜

恭喜,您已完成此 Codelab!

清理(可选)

为避免产生费用,建议您清理资源。

如果您不需要该项目,可以直接将其删除:

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 安排作业。