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.

Lerninhalte
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. Einrichtung und Anforderungen
Umgebung zum selbstbestimmten Lernen einrichten
- 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.



- 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_IDangegeben 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
- 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:

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

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:

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.jsenthält den Node.js-Code.package.jsondefiniert die Bibliotheksabhängigkeiten.Dockerfiledefiniert 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:

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:

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

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:

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:

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

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:

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:

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