1. Обзор
In the previous labs, you built an event-driven version of the Pic-a-daily app that used a Google Cloud Storage triggered Cloud Function for the Image Analysis service, a GCS triggered Cloud Run container via Pub/Sub for the Thumbnail service and Eventarc to trigger the Image Garbage Collector service on Cloud Run. There was also a Cloud Scheduler triggered Collage service:

In this lab, you will create an orchestrated version of the app. Instead of different types of events flowing through the system, you will use Workflows to orchestrate and call services as follows:

Что вы узнаете
- App Engine
- Облачный Firestore
- Облачные функции
- Cloud Run
- Рабочие процессы
2. Настройка и требования
Настройка среды для самостоятельного обучения
- Войдите в Cloud Console и создайте новый проект или используйте существующий. (Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .)



Запомните идентификатор проекта (Project ID) — уникальное имя для всех проектов Google Cloud (указанное выше имя уже занято и вам не подойдёт, извините!). В дальнейшем в этом практическом занятии оно будет обозначаться как PROJECT_ID .
- Далее вам потребуется включить оплату в Cloud Console, чтобы использовать ресурсы Google Cloud.
Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Обязательно следуйте инструкциям в разделе «Очистка», где указано, как отключить ресурсы, чтобы избежать дополнительных расходов после завершения этого урока. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .
Запустить Cloud Shell
Хотя Google Cloud можно управлять удаленно с ноутбука, в этом практическом занятии вы будете использовать Google Cloud Shell — среду командной строки, работающую в облаке.
В консоли GCP щелкните значок Cloud Shell на панели инструментов в правом верхнем углу:

Подготовка и подключение к среде займут всего несколько минут. После завершения вы должны увидеть что-то подобное:

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог размером 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Всю работу в этой лаборатории можно выполнять с помощью обычного браузера.
3. Введение в рабочие процессы

You can use Workflows to create serverless workflows that link a series of serverless tasks together in an order you define. You can combine the power of Google Cloud's APIs, serverless products like Cloud Functions and Cloud Run, and calls to external APIs to create flexible serverless applications.
As you might expect from an orchestrator, Workflows allows you to define the flow of your business logic in a YAML/JSON based workflow definition language and provides a Workflows Execution API and Workflows UI to trigger those flows.
Это не просто оркестратор, а нечто большее благодаря встроенным и настраиваемым функциям:
- Гибкая обработка повторных попыток и ошибок между этапами для надежного выполнения шагов.
- Парсинг JSON и передача переменных между этапами позволяют избежать использования связующего кода.
- Формулы выражений для принятия решений позволяют выполнять шаги в зависимости от условий.
- Вспомогательные рабочие процессы для модульного и многократно используемого использования.
- Поддержка внешних сервисов позволяет осуществлять оркестрацию сервисов за пределами Google Cloud.
- Поддержка аутентификации в Google Cloud и внешних сервисах для безопасного выполнения шагов.
- Коннекторы к сервисам Google Cloud, таким как Pub/Sub, Firestore, Tasks, Secret Manager, для упрощения интеграции.
Кроме того, Workflows — это полностью управляемый бессерверный продукт. Не нужно настраивать или масштабировать серверы, и вы платите только за то, что используете.
4. Включите API.
В этой лабораторной работе вы будете подключать сервисы 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. Получите код
Если вы еще не получили код в предыдущих практических заданиях, скачайте его:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Для выполнения этой лабораторной работы у вас будет следующая структура папок:
frontend | workflows | ├── functions ├── |── trigger-workflow ├── |── vision-data-transform ├── services ├── |── collage ├── |── thumbnails ├── workflows.yaml
Вот соответствующие папки:
- В состав
frontendвходит интерфейс App Engine, который мы будем повторно использовать из лабораторной работы №4. - В
functionsсодержатся облачные функции, созданные для рабочего процесса. - В состав
servicesвходят службы Cloud Run, модифицированные для рабочего процесса. -
workflows.yaml— это файл определения рабочего процесса.
6. Изучите YAML-файлы рабочих процессов.
Файл workflows.yaml определяет рабочий процесс в виде последовательности шагов. Давайте рассмотрим его подробнее, чтобы лучше понять.
At the beginning of the workflow, there are some parameters that are passed in. They will be passed in by two Cloud Functions triggering the Workflows. We'll get to these functions later but this is how the Workflows starts:

In YAML, you can see that these parameters are assigned to variables in the init step such as the file and bucket names triggering the event, and URLs of some Cloud Functions and Cloud Run services that Workflows will call:
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}
Next, Workflows check the event type. There are 2 event types supported: object.finalize (emitted when a file is saved in the cloud storage bucket) and object.delete (emitted when a file is deleted). Anything else will raise an event not supported exception.

Вот шаг в определении рабочего процесса 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
Notice how Workflows supports switch statements and exception handling, with the switch instruction and its various conditions, and the raise instruction to raise an error when the event is not recognized.
Next, let's take a look at the imageAnalysisCall . This is a series of calls from Workflows to call the Vision API to analyze the image, transform the Vision API response data to sort the labels of things recognized in the picture, pick the dominant colors, check whether the image is safe to display, and then save the metadata to Cloud Firestore.
Обратите внимание, что все операции выполняются в рамках рабочих процессов, за исключением облачных функций Vision Transform (которые мы развернем позже):

Вот как выглядят эти шаги в формате 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
Once the image is analyzed, the next two steps are to create the thumbnail of the image and a collage of the most recent images. This is done by deploying 2 Cloud Run services and making calls to them from thumbnailCall and collageCall steps:

Этапы работы с 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}
The other branch of the execution is when a file is deleted from the main storage bucket, containing the high-resolution versions of the pictures. In this branch, we want to delete the thumbnail of the image, in the bucket containing thumbnails and delete its metadata from Firestore. Both of these are done with HTTP calls from Workflows:

Этапы работы с 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
Ветка удаления завершается возвратом результатов/кодов каждого шага:
- deleteCompleted:
return:
gcsDeletion: ${gcsDeletionResult}
firestoreDeletion: ${firestoreDeletionResult.code}
На следующих этапах мы создадим все внешние зависимости для рабочих процессов: корзины, облачные функции, сервисы Cloud Run и базу данных Firestore.
7. Создайте корзины.
Для хранения изображений вам потребуется два хранилища: одно для сохранения оригинальных изображений высокого разрешения, а другое — для сохранения миниатюр изображений.
Создайте общедоступный региональный (в данном случае, в Европе) сегмент с единым доступом для загрузки изображений пользователями, используя инструмент 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 Console:

8. Преобразование визуальных данных (облачная функция)
Файл Workflows.yaml начинается с шагов init , eventTypeSwitch и eventTypeNotSupported . Они гарантируют, что события, поступающие из сегментов, будут направлены на соответствующие шаги.
В обработчике события object.finalize шаг imageAnalysisCall выполняет вызов к API Vision для извлечения метаданных созданного изображения. Все эти шаги выполняются в рамках рабочих процессов:

Далее нам необходимо преобразовать данные, возвращаемые API Vision, прежде чем мы сможем сохранить их в Firestore. В частности, нам нужно:
- Перечислите метки, полученные для изображения.
- Определите доминирующий цвет изображения.
- Определите, безопасен ли этот снимок.
Это делается в коде в облачной функции, и рабочие процессы просто вызывают эту функцию:

Изучите код
The Cloud Function is called vision-data-transform . You can check its full code in index.js . As you can see, the sole purpose of this function is to do a JSON to JSON transformation, so as to store the picture metadata conveniently in Firestore.
Развертывание в облачных функциях
Перейдите в папку:
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
После развертывания функции шаг transformImageAnalysisData в рабочих процессах сможет вызывать эту функцию для преобразования данных с помощью Vision API.
9. Подготовьте базу данных.
Next in the Workflows is to check the safety of the image from the image data and then store the information about the picture returned by the Vision API into the Cloud Firestore database, a fast, fully managed, serverless, cloud-native NoSQL document database:

Оба этих действия выполняются в рамках рабочих процессов, но для корректной работы хранения метаданных необходимо создать базу данных Firestore.
Сначала создайте приложение App Engine в регионе, где вы хотите использовать базу данных Firestore (это обязательное условие для Firestore):
export REGION_FIRESTORE=europe-west2
gcloud app create --region=${REGION_FIRESTORE}
Далее создайте базу данных Firestore в том же регионе:
gcloud firestore databases create --region=${REGION_FIRESTORE}
Документы будут созданы программно в нашей коллекции и будут содержать 4 поля:
- имя (строка): имя файла загруженного изображения, которое также является ключом документа.
- метки (массив строк): метки распознанных элементов в Vision API.
- цвет (строка): шестнадцатеричный код доминирующего цвета (например, #ab12ef)
- дата создания : метка времени сохранения метаданных этого изображения.
- миниатюра (логическое значение): необязательное поле, которое будет присутствовать и иметь значение true, если для этого изображения было сгенерировано миниатюрное изображение.
As we will be searching in Firestore to find pictures that have thumbnails available, and sorting along the creation date, we'll need to create a search index. You can create the index with the following command:
gcloud firestore indexes composite create --collection-group=pictures \ --field-config field-path=thumbnail,order=descending \ --field-config field-path=created,order=descending
Обратите внимание, что создание индекса может занять до 10 минут.
После создания индекса вы сможете увидеть его в Cloud Console:

Теперь шаг storeMetadata в рабочих процессах сможет сохранять метаданные изображений в Firestore.
10. Сервис создания миниатюр (Cloud Run)
Следующий шаг — создание миниатюры изображения. Это делается программно в службе Cloud Run, и Workflows вызывает эту службу на шаге thumbnailCall :

Изучите код
Сервис Cloud Run называется thumbnails . Полный код можно посмотреть в файле index.js .
Создайте и опубликуйте образ контейнера.
Cloud Run runs containers but you first need to build the container image (defined in Dockerfile ). Google Cloud Build can be used to build container images and then host to 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.
Развертывание в облаке. Запуск.
Установите необходимые переменные и выполните следующие действия с настройками:
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}
После развертывания сервиса шаг thumbnailCall в рабочих процессах сможет вызывать этот сервис.
11. Сервис создания коллажей (Cloud Run)
Следующий шаг — создание коллажа из последних изображений. Это делается программно в службе Cloud Run, и Workflows вызывает эту службу на шаге collageCall :

Изучите код
Сервис Cloud Run называется collage . Полный код можно посмотреть в файле index.js .
Создайте и опубликуйте образ контейнера.
Cloud Run runs containers but you first need to build the container image (defined in Dockerfile ). Google Cloud Build can be used to build container images and then host to 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.
Развертывание в облаке. Запуск.
Установите необходимые переменные и выполните следующие действия с настройками:
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 Run в Cloud Console, и шаг collageCall в Workflows сможет вызывать этот сервис:

12. Развертывание рабочих процессов
We deployed all the external dependencies of Workflows. All of the remaining steps ( finalizeCompleted , pictureGarbageCollectionGCS , pictureGarbageCollectionFirestore , deleteCompleted ) can be completed by Workflows itself.
Пришло время развернуть рабочие процессы!
Перейдите в папку, содержащую файл workflows.yaml , и разверните его с помощью следующей команды:
export WORKFLOW_REGION=europe-west4
export WORKFLOW_NAME=picadaily-workflows
gcloud workflows deploy ${WORKFLOW_NAME} \
--source=workflows.yaml \
--location=${WORKFLOW_REGION}
Через несколько секунд рабочий процесс должен развернуться, и вы сможете увидеть его в разделе «Рабочие процессы» в Cloud Console:

При желании вы можете щелкнуть по рабочему процессу и отредактировать его. Во время редактирования вы получите наглядное визуальное представление рабочего процесса:

Вы также можете запустить рабочий процесс из Cloud Console вручную, указав правильные параметры. Однако в этом случае мы запустим его автоматически в ответ на события Cloud Storage на следующем шаге.
13. Триггеры рабочих процессов (Cloud Functions)
The workflow is deployed and ready. Now, we need to trigger the Workflows when a file is created or deleted in a Cloud Storage bucket. These are storage.object.finalize and storage.object.delete events respectively.
Workflows have APIs and client libraries for creating, managing and executing Workflows that you can use. In this case, you will use Workflows Execution API and more specifically its Node.js client library to trigger the Workflow.
You will trigger the Workflows from Cloud Function listening to Cloud Storage events. Since a Cloud Function can only listen for one event type, you will deploy two Cloud Functions to listen for both create and delete events:

Изучите код
Функция Cloud Function называется trigger-workflow . Полный код можно посмотреть в файле index.js .
Развертывание в облачных функциях
Перейдите в папку:
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}
Разверните функцию, которая будет реагировать на события завершения:
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 Console:

14. Фронтенд (App Engine)
In this step, you create a web frontend on Google App Engine from Pic-a-daily: Lab 4—Create a web frontend that will let users upload pictures from the web application, as well as browse the uploaded pictures and their thumbnails.

Подробнее об App Engine и описание кода можно прочитать в Pic-a-daily: Lab 4 — Создание веб-интерфейса .
Изучите код
Приложение App Engine называется frontend . Полный код можно посмотреть в файле index.js .
Развернуть в App Engine
Перейдите в папку:
cd frontend
Укажите регион по своему выбору, а также замените GOOGLE_CLOUD_PROJECT в файле app.yaml на фактический идентификатор вашего проекта:
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
Вы также можете перейти в раздел App Engine в Cloud Console, чтобы убедиться, что приложение развернуто, и изучить такие функции App Engine, как версионирование и разделение трафика:

15. Протестируйте рабочие процессы.
Для проверки перейдите по стандартному URL-адресу приложения в App Engine ( https://<YOUR_PROJECT_ID>.appspot.com/ ), и вы должны увидеть работающий пользовательский интерфейс!

Загрузите изображение. Это должно запустить рабочие процессы, и вы сможете увидеть выполнение рабочего процесса в Active состоянии в консоли Cloud Console:

После завершения создания рабочих процессов вы можете щелкнуть по идентификатору выполнения и увидеть результаты работы различных служб:

Загрузите еще 3 изображения. Вы также должны увидеть обновленные миниатюры и коллаж изображений в хранилищах Cloud Storage и на интерфейсе App Engine:

16. Уборка (необязательно)
Если вы не планируете сохранять приложение, вы можете освободить ресурсы, чтобы сэкономить средства и в целом ответственно относиться к облачным сервисам, удалив весь проект:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
17. Поздравляем!
Вы создали оркестрованную версию приложения, используя рабочие процессы для организации и вызова сервисов.
Что мы рассмотрели
- App Engine
- Облачный Firestore
- Облачные функции
- Cloud Run
- Рабочие процессы