1. 简介

概览
虽然 Cloud Run 服务非常适合针对 HTTP 请求运行无限期侦听的容器,但 Cloud Run 作业可能更适合运行以完成操作并且不处理请求的容器。例如,如果以 Cloud Run 作业的形式实现,则处理数据库中的记录、处理 Cloud Storage 存储桶中的文件列表或长时间运行的操作(例如计算 Pi)的效果会比较好。
作业无法处理请求或侦听端口。这意味着 Cloud Run 作业与 Cloud Run 服务不同,应该不会捆绑 Web 服务器。作业容器应在完成后退出。
在 Cloud Run 作业中,您可以通过指定多个任务来并行运行容器的多个副本。每个任务代表容器的一个正在运行的副本。如果每个任务都可以独立处理您的一部分数据,则使用多个任务非常有用。例如,当 10 项任务逐个并行处理 1000 个记录或文件时,可以更快速地处理来自 Cloud SQL 的 10000 条记录或来自 Cloud Storage 的 10000 个文件。
作业工作流
Cloud Run 作业使用起来很简单,只需两个步骤:
- 创建作业。此步骤封装了运行作业所需的所有配置,例如容器映像、区域、环境变量。
- 运行该作业。这将创建一个新的作业执行。(可选)使用 Cloud Scheduler 将作业设置为按计划运行。
预览版限制
在预览版期间,Cloud Run 作业具有以下限制:
- 每个区域的每个项目最多可同时运行 50 个执行作业(来自相同或不同作业)。
- 您可以在 Cloud Console 的 Cloud Run 作业页面查看现有作业、启动执行作业和监控执行状态。请使用
gcloud创建新作业,因为 Cloud Console 目前不支持创建新作业。 - 请勿将 Cloud Run 作业用于生产工作负载。因为无法保证可靠性或性能。Cloud Run 作业在向 Google Analytics(分析)发布之前,可能会以不向后兼容的方式更改,恕不另行通知。
在此 Codelab 中,您首先要探索 Node.js 应用,了解如何截取网页屏幕截图并将其存储到 Cloud Storage 中。然后,为该应用构建容器映像,以作业的形式在 Cloud Run 上运行应用,更新作业以处理更多网页,然后使用 Cloud Scheduler 按计划运行该作业。
学习内容
- 如何使用应用来截取网页屏幕截图。
- 如何为应用构建容器映像。
- 如何为应用创建 Cloud Run 作业。
- 如何以 Cloud Run 作业的形式运行应用。
- 如何更新作业。
- 如何使用 Cloud Scheduler 安排作业。
2. 设置和要求
自定进度的环境设置
- 登录 Google Cloud Console,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 帐号,则必须创建一个。



- 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串,您可以随时对其进行更新。
- 项目 ID 在所有 Google Cloud 项目中必须是唯一的,并且不可变(一经设置便无法更改)。Cloud Console 会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(它通常标识为
PROJECT_ID),因此如果您不喜欢某个 ID,请再生成一个随机 ID,还可以尝试自己创建一个,并确认是否可用。然后,项目创建后,ID 会处于“冻结”状态。 - 第三个值是一些 API 使用的项目编号。如需详细了解所有这三个值,请参阅文档。
- 接下来,您需要在 Cloud Console 中启用结算功能,才能使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。要关闭资源以避免产生超出本教程范围的费用,请按照此 Codelab 末尾提供的任何“清理”说明操作。Google Cloud 的新用户符合参与 $300 USD 免费试用计划的条件。
启动 Cloud Shell
虽然可以通过笔记本电脑对 Google Cloud 进行远程操作,但在此 Codelab 中,您将使用 Google Cloud Shell,这是一个在云端运行的命令行环境。
在 Google Cloud Console 中,点击右上角工具栏中的 Cloud Shell 图标:

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

这个虚拟机已加载了您需要的所有开发工具。它提供了一个持久的 5GB 主目录,并且在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。只需一个浏览器,即可完成本实验中的所有工作。
设置 gcloud
在 Cloud Shell 中,设置项目 ID 以及要将 Cloud Run 作业部署到的区域。将它们保存为 PROJECT_ID 和 REGION 变量。您可以从某个 Cloud Run 位置中选择一个区域。
PROJECT_ID=[YOUR-PROJECT-ID] REGION=[YOUR-REGION] gcloud config set core/project $PROJECT_ID gcloud config set run/region $REGION
启用 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 按钮,以使用内置文本编辑器。

以下是对每个文件的简要说明。
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 node:17-alpine
# Installs latest Chromium (92) package.
RUN apk add --no-cache \
chromium \
nss \
freetype \
harfbuzz \
ca-certificates \
ttf-freefont \
nodejs \
npm
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
&& mkdir -p /home/pptruser/Downloads /app \
&& chown -R pptruser:pptruser /home/pptruser \
&& chown -R pptruser:pptruser /app
# Install dependencies
COPY package*.json ./
RUN npm install
# Copy all files
COPY . .
# Run everything after as a non-privileged user.
USER pptruser
ENTRYPOINT ["node", "screenshot.js"]
5. 构建和发布容器映像
Artifact Registry 是 Google Cloud 上的容器映像存储和管理服务。如需了解详情,请参阅使用容器映像。Artifact Registry 可以将 Docker 和 OCI 容器映像存储在 Docker 存储库中。
创建名为 containers 的新 Artifact Registry 存储库:
gcloud artifacts repositories create containers --repository-format=docker --location=$REGION
构建并发布容器映像:
gcloud builds submit -t $REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot:v1
几分钟后,您应该会看到在 Artifact Registry 中构建和托管的容器映像。

6.创建作业
在创建作业之前,您需要创建一个用于运行作业的服务帐号。
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 create screenshot \ --image=$REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot:v1 \ --args="https://example.com" \ --args="https://cloud.google.com" \ --tasks=2 \ --task-timeout=5m \ --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 个容器。
每个任务最长可以运行 1 小时。您可以使用 --task-timeout 标记缩短此超时时间,如此示例中所示。所有任务都必须成功,作业才能成功完成。默认情况下不会重试失败的任务。您可以将任务配置为在失败后重试。如果任何任务超过重试次数,整个作业都会失败。
默认情况下,您的作业将并行运行尽可能多的任务。此值等于作业的任务数量,最多为 100 个。如果作业要访问扩容能力有限的后端,您可能需要设置较小的最大并行数量。例如,支持有限数量的活跃连接的数据库。您可以使用 --parallelism 标记减少最大并行数量。
7. 恭喜
恭喜,您已完成此 Codelab!
所学内容
- 如何使用应用来截取网页屏幕截图。
- 如何为应用构建容器映像。
- 如何为应用创建 Cloud Run 作业。