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 Thumbnail-Dienst hinzu. Der Thumbnail-Dienst ist ein Webcontainer, der große Bilder aufnimmt und daraus Miniaturansichten erstellt.

Beim Hochladen des Bildes in Cloud Storage wird eine Benachrichtigung über Cloud Pub/Sub an einen Cloud Run-Webcontainer gesendet, der dann die Größe der Bilder ändert und sie wieder in einem anderen Bucket in Cloud Storage speichert.

31fa4f8a294d90df.png

Lerninhalte

  • Cloud Run
  • Cloud Storage
  • Cloud Pub/Sub

2. Einrichtung und Anforderungen

Umgebung für das selbstbestimmte Lernen einrichten

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt. 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 Projektteilnehmer. Es handelt sich um eine Zeichenfolge, die von Google APIs nicht verwendet wird und jederzeit aktualisiert werden kann.
  • Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich. Sie kann nach dem Festlegen nicht mehr geändert werden. Die Cloud Console generiert automatisch einen eindeutigen String. ist Ihnen meist egal, was es ist. In den meisten Codelabs musst du auf die Projekt-ID verweisen, die in der Regel als PROJECT_ID identifiziert wird. Wenn es dir nicht gefällt, kannst du eine weitere zufällige Projekt-ID generieren. Du kannst aber auch selbst eine andere testen, um zu sehen, ob sie verfügbar ist. Dann ist es „eingefroren“ nachdem das Projekt erstellt wurde.
  • Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu allen drei Werten finden Sie in der Dokumentation.
  1. Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Cloud-Ressourcen/APIs verwenden zu können. Dieses Codelab sollte ohne großen Aufwand betrieben werden. Wenn Sie Ressourcen beenden möchten, damit über diese Anleitung hinaus keine Kosten anfallen, führen Sie eine Bereinigung durch am Ende des Codelabs. Neue Google Cloud-Nutzer haben Anspruch auf eine kostenlose Testversion von 300$.

Cloud Shell starten

Sie können Google Cloud zwar von Ihrem Laptop aus aus der Ferne bedienen, in diesem Codelab verwenden Sie jedoch Google Cloud Shell, 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 dauert nur einen Moment. Wenn er abgeschlossen ist, sollten Sie in etwa Folgendes sehen:

f6ef2b5f13479f3a.png

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Es bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft auf Google Cloud, wodurch die Netzwerkleistung und Authentifizierung erheblich verbessert werden. Sie können alle Aufgaben in diesem Lab ganz einfach in einem Browser erledigen.

3. APIs aktivieren

In diesem Lab benötigen Sie Cloud Build, um Container-Images zu erstellen, und Cloud Run, um den Container bereitzustellen.

Aktivieren Sie beide APIs über Cloud Shell:

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

Der Vorgang sollte jetzt erfolgreich abgeschlossen werden:

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

4. Weiteren Bucket erstellen

Miniaturansichten der hochgeladenen Bilder werden in einem anderen Bucket gespeichert. Verwenden Sie 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 dies an den Bucket-Namen anhängen. Erstellen Sie dann einen öffentlichen multiregionalen Bucket in Europa mit einheitlichem Zugriff:

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 rufen Sie das Verzeichnis mit dem Dienst auf:

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

Sie haben das folgende Dateilayout für den Dienst:

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

Der Ordner thumbnails/nodejs enthält drei Dateien:

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

6. Code ansehen

Mit dem integrierten Texteditor können Sie sich den Code ansehen. Klicken Sie dazu oben im Cloud Shell-Fenster auf die Schaltfläche Open Editor:

3d145fe299dd8b3e.png

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

Abhängigkeiten

Die Datei package.json definiert die erforderlichen Bibliotheksabhängigkeiten:

{
  "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 zum Lesen und Speichern von Bilddateien in Cloud Storage verwendet. Firestore zum Aktualisieren der Bildmetadaten Express ist ein JavaScript / Node-Web-Framework. Mit dem Textparser-Modul können eingehende Anfragen einfach geparst werden. Bluebird wird für die Verarbeitung von Versprechen 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 zur Bildbearbeitung verwendet. Einige temporäre Verzeichnisse werden zum Speichern von Original- und Miniaturbilddateien erstellt. Anschließend werden die für den Code erforderlichen npm-Module installiert, bevor der Code mit npm start gestartet wird.

index.js

Untersuchen wir den Code in Teilen, um die Funktionsweise dieses Programms besser zu verstehen.

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 benötigen wir die erforderlichen Abhängigkeiten, erstellen unsere Express-Webanwendung und geben an, dass wir den JSON-Textparser verwenden möchten, da eingehende Anfragen eigentlich 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 an die Basis-URL / und umschließen unseren Code mit einer Fehlerlogik, um bessere Informationen zu möglichen Fehlern in unserem Code zu erhalten. Dazu sehen wir uns die Logs an, die in der Stackdriver Logging-Oberfläche in der Google Cloud-Webkonsole angezeigt werden.

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

Interessant in diesem JSON-Dokument ist jedoch, dass das Attribut message.data enthalten ist. Dabei handelt es sich nur um einen String, der die tatsächliche Nutzlast in Base 64 codiert. Aus diesem Grund decodiert der obige Code den Base64-Inhalt dieses Attributs. Nach der Decodierung enthält das data-Attribut ein weiteres JSON-Dokument mit den Cloud Storage-Ereignisdetails, die neben anderen Metadaten den Dateinamen und den Bucket-Namen angeben.

{
  "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 interessieren uns für die Bild- und Bucket-Namen, da unser Code dieses Bild zur Miniaturansicht aus dem Bucket abruft:

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

Der Name des Storage-Buckets für die Ausgabe wird aus einer Umgebungsvariablen abgerufen.

Sie sehen den Ursprungs-Bucket, dessen Dateierstellung den Cloud Run-Dienst ausgelöst hat, und den Ziel-Bucket, in dem das resultierende Image gespeichert wird. Wir verwenden die integrierte path-API für die lokale Dateiverarbeitung, da die imagemagick-Bibliothek die Miniaturansicht 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 schließen wir es mit einem JavaScript-Versprechen ab, das vom Bluebird-Modul bereitgestellt wird. Dann rufen wir die asynchrone Funktion zum Ändern der Größe / Zuschneiden auf, die wir mit den Parametern für die Quell- und Zieldateien erstellt haben, sowie die Abmessungen der zu erstellenden Miniaturansicht.

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

Sobald die Thumbnail-Datei 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 wird:

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 Ablauf 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 unserer Quelldatei finden Sie die Anweisungen, dass Express unsere Webanwendung auf dem Standardport 8080 starten soll.

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 gut gelaufen ist, sollte der Server auf Port 8080 gestartet werden:

Started thumbnail generator on port 8080

Verwende zum Beenden CTRL-C.

8. Container-Image erstellen und veröffentlichen

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

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

Der Cloud Build-"Verlauf" sollte auch den erfolgreichen Build anzeigen:

df00f198dd2bf6bf.png

Klicken Sie auf die Build-ID, um die Detailansicht unter „Build-Artefakte“ aufzurufen. sehen Sie, dass das Container-Image in die Cloud Registry (GCR) hochgeladen wurde:

a4577ce0744f73e2.png

Sie können 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

Der Server sollte damit an Port 8080 im Container gestartet werden:

Started thumbnail generator on port 8080

Verwende zum Beenden CTRL-C.

9. In Cloud Run bereitstellen

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

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

Sie können prüfen, 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, sollten Sie die folgende Ausgabe sehen:

c0f28e7d6de0024.png

Auf der Benutzeroberfläche der Cloud Console sollten Sie außerdem sehen, dass der Dienst erfolgreich bereitgestellt wurde:

9bfe48e3c8b597e5.png

10. Cloud Storage-Ereignisse für Cloud Run über Pub/Sub

Der Dienst ist bereit, aber Sie müssen noch Cloud Storage-Ereignisse für den neu erstellten Cloud Run-Dienst festlegen. Cloud Storage kann Ereignisse zur Dateierstellung über Cloud Pub/Sub senden. Es sind jedoch einige Schritte erforderlich, um dies zu erreichen.

Erstellen Sie ein Pub/Sub-Thema als Kommunikationspipeline:

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

Erstellen Sie Pub/Sub-Benachrichtigungen, wenn Dateien im Bucket gespeichert sind:

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:

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

Gewähren Sie dem Dienstkonto die Berechtigung zum Aufrufen eines Cloud Run-Dienstes:

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 wirksam werden.

Erstellen Sie abschließend 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 in der Console Pub/Sub auf, wählen Sie das Thema gcs-events aus. Unten sollten Sie das Abo sehen:

e8ab86dccb8d890.png

11. Dienst testen

Um zu testen, ob die Einrichtung funktioniert, laden Sie ein neues Bild in den Bucket uploaded-pictures hoch und prüfen Sie im thumbnails-Bucket, ob neue Bilder mit geänderter Größe wie erwartet angezeigt werden.

Sie können auch in den Logs nachsehen, ob die Logging-Meldungen angezeigt werden, während die verschiedenen Schritte des Cloud Run-Dienstes ausgeführt werden:

42c025e2d7d6ca3a.png

12. Bereinigen (optional)

Wenn Sie nicht vorhaben, mit den anderen Labs dieser Reihe fortzufahren, können Sie Ressourcen bereinigen, um Kosten zu sparen und insgesamt ein guter Cloud-Nutzer zu sein. Sie können Ressourcen wie folgt einzeln 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 eingerichtet:

  • Es wurde eine Benachrichtigung in Cloud Storage 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 dies hier manuell konfiguriert.
  • Sie haben ein Abo erstellt, damit unser Cloud Run-Dienst die Pub/Sub-Nachrichten empfängt.
  • Jedes Mal, wenn ein neues Bild in den Bucket hochgeladen wird, wird die Bildgröße mithilfe des neuen Cloud Run-Dienstes angepasst.

Behandelte Themen

  • Cloud Run
  • Cloud Storage
  • Cloud Pub/Sub

Nächste Schritte