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