每日图片:实验 3 - 使用最新照片创建拼贴

1. 概览

在此 Codelab 中,您将创建一个新的 Cloud Run 服务,即拼贴服务,该服务将由 Cloud Scheduler 定期触发。该服务会提取最新上传的图片并为这些照片创建拼贴:它会在 Cloud Firestore 中找到近期图片的列表,然后从 Cloud Storage 下载实际的图片文件。

df20f5d0402b54b4.png

学习内容

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

2. 设置和要求

自定进度的环境设置

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

96a9c957bc475304

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

启动 Cloud Shell

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

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

bce75f34b2c53987.png

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

f6ef2b5f13479f3a.png

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

3. 启用 API

您需要使用 Cloud Scheduler 定期触发 Cloud Run 服务。确保其已启用:

gcloud services enable cloudscheduler.googleapis.com

您应该会看到操作成功完成:

Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.

4. 克隆代码

如果之前的 Codelab 中尚未克隆该代码,请执行以下操作:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

然后,您可以转到包含该服务的目录:

cd serverless-photosharing-workshop/services/collage/nodejs

该服务的文件布局如下:

services
 |
 ├── collage
      |
      ├── nodejs
           |
           ├── Dockerfile
           ├── index.js
           ├── package.json

该文件夹中包含 3 个文件:

  • index.js 包含 Node.js 代码
  • package.json 定义库依赖项
  • Dockerfile 定义容器映像

5. 探索代码

依赖项

package.json 文件定义了所需的库依赖项:

{
  "name": "collage_service",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "bluebird": "^3.7.2",
    "express": "^4.17.1",
    "imagemagick": "^0.1.3",
    "@google-cloud/firestore": "^4.9.9",
    "@google-cloud/storage": "^5.8.3"
  }
}

我们依赖 Cloud Storage 库来读取和保存 Cloud Storage 中的图片文件。我们声明依赖于 Cloud Firestore 来提取之前存储的照片元数据。Express 是一种 JavaScript / 节点 Web 框架。Bluebird 用于处理 promise,而 imagemagick 是一个用于处理图片的库。

Dockerfile

Dockerfile 定义应用的容器映像:

FROM node:14-slim

# installing Imagemagick
RUN set -ex; \
  apt-get -y update; \
  apt-get -y install imagemagick; \
  rm -rf /var/lib/apt/lists/*

WORKDIR /picadaily/services/collage
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]

我们使用的是轻量级 Node 14 基础映像。我们正在安装 imagemagick 库。然后,我们安装代码所需的 NPM 模块,并使用 npm start 运行节点代码。

index.js

我们来详细了解一下 index.js 代码:

const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');

我们需要程序运行所需的各种依赖项:Express 是我们要使用的节点 Web 框架,ImageMagick 是用于处理图片操作的库,Bluebird 是一个用于处理 JavaScript promise 的库,Path 用于处理文件和目录路径,然后 Storage 和 Firestore 分别用于处理 Google Cloud Storage(我们的图片存储分区),以及 Cloud Firestore 数据存储区。

const app = express();

app.get('/', async (req, res) => {
    try {
        console.log('Collage request');

        /* ... */

    } catch (err) {
        console.log(`Error: creating the collage: ${err}`);
        console.error(err);
        res.status(500).send(err);
    }
});

上面我们有 Node 处理程序的结构:我们的应用响应 HTTP GET 请求。我们会进行一些错误处理,以防出现问题。现在,我们来看看这个结构内部是什么。

const thumbnailFiles = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
    .where('thumbnail', '==', true)
    .orderBy('created', 'desc')
    .limit(4).get();

if (snapshot.empty) {
    console.log('Empty collection, no collage to make');
    res.status(204).send("No collage created.");
} else {

    /* ... */

}

我们的拼图服务至少需要四张图片(已生成缩略图),因此请务必先上传 4 张图片。

我们从 Cloud Firerstore 中存储的元数据中检索用户上传的 4 张最新照片。我们检查生成的集合是否为空,然后在代码的 else 分支中继续操作。

让我们来收集文件名列表:

snapshot.forEach(doc => {
    thumbnailFiles.push(doc.id);
});
console.log(`Picture file names: ${JSON.stringify(thumbnailFiles)}`);

我们将从缩略图存储分区下载其中的每个文件,其名称来自我们在部署时设置的环境变量:

const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);

await Promise.all(thumbnailFiles.map(async fileName => {
    const filePath = path.resolve('/tmp', fileName);
    await thumbBucket.file(fileName).download({
        destination: filePath
    });
}));
console.log('Downloaded all thumbnails');

上传最新缩略图后,我们将使用 ImageMagick 库创建这些缩略图的 4x4 网格。我们使用 Bluebird 库及其 Promise 实现将回调驱动的代码转换为适用于 async / await 的代码,然后等待生成图片拼贴的 promise:

const collagePath = path.resolve('/tmp', 'collage.png');

const thumbnailPaths = thumbnailFiles.map(f => path.resolve('/tmp', f));
const convert = Promise.promisify(im.convert);
await convert([
    '(', ...thumbnailPaths.slice(0, 2), '+append', ')',
    '(', ...thumbnailPaths.slice(2), '+append', ')',
    '-size', '400x400', 'xc:none', '-background', 'none',  '-append',
    collagePath]);
console.log("Created local collage picture");

由于拼贴图片已保存到本地磁盘的临时文件夹中,我们现在需要将其上传到 Cloud Storage,然后返回成功响应(状态代码 2xx):

await thumbBucket.upload(collagePath);
console.log("Uploaded collage to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}");

res.status(204).send("Collage created.");

现在,让我们的 Node 脚本监听传入请求:

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
    console.log(`Started collage service on port ${PORT}`);
});

在源文件末尾,我们有指示让 Express 在 8080 默认端口上实际启动我们的 Web 应用。

6. 在本地测试

在将代码部署到云端之前,请在本地测试代码以确保它可以正常运行。

collage/nodejs 文件夹内,安装 npm 依赖项并启动服务器:

npm install; npm start

如果一切顺利,它应该会在端口 8080 上启动服务器:

Started collage service on port 8080

使用 CTRL-C 退出。

7. 构建并部署到 Cloud Run

在部署到 Cloud Run 之前,请将 Cloud Run 区域设置为其中一个受支持的区域和平台:managed

gcloud config set run/region europe-west1
gcloud config set run/platform managed

您可以检查是否已设置配置:

gcloud config list

...
[run]
platform = managed
region = europe-west1

您还可以依靠 Cloud Run 使用 Google Cloud Buildpack 为您构建容器映像,而无需使用 Cloud Build 手动构建和发布容器映像。

运行以下命令以构建容器映像:

BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT
SERVICE_NAME=collage-service
gcloud run deploy $SERVICE_NAME \
    --source . \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

请注意 –-source 标志。这是 Cloud Run 中基于源代码的部署 。如果源代码目录中存在 Dockerfile,则上传的源代码将使用该 Dockerfile 构建。如果源代码目录中没有 DockerfileGoogle Cloud Buildpacks 会使用由 Google 管理的安全基础映像,自动检测您使用的语言并提取代码的依赖项,以创建可用于生产环境的容器映像。这会标记 Cloud Run 以使用 Google Cloud Buildpack 构建 Dockerfile 中定义的容器映像。

另请注意,基于源代码的部署使用 Artifact Registry 来存储构建的容器。Artifact Registry 是 Google Container Registry 的现代版本。CLI 会提示您启用该 API(如果项目中尚未启用该 API),并在要部署到的区域中创建名为 cloud-run-source-deploy 的代码库。

--no-allow-unauthenticated 标志将 Cloud Run 服务设置为只能由特定服务账号触发的内部服务。

8. 设置 Cloud Scheduler

现在,Cloud Run 服务已准备就绪并已部署,接下来可以创建定期时间表,以每分钟调用一次服务。

创建服务账号:

SERVICE_ACCOUNT=collage-scheduler-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
   --display-name "Collage Scheduler Service Account"

向服务账号授予调用 Cloud Run 服务的权限:

gcloud run services add-iam-policy-binding $SERVICE_NAME \
   --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --role=roles/run.invoker

创建一个每 1 分钟执行的 Cloud Scheduler 作业:

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud scheduler jobs create http $SERVICE_NAME-job --schedule "* * * * *" \
   --http-method=GET \
   --location=europe-west1 \
   --uri=$SERVICE_URL \
   --oidc-service-account-email=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --oidc-token-audience=$SERVICE_URL

您可以前往 Cloud 控制台中的 Cloud Scheduler 部分,确认它已设置完毕并指向 Cloud Run 服务网址:

35119e28c1da53f3

9. 测试服务

如需测试设置是否有效,请在 thumbnails 存储分区中检查拼图图片(称为 collage.png)。您还可以检查服务的日志:

93922335a384be2e

10. 清理(可选)

如果您不打算继续完成本系列中的其他实验,可以清理资源以节省成本,并成为一个整体优秀的云公民。您可以按以下步骤逐个清理资源。

删除服务:

gcloud run services delete $SERVICE_NAME -q

删除 Cloud Scheduler 作业:

gcloud scheduler jobs delete $SERVICE_NAME-job -q

或者,您也可以删除整个项目:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

11. 恭喜!

恭喜!您创建了一项预定服务:借助 Cloud Scheduler,该调度程序每分钟都会针对一个 Pub/Sub 主题推送一条消息,您的 Cloud Run 拼图服务被调用并能够将图片附加到一起,从而生成图片。

所学内容

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

后续步骤