1. 概览
在之前的实验中,您构建了 Pic-a-daily 应用的事件驱动型版本,该版本使用 Google Cloud Storage 触发的 Cloud Functions 函数以用于图片分析服务,一个 GCS 通过 Pub/Sub 触发 Cloud Run 容器的缩略图服务触发 Cloud Run 容器,并使用 Eventarc 触发 Cloud Run 上的图片垃圾回收器服务。还有一项 Cloud Scheduler 触发的拼贴服务:
在本实验中,您将创建一个应用的编排版本。您无需使用不同类型的事件流经系统,而是使用 Workflows 来编排和调用服务,如下所示:
学习内容
- App Engine
- Cloud Firestore
- Cloud Functions
- Cloud Run
- Workflows
2. 设置和要求
自定进度的环境设置
请记住项目 ID,它在所有 Google Cloud 项目中都是唯一的名称(上述名称已被占用,您无法使用,抱歉!)。它稍后将在此 Codelab 中被称为 PROJECT_ID
。
- 接下来,您需要在 Cloud 控制台中启用结算功能,才能使用 Google Cloud 资源。
运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。请务必按照“清理”部分部分,其中会指导您如何关停资源,以免产生超出本教程范围的结算费用。Google Cloud 的新用户符合参与 300 美元的免费试用计划的条件。
启动 Cloud Shell
虽然可以通过笔记本电脑对 Google Cloud 进行远程操作,但在此 Codelab 中,您将使用 Google Cloud Shell,这是一个在云端运行的命令行环境。
在 GCP 控制台中,点击右上角工具栏上的 Cloud Shell 图标:
预配和连接到环境应该只需要片刻时间。完成后,您应该会看到如下内容:
这个虚拟机已加载了您需要的所有开发工具。它提供了一个持久的 5GB 主目录,并且在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。只需一个浏览器,即可完成本实验中的所有工作。
3. Workflows 简介
您可以使用 Workflows 创建无服务器工作流,以便按照您定义的顺序将一系列无服务器任务关联在一起。您可以结合使用 Google Cloud 的 API、Cloud Functions 和 Cloud Run 等无服务器产品以及对外部 API 的调用,以创建灵活的无服务器应用。
正如您对编排器所期望的那样,Workflows 允许您使用基于 YAML/JSON 的工作流定义语言来定义业务逻辑的流,并提供 Workflows Execution API 和 Workflows 界面来触发这些流。
它不仅仅是一个具有下列可配置内置功能的编排系统:
- 在步骤之间灵活地进行重试和错误处理,确保步骤的执行可靠。
- 在步骤之间进行 JSON 解析和变量传递,以避免粘合代码。
- 用于决策的表达式公式支持按条件执行步骤。
- 适用于模块化且可重复使用的工作流的子工作流。
- 通过支持外部服务,您可以编排 Google Cloud 以外的服务。
- 为 Google Cloud 和外部服务提供身份验证支持,以确保安全执行步骤。
- 连接到 Google Cloud 服务(例如 Pub/Sub、Firestore、Tasks、Secret Manager),可简化集成。
更不用说,Workflows 是一款全代管式无服务器产品。您无需配置或扩缩服务器,并且只需为实际用量付费。
4. 启用 API
在本实验中,您将使用 Workflows 连接 Cloud Functions 和 Cloud Run 服务。您还将使用 App Engine、Cloud Build、Vision API 和其他服务。
在 Cloud Shell 中,确保启用了所有必要的服务:
gcloud services enable \ appengine.googleapis.com \ cloudbuild.googleapis.com \ cloudfunctions.googleapis.com \ compute.googleapis.com \ firestore.googleapis.com \ run.googleapis.com \ vision.googleapis.com \ workflows.googleapis.com \
一段时间后,您应该会看到操作成功完成:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
5. 获取代码
如果之前的 Codelab 中没有获得过该代码,请执行以下操作:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
您将获得以下与此实验相关的文件夹结构:
frontend | workflows | ├── functions ├── |── trigger-workflow ├── |── vision-data-transform ├── services ├── |── collage ├── |── thumbnails ├── workflows.yaml
以下是相关文件夹:
frontend
包含我们将在实验 4 中重复使用的 App Engine 前端。functions
包含为工作流创建的 Cloud Functions 函数。services
包含为工作流修改的 Cloud Run 服务。workflows.yaml
是工作流定义文件。
6. 探索 Workflows YAML
workflows.yaml 通过一系列步骤定义了工作流。我们来仔细了解一下它。
在工作流开始时,系统会传入一些参数。它们将由两个触发 Workflows 的 Cloud Functions 函数传入。我们稍后会介绍这些函数,但 Workflows 的启动方式如下:
在 YAML 中,您可以看到这些参数在 init
步骤中分配给了变量,例如触发事件的文件和存储分区名称,以及 Workflows 将调用的某些 Cloud Functions 和 Cloud Run 服务的网址:
main: params: [args] steps: - init: assign: - file: ${args.file} - bucket: ${args.bucket} - gsUri: ${"gs://" + bucket + "/" + file} - projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")} - urls: ${args.urls}
接下来,Workflows 检查事件类型。支持两种事件类型:object.finalize
(文件保存在 Cloud Storage 存储分区中时发出)和 object.delete
(文件被删除时发出)。执行任何其他操作都会引发不受支持的事件异常。
以下是 YAML 工作流定义中的步骤,我们将检查文件存储事件类型:
- eventTypeSwitch: switch: - condition: ${args.eventType == "google.storage.object.finalize"} next: imageAnalysisCall - condition: ${args.eventType == "google.storage.object.delete"} next: pictureGarbageCollectionGCS - eventTypeNotSupported: raise: ${"eventType " + args.eventType + " is not supported"} next: end
请注意 Workflows 如何通过 switch 指令及其各种条件支持 switch 语句和异常处理,以及如何在无法识别事件时引发错误。
接下来,我们来看看 imageAnalysisCall
。这是来自 Workflows 的一系列调用,用于调用 Vision API 来分析图片,转换 Vision API 响应数据以对图片中识别出的内容的标签进行排序,选择主色,检查图片是否可以安全显示,然后将元数据保存到 Cloud Firestore。
请注意,除 Vision Transform Cloud Functions 函数(稍后将部署)之外的所有其他操作都在 Workflows 中完成:
YAML 格式的步骤如下:
- imageAnalysisCall: call: http.post args: url: https://vision.googleapis.com/v1/images:annotate headers: Content-Type: application/json auth: type: OAuth2 body: requests: - image: source: gcsImageUri: ${gsUri} features: - type: LABEL_DETECTION - type: SAFE_SEARCH_DETECTION - type: IMAGE_PROPERTIES result: imageAnalysisResponse - transformImageAnalysisData: call: http.post args: url: ${urls.VISION_DATA_TRANSFORM_URL} auth: type: OIDC body: ${imageAnalysisResponse.body} result: imageMetadata - checkSafety: switch: - condition: ${imageMetadata.body.safe == true} next: storeMetadata next: end - storeMetadata: call: http.request args: url: ${"https://firestore.googleapis.com/v1/projects/" + projectId + "/databases/(default)/documents/pictures/" + file + "?updateMask.fieldPaths=color&updateMask.fieldPaths=labels&updateMask.fieldPaths=created"} auth: type: OAuth2 method: PATCH body: name: ${"projects/" + projectId + "/databases/(default)/documents/pictures/" + file} fields: color: stringValue: ${imageMetadata.body.color} created: timestampValue: ${imageMetadata.body.created} labels: arrayValue: values: ${imageMetadata.body.labels} result: storeMetadataResponse
图片分析完毕后,接下来的两个步骤是创建图片的缩略图和最新图片的拼贴图。具体方法是部署 2 项 Cloud Run 服务,并通过 thumbnailCall
和 collageCall
步骤调用这些服务:
YAML 中的步骤:
- thumbnailCall: call: http.post args: url: ${urls.THUMBNAILS_URL} auth: type: OIDC body: gcsImageUri: ${gsUri} result: thumbnailResponse - collageCall: call: http.get args: url: ${urls.COLLAGE_URL} auth: type: OIDC result: collageResponse
此执行分支以 finalizeCompleted
步骤中每项服务返回状态代码结束:
- finalizeCompleted: return: imageAnalysis: ${imageAnalysisResponse.code} storeMetadata: ${storeMetadataResponse.code} thumbnail: ${thumbnailResponse.code} collage: ${collageResponse.code}
执行的另一个分支是从主存储分区中删除某个文件,该文件包含图片的高分辨率版本。在该分支中,我们需要从包含缩略图的存储分区中删除图片缩略图,并从 Firestore 中删除该图片的元数据。这两个操作都通过 Workflows 中的 HTTP 调用完成:
YAML 中的步骤:
- pictureGarbageCollectionGCS: try: call: http.request args: url: ${"https://storage.googleapis.com/storage/v1/b/thumbnails-" + projectId + "/o/" + file} auth: type: OAuth2 method: DELETE result: gcsDeletionResult except: as: e steps: - dummyResultInOutVar: assign: - gcsDeletionResult: code: 200 body: "Workaround for empty body response" - pictureGarbageCollectionFirestore: call: http.request args: url: ${"https://firestore.googleapis.com/v1/projects/" + projectId + "/databases/(default)/documents/pictures/" + file} auth: type: OAuth2 method: DELETE result: firestoreDeletionResult
delete 分支最后会返回每个步骤的结果 / 代码:
- deleteCompleted: return: gcsDeletion: ${gcsDeletionResult} firestoreDeletion: ${firestoreDeletionResult.code}
在以下步骤中,我们将创建 Workflows 的所有外部依赖项:存储分区、Cloud Functions、Cloud Run 服务和 Firestore 数据库。
7. 创建存储分区
您需要为图片设置 2 个存储分区:一个用于保存原始高分辨率图片,另一个用于保存图片的缩略图。
使用 gsutil
工具创建一个具有统一访问权限的公共区域级(本例中为欧洲)存储分区,以便用户上传照片:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT} gsutil mb -l EU gs://${BUCKET_PICTURES} gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES} gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
为缩略图再创建一个公开区域存储分区:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT} gsutil mb -l EU gs://${BUCKET_THUMBNAILS} gsutil uniformbucketlevelaccess set on gs://${BUCKET_THUMBNAILS} gsutil iam ch allUsers:objectViewer gs://${BUCKET_THUMBNAILS}
您可以访问 Cloud 控制台的 Cloud Storage 部分,仔细检查存储分区是否已创建并公开:
8. Vision 数据转换 (Cloud Functions)
Workflows.yaml 以 init
、eventTypeSwitch
、eventTypeNotSupported
步开头。这可确保将来自存储分区的事件路由到正确的步骤。
对于 object.finalize
事件,imageAnalysisCall
步骤会调用 Vision API 以提取所创建图片的元数据。所有这些步骤都在 Workflows 中完成:
接下来,我们需要转换从 Vision API 返回的数据,然后才能将其保存到 Firestore。更具体地说,我们需要:
- 列出为图片返回的标签。
- 检索图片的主色。
- 确定照片是否安全。
这是在 Cloud Functions 函数的代码中完成的,Workflows 会直接调用此函数:
探索代码
Cloud Functions 函数称为 vision-data-transform
。您可以在 index.js 中查看其完整代码。如您所见,此函数的唯一目的是执行从 JSON 到 JSON 的转换,以便方便地在 Firestore 中存储图片元数据。
部署到 Cloud Functions
导航到该文件夹:
cd workflows/functions/vision-data-transform/nodejs
设置您选择的区域:
export REGION=europe-west1 gcloud config set functions/region ${REGION}
使用以下命令部署该函数:
export SERVICE_NAME=vision-data-transform gcloud functions deploy ${SERVICE_NAME} \ --source=. \ --runtime nodejs10 \ --entry-point=vision_data_transform \ --trigger-http \ --allow-unauthenticated
部署函数后,Workflows transformImageAnalysisData
步骤将能够调用此函数以执行 Vision API 数据转换。
9. 准备数据库
工作流程中的下一步是通过图片数据检查图片的安全性,然后将 Vision API 返回的图片相关信息存储到 Cloud Firestore 数据库(一种快速、全代管式、无服务器、云原生的 NoSQL 文档数据库)中:
这两项操作都在 Workflows 中完成,但您需要创建 Firestore 数据库才能存储元数据。
首先,在您想要 Firestore 数据库的区域中创建 App Engine 应用(Firestore 的一项要求):
export REGION_FIRESTORE=europe-west2 gcloud app create --region=${REGION_FIRESTORE}
接下来,在同一区域中创建 Firestore 数据库:
gcloud firestore databases create --region=${REGION_FIRESTORE}
这些文档将在我们的集合中以编程方式创建,并包含 4 个字段:
- name(字符串):已上传图片的文件名,也是文档的键
- labels(字符串数组):Vision API 识别出的项目的标签
- color(字符串):主色的十六进制颜色代码(即#ab12ef)
- created(日期):存储此图片的元数据时的时间戳
- thumbnail(布尔值):选填字段,如果为此照片生成了缩略图,此字段将是 true
我们将在 Firestore 中搜索包含缩略图的图片,并按创建日期排序,因此需要创建一个搜索索引。您可以使用以下命令创建索引:
gcloud firestore indexes composite create --collection-group=pictures \ --field-config field-path=thumbnail,order=descending \ --field-config field-path=created,order=descending
请注意,索引创建过程最多可能需要 10 分钟左右。
创建索引后,您可以在 Cloud 控制台中查看该索引:
Workflows storeMetadata
步骤现在能够将图片元数据存储到 Firestore。
10. 缩略图服务 (Cloud Run)
链中的下一个步骤是创建图片的缩略图。这是在 Cloud Run 服务的代码中完成的,Workflows 会在 thumbnailCall
步骤中调用此服务:
探索代码
Cloud Run 服务称为 thumbnails
。您可以在 index.js 中查看其完整代码。
构建和发布容器映像
Cloud Run 可以运行容器,但您首先需要构建容器映像(在 Dockerfile
中定义)。Google Cloud Build 可用于构建容器映像,然后托管到 Google Container Registry。
导航到该文件夹:
cd workflows/services/thumbnails/nodejs
构建:
export SERVICE_SRC=thumbnails export SERVICE_NAME=${SERVICE_SRC}-service gcloud builds submit \ . \ --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/${SERVICE_NAME}
一两分钟后,构建应该会成功,容器将部署到 Google Container Registry。
部署到 Cloud Run
设置一些所需的变量和配置:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT} export REGION=europe-west1 gcloud config set run/region ${REGION} gcloud config set run/platform managed
使用以下命令进行部署:
gcloud run deploy ${SERVICE_NAME} \ --image gcr.io/${GOOGLE_CLOUD_PROJECT}/${SERVICE_NAME} \ --no-allow-unauthenticated \ --memory=1Gi \ --update-env-vars BUCKET_THUMBNAILS=${BUCKET_THUMBNAILS}
部署该服务后,Workflows thumbnailCall
步骤将能够调用此服务。
11. 拼图服务 (Cloud Run)
链中的下一个步骤是根据最新的图片创建拼贴。这是在 Cloud Run 服务的代码中完成的,Workflows 会在 collageCall
步骤中调用此服务:
探索代码
Cloud Run 服务称为 collage
。您可以在 index.js 中查看其完整代码。
构建和发布容器映像
Cloud Run 可以运行容器,但您首先需要构建容器映像(在 Dockerfile
中定义)。Google Cloud Build 可用于构建容器映像,然后托管到 Google Container Registry。
导航到该文件夹:
cd services/collage/nodejs
构建:
export SERVICE_SRC=collage export SERVICE_NAME=${SERVICE_SRC}-service gcloud builds submit \ . \ --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/${SERVICE_NAME}
一两分钟后,构建应该会成功,容器将部署到 Google Container Registry。
部署到 Cloud Run
设置一些所需的变量和配置:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT} export REGION=europe-west1 gcloud config set run/region ${REGION} gcloud config set run/platform managed
部署:
gcloud run deploy ${SERVICE_NAME} \ --image gcr.io/${GOOGLE_CLOUD_PROJECT}/${SERVICE_NAME} \ --no-allow-unauthenticated \ --memory=1Gi \ --update-env-vars BUCKET_THUMBNAILS=${BUCKET_THUMBNAILS}
部署服务后,您可以在 Cloud 控制台的“Cloud Run”部分下检查两项服务是否正在运行,并且工作流 collageCall
步骤将能够调用此服务:
12. Workflows 部署
我们部署了 Workflows 的所有外部依赖项。其余所有步骤(finalizeCompleted
、pictureGarbageCollectionGCS
、pictureGarbageCollectionFirestore
、deleteCompleted
)都可以由 Workflows 自行完成。
现在可以部署 Workflows 了!
前往包含 workflows.yaml
文件的文件夹,并使用以下命令部署该文件:
export WORKFLOW_REGION=europe-west4 export WORKFLOW_NAME=picadaily-workflows gcloud workflows deploy ${WORKFLOW_NAME} \ --source=workflows.yaml \ --location=${WORKFLOW_REGION}
工作流应该会在几秒钟后部署完毕,然后您可以在 Cloud 控制台的“Workflows”部分看到它:
如果需要,您可以点击工作流并进行修改。在修改过程中,您会获得非常直观的工作流程:
您也可以在 Cloud 控制台中使用正确的参数手动执行工作流。相反,我们会在下一步中自动执行该操作,以响应 Cloud Storage 事件。
13. Workflows 触发器 (Cloud Functions)
工作流已部署并准备就绪。现在,当 Cloud Storage 存储分区中创建或删除文件时,我们需要触发 Workflows。分别为 storage.object.finalize
和 storage.object.delete
事件。
Workflows 提供了用于创建、管理和执行您可以使用的 Workflows 的 API 和客户端库。在这种情况下,您将使用 Workflows Execution API,具体来说就是使用其 Node.js 客户端库来触发工作流。
您将通过 Cloud Functions 函数触发 Workflows,以监听 Cloud Storage 事件。由于一个 Cloud Functions 函数只能监听一种事件类型,因此您需要部署两个 Cloud Functions 函数来同时监听 create 事件和 delete 事件:
探索代码
Cloud Functions 函数称为 trigger-workflow
。您可以在 index.js 中查看其完整代码。
部署到 Cloud Functions
导航到该文件夹:
cd workflows/functions/trigger-workflow/nodejs
设置一些所需的变量和配置:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT} export REGION=europe-west1 export WORKFLOW_NAME=picadaily-workflows export WORKFLOW_REGION=europe-west4 export COLLAGE_URL=$(gcloud run services describe collage-service --format 'value(status.url)') export THUMBNAILS_URL=$(gcloud run services describe thumbnails-service --format 'value(status.url)') export VISION_DATA_TRANSFORM_URL=$(gcloud functions describe vision-data-transform --format 'value(httpsTrigger.url)') gcloud config set functions/region ${REGION}
部署用于响应 finalize 事件的函数:
export SERVICE_NAME=trigger-workflow-on-finalize gcloud functions deploy ${SERVICE_NAME} \ --source=. \ --runtime nodejs10 \ --entry-point=trigger_workflow \ --trigger-resource=${BUCKET_PICTURES} \ --trigger-event=google.storage.object.finalize \ --allow-unauthenticated \ --set-env-vars GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT},WORKFLOW_REGION=${WORKFLOW_REGION},WORKFLOW_NAME=${WORKFLOW_NAME},THUMBNAILS_URL=${THUMBNAILS_URL},COLLAGE_URL=${COLLAGE_URL},VISION_DATA_TRANSFORM_URL=${VISION_DATA_TRANSFORM_URL}
部署用于响应删除事件的第二个函数:
export SERVICE_NAME=trigger-workflow-on-delete gcloud functions deploy ${SERVICE_NAME} \ --source=. \ --runtime nodejs10 \ --entry-point=trigger_workflow \ --trigger-resource=${BUCKET_PICTURES} \ --trigger-event=google.storage.object.delete \ --allow-unauthenticated \ --set-env-vars GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT},WORKFLOW_REGION=${WORKFLOW_REGION},WORKFLOW_NAME=${WORKFLOW_NAME},THUMBNAILS_URL=${THUMBNAILS_URL},COLLAGE_URL=${COLLAGE_URL},VISION_DATA_TRANSFORM_URL=${VISION_DATA_TRANSFORM_URL}
部署完成后,您可以在 Cloud 控制台中看到这两项功能:
14. 前端 (App Engine)
在此步骤中,您将通过 Pic-a-daily: Lab 4 - 创建网络前端 (Pic-a-daily: Lab 4-Create a web frontend) 在 Google App Engine 上创建一个网络前端,以便用户从网络应用上传图片,以及浏览上传的图片及其缩略图。
如需详细了解 App Engine 并阅读代码说明,请参阅每日图片:实验 4 - 创建网络前端。
探索代码
App Engine 应用名为 frontend
。您可以在 index.js 中查看其完整代码。
部署到 App Engine
导航到该文件夹:
cd frontend
设置您选择的区域,并将 app.yaml 中的 GOOGLE_CLOUD_PROJECT
替换为您的实际项目 ID:
export REGION=europe-west1 gcloud config set compute/region ${REGION} sed -i -e "s/GOOGLE_CLOUD_PROJECT/${GOOGLE_CLOUD_PROJECT}/" app.yaml
部署:
gcloud app deploy app.yaml -q
一两分钟后,系统会告知您应用正在处理流量:
Beginning deployment of service [default]... ╔════════════════════════════════════════════════════════════╗ ╠═ Uploading 8 files to Google Cloud Storage ═╣ ╚════════════════════════════════════════════════════════════╝ File upload done. Updating service [default]...done. Setting traffic split for service [default]...done. Deployed service [default] to [https://GOOGLE_CLOUD_PROJECT.appspot.com] You can stream logs from the command line by running: $ gcloud app logs tail -s default To view your application in the web browser run: $ gcloud app browse
您还可以访问 Cloud 控制台的 App Engine 部分,查看应用是否已部署,并探索 App Engine 的功能,例如版本控制和流量分配:
15. 测试工作流
如需进行测试,请转到应用的默认 App Engine 网址 (https://<YOUR_PROJECT_ID>.appspot.com/
),您应该会看到前端界面已启动并运行!
上传图片。这应该会触发 Workflows,并且您可以在 Cloud 控制台中看到处于 Active
状态的工作流执行情况:
完成工作流后,您可以点击执行 ID 并查看不同服务的输出:
再上传 3 张照片。您还应该看到 Cloud Storage 存储分区和 App Engine 前端中图片的缩略图和拼贴已更新:
16. 清理(可选)
如果您不打算保留该应用,可以通过删除整个项目来清理资源,从而节省成本并成为一个整体优秀的云公民:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
17. 恭喜!
您使用 Workflows 创建了应用的编排版本,以编排和调用服务。
所学内容
- App Engine
- Cloud Firestore
- Cloud Functions
- Cloud Run
- Workflows