1. 简介
概览
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 作业分为两个步骤:
- 创建作业:这包含运行作业所需的所有配置,例如容器映像、区域、环境变量。
- 运行作业:这将创建新的作业执行。(可选)使用 Cloud Scheduler 将作业设置为按计划运行。
在此 Codelab 中,您首先要探索 Node.js 应用,了解如何截取网页屏幕截图并将其存储到 Cloud Storage 中。然后,您可以为应用构建容器映像,在 Cloud Run 作业上运行应用,更新作业以处理更多网页,然后使用 Cloud Scheduler 按计划运行作业。
学习内容
- 如何使用应用来截取网页屏幕截图。
- 如何为应用构建容器映像。
- 如何为应用创建 Cloud Run 作业。
- 如何以 Cloud Run 作业的形式运行应用。
- 如何更新作业。
- 如何使用 Cloud Scheduler 安排作业。
2. 设置和要求
自定进度的环境设置
- 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。
- 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串。您可以随时对其进行更新。
- 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Cloud 控制台会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(通常用
PROJECT_ID
标识)。如果您不喜欢生成的 ID,可以再随机生成一个 ID。或者,您也可以尝试自己的项目 ID,看看是否可用。完成此步骤后便无法更改该 ID,并且此 ID 在项目期间会一直保留。 - 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档。
- 接下来,您需要在 Cloud 控制台中启用结算功能,以便使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有的话)。若要关闭资源以避免产生超出本教程范围的结算费用,您可以删除自己创建的资源或删除项目。Google Cloud 新用户符合参与 300 美元免费试用计划的条件。
启动 Cloud Shell
虽然可以通过笔记本电脑对 Google Cloud 进行远程操作,但在此 Codelab 中,您将使用 Google Cloud Shell,这是一个在云端运行的命令行环境。
在 Google Cloud 控制台 中,点击右上角工具栏中的 Cloud Shell 图标:
预配和连接到环境应该只需要片刻时间。完成后,您应该会看到如下内容:
这个虚拟机已加载了您需要的所有开发工具。它提供了一个持久的 5 GB 主目录,并且在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。您在此 Codelab 中的所有工作都可以在浏览器中完成。您无需安装任何程序。
设置 gcloud
在 Cloud Shell 中,设置项目 ID 以及要将 Cloud Run 作业部署到的区域。将它们保存为 PROJECT_ID
和 REGION
变量。将来,您将能够从其中一个 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
按钮,以使用内置文本编辑器。
以下是对每个文件的简要说明。
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 作业页面上查看状态:
如果您检查 Cloud Storage 存储桶,应该会看到已创建的两个屏幕截图文件:
有时,您可能需要在执行作业完成之前将其停止,原因可能是您已经意识到需要使用不同的参数运行作业,或者代码中存在错误,以及您不想使用不必要的计算时间。
要停止执行作业,您需要删除执行作业:
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 个屏幕截图:
8. 安排作业
到目前为止,您是在手动运行作业。在实际场景中,您可能需要运行作业来响应事件或按计划运行作业。我们来看看如何使用 Cloud Scheduler 按计划运行屏幕截图作业。
首先,确保已启用 Cloud Scheduler API:
gcloud services enable cloudscheduler.googleapis.com
转到 Cloud Run 作业详情页面,然后点击 Triggers
部分:
选择 Add Scheduler Trigger
按钮:
系统会在右侧打开一个面板。使用以下配置创建一个在每天 9:00 运行的 Scheduler 作业,并选择 Continue
:
在下一页中,选择默认计算服务账号,然后选择 Create
:
您现在应该会看到已创建新的 Cloud Scheduler 触发器:
点击 View Details
进入 Cloud Scheduler 页面。
您可以等到上午 9 点让调度器启动,也可以选择 Force Run
来手动触发 Cloud Scheduler:
几秒钟后,您应该会看到 Cloud Scheduler 作业已成功执行:
您还应该看到通过 Cloud Scheduler 中的调用添加的另外 3 个屏幕截图:
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 安排作业。