Pic-a-daily: Lab 2 – Miniaturansichten von Bildern erstellen

1. Übersicht

In diesem Code-Lab bauen Sie auf dem vorherigen Lab auf und fügen einen Miniaturansichtsdienst hinzu. Der Miniaturansichtendienst ist ein Webcontainer, der große Bilder entgegennimmt und daraus Miniaturansichten erstellt.

Wenn das Bild in Cloud Storage hochgeladen wird, wird über Cloud Pub/Sub eine Benachrichtigung an einen Cloud Run-Webcontainer gesendet, der die Größe der Bilder ändert und sie in einem anderen Bucket in Cloud Storage speichert.

31fa4f8a294d90df.png

Lerninhalte

  • Cloud Run
  • Cloud Storage
  • Cloud Pub/Sub

2. Einrichtung und Anforderungen

Umgebung zum selbstbestimmten Lernen einrichten

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

  • Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es handelt sich um einen String, der nicht von Google APIs verwendet wird und den Sie jederzeit aktualisieren können.
  • Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich (kann nach der Festlegung nicht mehr geändert werden). In der Cloud Console wird automatisch ein eindeutiger String generiert. Normalerweise ist es nicht wichtig, wie dieser aussieht. In den meisten Codelabs müssen Sie auf die Projekt-ID verweisen (die in der Regel als PROJECT_ID angegeben wird). Wenn Ihnen die ID nicht gefällt, können Sie eine andere zufällige ID generieren oder eine eigene ID ausprobieren und sehen, ob sie verfügbar ist. Nachdem das Projekt erstellt wurde, wird es „eingefroren“.
  • Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten
  1. Als Nächstes müssen Sie die Abrechnung in der Cloud Console aktivieren, um Cloud-Ressourcen/-APIs verwenden zu können. Die Durchführung dieses Codelabs sollte keine oder nur geringe Kosten verursachen. Wenn Sie Ressourcen herunterfahren möchten, damit nach Abschluss dieses Codelabs keine Gebühren anfallen, folgen Sie den Bereinigungsanweisungen am Ende des Codelabs. Neue Nutzer von Google Cloud kommen für das Programm für kostenlose Testversionen mit einem Guthaben von 300$ infrage.

Cloud Shell starten

Während Sie Google Cloud von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Google Cloud Shell verwendet, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.

Klicken Sie in der GCP Console oben rechts in der Symbolleiste auf das Cloud Shell-Symbol:

bce75f34b2c53987.png

Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Augenblicke dauern. Anschließend sehen Sie in etwa Folgendes:

f6ef2b5f13479f3a.png

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Für dieses Lab benötigen Sie lediglich einen Browser.

3. APIs aktivieren

In diesem Lab benötigen Sie Cloud Build zum Erstellen von Container-Images und Cloud Run zum Bereitstellen des Containers.

Aktivieren Sie beide APIs über Cloud Shell:

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

Der Vorgang sollte erfolgreich abgeschlossen werden:

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

4. Weiteren Bucket erstellen

Sie speichern Thumbnails der hochgeladenen Bilder in einem anderen Bucket. Verwenden wir gsutil, um den zweiten Bucket zu erstellen.

Legen Sie in Cloud Shell eine Variable für den eindeutigen Bucket-Namen fest. In Cloud Shell ist GOOGLE_CLOUD_PROJECT bereits auf Ihre eindeutige Projekt-ID festgelegt. Sie können das an den Bucket-Namen anhängen. Erstellen Sie dann einen öffentlichen multiregionalen Bucket in Europa mit einheitlichem Zugriff auf Bucket-Ebene:

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

Am Ende sollten Sie einen neuen öffentlichen Bucket haben:

8e75c8099938e972.png

5. Code klonen

Klonen Sie den Code und wechseln Sie in das Verzeichnis mit dem Dienst:

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

Der Dienst hat das folgende Dateilayout:

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

Im Ordner thumbnails/nodejs befinden sich drei Dateien:

  • index.js enthält den Node.js-Code.
  • package.json definiert die Bibliotheksabhängigkeiten.
  • Dockerfile definiert das Container-Image.

6. Code ansehen

Wenn Sie den Code genauer ansehen möchten, können Sie den integrierten Texteditor verwenden. Klicken Sie dazu oben im Cloud Shell-Fenster auf die Schaltfläche Open Editor:

3d145fe299dd8b3e.png

Sie können den Editor auch in einem separaten Browserfenster öffnen, um mehr Platz auf dem Bildschirm zu haben.

Abhängigkeiten

In der Datei package.json werden die erforderlichen Bibliotheksabhängigkeiten definiert:

{
  "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"
  }
}

Die Cloud Storage-Bibliothek wird verwendet, um Bilddateien in Cloud Storage zu lesen und zu speichern. Firestore zum Aktualisieren der Bildmetadaten. Express ist ein JavaScript-/Node-Web-Framework. Das body-parser-Modul wird verwendet, um eingehende Anfragen einfach zu parsen. Bluebird wird für die Verarbeitung von Promises verwendet und Imagemagick ist eine Bibliothek zum Bearbeiten von Bildern.

Dockerfile

Dockerfile definiert das Container-Image für die Anwendung:

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" ]

Das Basis-Image ist Node 14 und die Imagemagick-Bibliothek wird für die Bildbearbeitung verwendet. Einige temporäre Verzeichnisse werden zum Speichern von Original- und Miniaturbilddateien erstellt. Anschließend werden die von unserem Code benötigten NPM-Module installiert, bevor der Code mit npm start gestartet wird.

index.js

Sehen wir uns den Code in Teilen an, damit wir besser verstehen können, was dieses Programm macht.

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

Zuerst werden die erforderlichen Abhängigkeiten angefordert und unsere Express-Webanwendung erstellt. Außerdem wird angegeben, dass wir den JSON-Body-Parser verwenden möchten, da eingehende Anfragen tatsächlich nur JSON-Nutzlasten sind, die über eine POST-Anfrage an unsere Anwendung gesendet werden.

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

Wir empfangen diese eingehenden Nutzlasten über die Basis-URL „/“ und umschließen unseren Code mit einer Fehlerbehandlungslogik, um anhand der Logs, die über die Stackdriver Logging-Schnittstelle in der Google Cloud Webkonsole sichtbar sind, besser nachvollziehen zu können, warum etwas in unserem Code fehlschlägt.

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}`);

Auf der Cloud Run-Plattform werden Pub/Sub-Nachrichten über HTTP-POST-Anfragen als JSON-Nutzlasten im folgenden Format gesendet:

{
  "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"
}

Das wirklich Interessante an diesem JSON-Dokument ist jedoch das Attribut message.data, das nur ein String ist, in dem die eigentliche Nutzlast in Base 64 codiert ist. Aus diesem Grund wird im obigen Code der Base64-Inhalt dieses Attributs decodiert. Das Attribut data enthält nach der Decodierung ein weiteres JSON-Dokument mit den Cloud Storage-Ereignisdetails, das unter anderem den Dateinamen und den Bucket-Namen angibt.

{
  "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="
}

Wir benötigen die Namen des Bildes und des Buckets, da unser Code das Bild für die Thumbnail-Bearbeitung aus dem Bucket abrufen wird:

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}`);

Wir rufen den Namen des Ausgabespeicher-Buckets aus einer Umgebungsvariablen ab.

Wir haben den Quell-Bucket, dessen Dateierstellung unseren Cloud Run-Dienst ausgelöst hat, und den Ziel-Bucket, in dem wir das resultierende Bild speichern. Wir verwenden die integrierte path-API für die Verarbeitung lokaler Dateien, da die imagemagick-Bibliothek das Miniaturbild lokal im temporären Verzeichnis /tmp erstellt. Wir await für einen asynchronen Aufruf zum Herunterladen der hochgeladenen Bilddatei.

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

Das imagemagick-Modul ist nicht sehr async-/await-freundlich. Daher wird es in einem JavaScript-Promise (bereitgestellt vom Bluebird-Modul) umschlossen. Anschließend rufen wir die asynchrone Funktion zum Ändern der Größe / Zuschneiden auf, die wir erstellt haben, und übergeben die Parameter für die Quell- und Zieldateien sowie die Abmessungen des Thumbnails, das wir erstellen möchten.

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

Sobald die Miniaturansichtsdatei in Cloud Storage hochgeladen wurde, aktualisieren wir auch die Metadaten in Cloud Firestore, um ein boolesches Flag hinzuzufügen, das angibt, dass die Miniaturansicht für dieses Bild tatsächlich generiert wurde:

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

Nach Abschluss unserer Anfrage antworten wir auf die HTTP-POST-Anfrage, dass die Datei ordnungsgemäß verarbeitet wurde.

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

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

Am Ende der Quelldatei finden wir die Anweisungen, mit denen Express unsere Webanwendung auf dem Standardport 8080 startet.

7. Lokal testen

Testen Sie den Code lokal, um sicherzustellen, dass er funktioniert, bevor Sie ihn in der Cloud bereitstellen.

Installieren Sie im Ordner thumbnails/nodejs die npm-Abhängigkeiten und starten Sie den Server:

npm install; npm start

Wenn alles geklappt hat, sollte der Server auf Port 8080 gestartet werden:

Started thumbnail generator on port 8080

Verwenden Sie zum Beenden CTRL-C.

8. Container-Image erstellen und veröffentlichen

In Cloud Run werden Container ausgeführt, aber Sie müssen zuerst das Container-Image erstellen (definiert in Dockerfile). Mit Google Cloud Build können Sie Container-Images erstellen und dann in Google Container Registry hosten.

Führen Sie im Ordner thumbnails/nodejs, in dem sich Dockerfile befindet, den folgenden Befehl aus, um das Container-Image zu erstellen:

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

Nach ein oder zwei Minuten sollte der Build erfolgreich sein:

b354b3a9a3631097.png

Im Abschnitt „Verlauf“ von Cloud Build sollte der erfolgreiche Build ebenfalls angezeigt werden:

df00f198dd2bf6bf.png

Wenn Sie auf die Build-ID klicken, um die Detailansicht aufzurufen, sehen Sie auf dem Tab „Build-Artefakte“, dass das Container-Image in die Cloud Registry (GCR) hochgeladen wurde:

a4577ce0744f73e2.png

Wenn Sie möchten, können Sie noch einmal prüfen, ob das Container-Image lokal in Cloud Shell ausgeführt wird:

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

Dadurch sollte der Server auf Port 8080 im Container gestartet werden:

Started thumbnail generator on port 8080

Verwenden Sie zum Beenden CTRL-C.

9. In Cloud Run bereitstellen

Legen Sie vor der Bereitstellung in Cloud Run die Cloud Run-Region auf eine der unterstützten Regionen und die Plattform auf managed fest:

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

So prüfen Sie, ob die Konfiguration festgelegt ist:

gcloud config list

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

Führen Sie den folgenden Befehl aus, um das Container-Image in Cloud Run bereitzustellen:

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

Beachten Sie das Flag --no-allow-unauthenticated. Dadurch wird der Cloud Run-Dienst zu einem internen Dienst, der nur von bestimmten Dienstkonten ausgelöst wird.

Wenn die Bereitstellung erfolgreich ist, sollte die folgende Ausgabe angezeigt werden:

c0f28e7d6de0024.png

Wenn Sie die Cloud Console-UI aufrufen, sollten Sie auch sehen, dass der Dienst erfolgreich bereitgestellt wurde:

9bfe48e3c8b597e5.png

10. Cloud Storage-Ereignisse über Pub/Sub an Cloud Run weiterleiten

Der Dienst ist bereit, aber Sie müssen Cloud Storage-Ereignisse an den neu erstellten Cloud Run-Dienst senden. Cloud Storage kann Ereignisse zur Dateierstellung über Cloud Pub/Sub senden. Dazu sind jedoch einige Schritte erforderlich.

Erstellen Sie ein Pub/Sub-Thema als Kommunikationspipeline:

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

Pub/Sub-Benachrichtigungen erstellen, wenn Dateien im Bucket gespeichert werden:

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

Erstellen Sie ein Dienstkonto für das Pub/Sub-Abo, das wir später erstellen werden:

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

Erteilen Sie dem Dienstkonto die Berechtigung, einen Cloud Run-Dienst aufzurufen:

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

Wenn Sie das Pub/Sub-Dienstkonto am oder vor dem 8. April 2021 aktiviert haben, weisen Sie dem Pub/Sub-Dienstkonto die Rolle iam.serviceAccountTokenCreator zu:

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

Es kann einige Minuten dauern, bis die IAM-Änderungen übertragen werden.

Erstellen Sie schließlich ein Pub/Sub-Abo mit dem Dienstkonto:

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

Sie können prüfen, ob ein Abo erstellt wurde. Rufen Sie Pub/Sub in der Console auf, wählen Sie das Thema gcs-events aus und unten sollte das Abo angezeigt werden:

e8ab86dccb8d890.png

11. Dienst testen

Laden Sie zum Testen der Einrichtung ein neues Bild in den Bucket uploaded-pictures hoch und prüfen Sie, ob im Bucket thumbnails wie erwartet neue, in der Größe angepasste Bilder angezeigt werden.

Sie können auch die Logs prüfen, um zu sehen, ob die Logging-Nachrichten angezeigt werden, während die verschiedenen Schritte des Cloud Run-Dienstes durchlaufen werden:

42c025e2d7d6ca3a.png

12. Bereinigen (optional)

Wenn Sie nicht mit den anderen Labs der Reihe fortfahren möchten, können Sie Ressourcen bereinigen, um Kosten zu sparen und nicht mehr benötigte Ressourcen für andere freizugeben. Sie können Ressourcen einzeln so bereinigen:

Löschen Sie den Bucket:

gsutil rb gs://$BUCKET_THUMBNAILS

Löschen Sie den Dienst:

gcloud run services delete $SERVICE_NAME -q

Löschen Sie das Pub/Sub-Thema:

gcloud pubsub topics delete $TOPIC_NAME

Alternativ können Sie das gesamte Projekt löschen:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

13. Glückwunsch!

Jetzt ist alles bereit:

  • Sie haben in Cloud Storage eine Benachrichtigung erstellt, die Pub/Sub-Nachrichten zu einem Thema sendet, wenn ein neues Bild hochgeladen wird.
  • Die erforderlichen IAM-Bindungen und -Konten wurden definiert. Im Gegensatz zu Cloud Functions, wo alles automatisiert ist, wird hier alles manuell konfiguriert.
  • Wir haben ein Abo erstellt, damit unser Cloud Run-Dienst die Pub/Sub-Nachrichten empfängt.
  • Immer wenn ein neues Bild in den Bucket hochgeladen wird, wird es durch den neuen Cloud Run-Dienst in der Größe angepasst.

Behandelte Themen

  • Cloud Run
  • Cloud Storage
  • Cloud Pub/Sub

Nächste Schritte