Pic-a-daily: Лабораторная работа 2. Создание миниатюр изображений.

1. Обзор

В этой лабораторной работе по коду вы опираетесь на предыдущую лабораторную работу и добавляете службу миниатюр. Сервис миниатюр — это веб-контейнер, который делает большие изображения и создает из них миниатюры.

Когда изображение загружается в Cloud Storage, уведомление отправляется через Cloud Pub/Sub в веб-контейнер Cloud Run, который затем изменяет размеры изображений и сохраняет их обратно в другую корзину в Cloud Storage.

31fa4f8a294d90df.png

Что вы узнаете

  • Облачный бег
  • Облачное хранилище
  • Облачный паб/саб

2. Настройка и требования

Самостоятельная настройка среды

  1. Войдите в Google Cloud Console и создайте новый проект или повторно используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

  • Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google, и вы можете обновить ее в любое время.
  • Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (нельзя изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ), поэтому, если он вам не нравится, создайте другой случайный идентификатор или попробуйте свой собственный и посмотрите, доступен ли он. Затем он «замораживается» после создания проекта.
  • Существует третье значение — номер проекта , который используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
  1. Затем вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Чтобы отключить ресурсы и не платить за выставление счетов за пределами этого руководства, следуйте инструкциям по «очистке», которые можно найти в конце лаборатории кода. Новые пользователи Google Cloud имеют право на участие в программе бесплатной пробной версии стоимостью 300 долларов США .

Запустить Cloud Shell

Хотя Google Cloud можно управлять удаленно с вашего ноутбука, в этой лаборатории вы будете использовать Google Cloud Shell , среду командной строки, работающую в облаке.

В консоли GCP щелкните значок Cloud Shell на верхней правой панели инструментов:

bce75f34b2c53987.png

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

f6ef2b5f13479f3a.png

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

3. Включите API

В ходе этой лабораторной работы вам понадобится Cloud Build для создания образов контейнеров и Cloud Run для развертывания контейнера.

Включите оба API из Cloud Shell:

gcloud services enable cloudbuild.googleapis.com \
  run.googleapis.com

Вы должны увидеть успешное завершение операции:

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

4. Создайте еще одно ведро

Миниатюры загруженных изображений будут храниться в другой корзине. Давайте воспользуемся gsutil для создания второго сегмента.

В Cloud Shell задайте переменную для уникального имени сегмента. В Cloud Shell уже указан уникальный идентификатор вашего проекта GOOGLE_CLOUD_PROJECT . Вы можете добавить это к имени корзины. Затем создайте общедоступную мультирегиональную корзину в Европе с единым уровнем доступа:

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

В итоге у вас должен получиться новый публичный бакет:

8e75c8099938e972.png

5. Клонируйте код

Клонируйте код и перейдите в каталог, содержащий сервис:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
cd serverless-photosharing-workshop/services/thumbnails/nodejs

У вас будет следующий макет файла для сервиса:

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

Внутри папки thumbnails/nodejs есть 3 файла:

  • index.js содержит код Node.js.
  • package.json определяет зависимости библиотеки
  • Dockerfile определяет образ контейнера

6. Изучите код

Чтобы изучить код, вы можете использовать встроенный текстовый редактор, нажав кнопку Open Editor в верхней части окна Cloud Shell:

3d145fe299dd8b3e.png

Вы также можете открыть редактор в специальном окне браузера, чтобы получить больше места на экране.

Зависимости

Файл package.json определяет необходимые зависимости библиотеки:

{
  "name": "thumbnail_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. Firestore для обновления метаданных изображения. Express — это веб-фреймворк JavaScript/Node. Модуль body-parser используется для простого анализа входящих запросов. Bluebird используется для обработки обещаний, а Imagemagick — библиотека для управления изображениями.

Докерфайл

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/*; \
  mkdir /tmp/original; \
  mkdir /tmp/thumbnail;

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

Базовым изображением является Node 14, а для манипулирования изображениями используется библиотека imagemagick. Некоторые временные каталоги создаются для хранения файлов оригиналов и миниатюр изображений. Затем перед запуском кода с помощью npm start устанавливаются модули NPM, необходимые для нашего кода.

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');

const app = express();
app.use(express.json());

Сначала мы требуем необходимые зависимости и создаем наше веб-приложение Express, а также указываем, что хотим использовать синтаксический анализатор тела JSON, поскольку входящие запросы на самом деле представляют собой просто полезные данные JSON, отправляемые через POST-запрос в наше приложение.

app.post('/', async (req, res) => {
    try {
        // ...
    } catch (err) {
        console.log(`Error: creating the thumbnail: ${err}`);
        console.error(err);
        res.status(500).send(err);
    }
});

Мы получаем эти входящие полезные данные по базовому URL-адресу / и оборачиваем наш код некоторой обработкой логики ошибок, чтобы получить более подробную информацию о том, почему что-то может не работать в нашем коде, просматривая журналы, которые будут видны из журнала Stackdriver. интерфейс в веб-консоли Google Cloud.

const pubSubMessage = req.body;
console.log(`PubSub message: ${JSON.stringify(pubSubMessage)}`);

const fileEvent = JSON.parse(Buffer.from(pubSubMessage.message.data, 'base64').toString().trim());
console.log(`Received thumbnail request for file ${fileEvent.name} from bucket ${fileEvent.bucket}`);

На платформе Cloud Run сообщения Pub/Sub отправляются через запросы HTTP POST в виде полезных данных JSON вида:

{
  "message": {
    "attributes": {
      "bucketId": "uploaded-pictures",
      "eventTime": "2020-02-27T09:22:43.255225Z",
      "eventType": "OBJECT_FINALIZE",
      "notificationConfig": "projects/_/buckets/uploaded-pictures/notificationConfigs/28",
      "objectGeneration": "1582795363255481",
      "objectId": "IMG_20200213_181159.jpg",
      "payloadFormat": "JSON_API_V1"
    },
    "data": "ewogICJraW5kIjogInN0b3JhZ2Ujb2JqZWN...FQUU9Igp9Cg==",
    "messageId": "1014308302773399",
    "message_id": "1014308302773399",
    "publishTime": "2020-02-27T09:22:43.973Z",
    "publish_time": "2020-02-27T09:22:43.973Z"
  },
  "subscription": "projects/serverless-picadaily/subscriptions/gcs-events-subscription"
}

Но что действительно интересно в этом документе JSON, так это то, что на самом деле содержится в атрибуте message.data , который представляет собой просто строку, но кодирует фактическую полезную нагрузку в Base 64. Вот почему наш код выше декодирует содержимое этого атрибута в Base 64. . Этот атрибут data после декодирования содержит еще один документ JSON, который представляет сведения о событии Cloud Storage, который, среди других метаданных, указывает имя файла и имя сегмента.

{
  "kind": "storage#object",
  "id": "uploaded-pictures/IMG_20200213_181159.jpg/1582795363255481",
  "selfLink": "https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg",
  "name": "IMG_20200213_181159.jpg",
  "bucket": "uploaded-pictures",
  "generation": "1582795363255481",
  "metageneration": "1",
  "contentType": "image/jpeg",
  "timeCreated": "2020-02-27T09:22:43.255Z",
  "updated": "2020-02-27T09:22:43.255Z",
  "storageClass": "STANDARD",
  "timeStorageClassUpdated": "2020-02-27T09:22:43.255Z",
  "size": "4944335",
  "md5Hash": "QzBIoPJBV2EvqB1EVk1riw==",
  "mediaLink": "https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg?generation=1582795363255481&alt=media",
  "crc32c": "hQ3uHg==",
  "etag": "CLmJhJu08ecCEAE="
}

Нас интересуют имена изображений и сегментов, поскольку наш код будет извлекать это изображение из сегмента для обработки его миниатюр:

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

const originalFile = path.resolve('/tmp/original', fileEvent.name);
const thumbFile = path.resolve('/tmp/thumbnail', fileEvent.name);

await bucket.file(fileEvent.name).download({
    destination: originalFile
});
console.log(`Downloaded picture into ${originalFile}`);

Мы получаем имя выходного сегмента хранилища из переменной среды.

У нас есть исходная корзина, создание файла которой активировала нашу службу Cloud Run, и целевая корзина, в которой мы будем хранить полученное изображение. Мы используем встроенный API path для локальной обработки файлов, поскольку библиотека imagemagick будет создавать миниатюру локально во временном каталоге /tmp . Мы await асинхронного вызова для загрузки загруженного файла изображения.

const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
        srcPath: originalFile,
        dstPath: thumbFile,
        width: 400,
        height: 400         
});
console.log(`Created local thumbnail in ${thumbFile}`);

Модуль imagemagick не очень дружелюбен к async / await , поэтому мы заключаем его в обещание Javascript (предоставляемое модулем Bluebird). Затем мы вызываем созданную нами асинхронную функцию изменения размера/обрезки с параметрами исходного и целевого файлов, а также размерами миниатюры, которую мы хотим создать.

await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);

Как только файл миниатюры будет загружен в Cloud Storage, мы также обновим метаданные в Cloud Firestore, добавив логический флаг, указывающий, что миниатюра для этого изображения действительно создана:

const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(fileEvent.name);
await doc.set({
    thumbnail: true
}, {merge: true});
console.log(`Updated Firestore about thumbnail creation for ${fileEvent.name}`);

res.status(204).send(`${fileEvent.name} processed`);

Как только наш запрос будет завершен, мы ответим на запрос HTTP POST о том, что файл был обработан должным образом.

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

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

В конце исходного файла у нас есть инструкции, позволяющие Express запустить наше веб-приложение через порт по умолчанию 8080.

7. Тестируйте локально

Перед развертыванием в облаке протестируйте код локально, чтобы убедиться, что он работает.

В папке thumbnails/nodejs установите зависимости npm и запустите сервер:

npm install; npm start

Если все прошло успешно, сервер должен запуститься на порту 8080:

Started thumbnail generator on port 8080

Используйте CTRL-C для выхода.

8. Создайте и опубликуйте образ контейнера.

Cloud Run запускает контейнеры, но сначала вам необходимо создать образ контейнера (определенный в Dockerfile ). Google Cloud Build можно использовать для создания образов контейнеров, а затем размещения в реестре контейнеров Google.

В папке thumbnails/nodejs , где находится Dockerfile , введите следующую команду, чтобы создать образ контейнера:

gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service

Через минуту или две сборка должна завершиться успешно:

b354b3a9a3631097.png

В разделе «История» Cloud Build также должна быть показана успешная сборка:

df00f198dd2bf6bf.png

Нажав на идентификатор сборки, чтобы просмотреть подробную информацию, на вкладке «Артефакты сборки» вы должны увидеть, что образ контейнера был загружен в облачный реестр (GCR):

a4577ce0744f73e2.png

При желании вы можете дважды проверить, что образ контейнера выполняется локально в Cloud Shell:

docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service

Сервер должен запуститься на порту 8080 в контейнере:

Started thumbnail generator on port 8080

Используйте CTRL-C для выхода.

9. Развертывание в 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:

SERVICE_NAME=thumbnail-service
gcloud run deploy $SERVICE_NAME \
    --image gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

Обратите внимание на флаг --no-allow-unauthenticated . Это делает службу Cloud Run внутренней службой, которая будет запускаться только определенными учетными записями служб.

Если развертывание прошло успешно, вы должны увидеть следующий вывод:

c0f28e7d6de0024.png

Если вы перейдете в пользовательский интерфейс облачной консоли, вы также увидите, что служба была успешно развернута:

9bfe48e3c8b597e5.png

10. События Cloud Storage в Cloud Run через Pub/Sub

Сервис готов, но вам все равно нужно передавать события Cloud Storage в только что созданный сервис Cloud Run. Cloud Storage может отправлять события создания файлов через Cloud Pub/Sub, но чтобы это заработало, нужно выполнить несколько шагов.

Создайте тему Pub/Sub в качестве канала связи:

TOPIC_NAME=cloudstorage-cloudrun-topic
gcloud pubsub topics create $TOPIC_NAME

Создавайте уведомления Pub/Sub, когда файлы хранятся в корзине:

BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT
gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES

Создайте сервисную учетную запись для подписки Pub/Sub, которую мы создадим позже:

SERVICE_ACCOUNT=$TOPIC_NAME-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
     --display-name "Cloud Run Pub/Sub Invoker"

Предоставьте учетной записи службы разрешение на вызов службы Cloud Run:

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

Если вы включили учетную запись службы Pub/Sub 8 апреля 2021 г. или ранее, предоставьте роль iam.serviceAccountTokenCreator учетной записи службы Pub/Sub:

PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
     --member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
     --role=roles/iam.serviceAccountTokenCreator

Для распространения изменений IAM может потребоваться несколько минут.

Наконец, создайте подписку Pub/Sub с учетной записью службы:

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud pubsub subscriptions create $TOPIC_NAME-subscription --topic $TOPIC_NAME \
   --push-endpoint=$SERVICE_URL \
   --push-auth-service-account=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com

Вы можете проверить, что подписка создана. Перейдите в Pub/Sub в консоли, выберите тему gcs-events и внизу вы должны увидеть подписку:

e8ab86dccb8d890.png

11. Протестируйте сервис

Чтобы проверить, работает ли настройка, загрузите новое изображение в корзину uploaded-pictures и проверьте в корзине thumbnails , что новые изображения с измененным размером отображаются так, как ожидалось.

Вы также можете дважды проверить журналы, чтобы увидеть сообщения журналов, которые появляются во время выполнения различных этапов службы Cloud Run:

42c025e2d7d6ca3a.png

12. Очистка (необязательно)

Если вы не собираетесь продолжать другие лабораторные работы из этой серии, вы можете очистить ресурсы, чтобы сэкономить средства и стать в целом хорошим гражданином облака. Вы можете очистить ресурсы по отдельности следующим образом.

Удалить корзину:

gsutil rb gs://$BUCKET_THUMBNAILS

Удалить услугу:

gcloud run services delete $SERVICE_NAME -q

Удалите тему Pub/Sub:

gcloud pubsub topics delete $TOPIC_NAME

Альтернативно, вы можете удалить весь проект:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

13. Поздравляем!

Теперь все на месте:

  • В Cloud Storage создано уведомление, которое отправляет сообщения Pub/Sub по теме при загрузке нового изображения.
  • Определили необходимые привязки и учетные записи IAM (в отличие от Cloud Functions, где все это автоматизировано, здесь все настраивается вручную).
  • Создана подписка, чтобы наша служба Cloud Run получала сообщения Pub/Sub.
  • Каждый раз, когда в корзину загружается новое изображение, его размер изменяется благодаря новому сервису Cloud Run.

Что мы рассмотрели

  • Облачный бег
  • Облачное хранилище
  • Облачный паб/саб

Следующие шаги