1. Panoramica
In questo codelab, ti baserai sul lab precedente e aggiungerai un servizio di generazione di miniature. Il servizio di generazione delle miniature è un container web che prende immagini di grandi dimensioni e crea miniature.
Man mano che l'immagine viene caricata in Cloud Storage, viene inviata una notifica tramite Cloud Pub/Sub a un container web Cloud Run, che ridimensiona le immagini e le salva di nuovo in un altro bucket in Cloud Storage.

Obiettivi didattici
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. Configurazione e requisiti
Configurazione dell'ambiente autonomo
- Accedi alla console Google Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.



- Il nome del progetto è il nome visualizzato per i partecipanti a questo progetto. È una stringa di caratteri non utilizzata dalle API di Google e puoi aggiornarla in qualsiasi momento.
- L'ID progetto deve essere univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). Cloud Console genera automaticamente una stringa univoca, di solito non ti interessa di cosa si tratta. Nella maggior parte dei codelab, devi fare riferimento all'ID progetto (che in genere è identificato come
PROJECT_ID), quindi, se non ti piace, generane un altro casuale oppure puoi provare il tuo e vedere se è disponibile. Viene "congelato" dopo la creazione del progetto. - Esiste un terzo valore, un numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
- Successivamente, devi abilitare la fatturazione in Cloud Console per utilizzare le risorse/API Cloud. L'esecuzione di questo codelab non dovrebbe costare molto, se non nulla. Per arrestare le risorse in modo da non incorrere in costi di fatturazione al termine di questo tutorial, segui le istruzioni di "pulizia" riportate alla fine del codelab. I nuovi utenti di Google Cloud possono beneficiare del programma prova senza costi di 300$.
Avvia Cloud Shell
Sebbene Google Cloud possa essere gestito da remoto dal tuo laptop, in questo codelab utilizzerai Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.
Nella console GCP, fai clic sull'icona di Cloud Shell nella barra degli strumenti in alto a destra:

Bastano pochi istanti per eseguire il provisioning e connettersi all'ambiente. Al termine, dovresti vedere un risultato simile a questo:

Questa macchina virtuale è caricata con tutti gli strumenti per sviluppatori di cui avrai bisogno. Offre una home directory permanente da 5 GB e viene eseguita su Google Cloud, migliorando notevolmente le prestazioni e l'autenticazione della rete. Tutto il lavoro di questo lab può essere svolto semplicemente con un browser.
3. Abilita API
In questo lab, avrai bisogno di Cloud Build per creare immagini container e di Cloud Run per eseguire il deployment del container.
Abilita entrambe le API da Cloud Shell:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
Dovresti vedere che l'operazione è stata completata correttamente:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Crea un altro bucket
Memorizzerai le miniature delle immagini caricate in un altro bucket. Utilizziamo gsutil per creare il secondo bucket.
In Cloud Shell, imposta una variabile per il nome univoco del bucket. Cloud Shell ha già impostato GOOGLE_CLOUD_PROJECT sul tuo ID progetto univoco. Puoi aggiungerlo al nome del bucket. Quindi, crea un bucket multiregionale pubblico in Europa con accesso a livello uniforme:
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
Alla fine, dovresti avere un nuovo bucket pubblico:

5. Clona il codice
Clona il codice e vai alla directory contenente il servizio:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop cd serverless-photosharing-workshop/services/thumbnails/nodejs
Per il servizio avrai il seguente layout dei file:
services
|
├── thumbnails
|
├── nodejs
|
├── Dockerfile
├── index.js
├── package.json
All'interno della cartella thumbnails/nodejs, hai tre file:
index.jscontiene il codice Node.jspackage.jsondefinisce le dipendenze della libreriaDockerfiledefinisce l'immagine container
6. Esplora il codice
Per esplorare il codice, puoi utilizzare l'editor di testo integrato facendo clic sul pulsante Open Editor nella parte superiore della finestra di Cloud Shell:

Puoi anche aprire l'editor in una finestra del browser dedicata, per avere più spazio sullo schermo.
Dipendenze
Il file package.json definisce le dipendenze della libreria necessarie:
{
"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"
}
}
La libreria Cloud Storage viene utilizzata per leggere e salvare i file immagine in Cloud Storage. Firestore per aggiornare i metadati dell'immagine. Express è un framework web JavaScript / Node. Il modulo body-parser viene utilizzato per analizzare facilmente le richieste in entrata. Bluebird viene utilizzato per la gestione delle promesse e Imagemagick è una libreria per la manipolazione delle immagini.
Dockerfile
Dockerfile definisce l'immagine container per l'applicazione:
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" ]
L'immagine di base è Node 14 e la libreria ImageMagick viene utilizzata per la manipolazione delle immagini. Vengono create alcune directory temporanee per contenere i file delle immagini originali e delle miniature. Poi vengono installati i moduli NPM necessari al nostro codice prima di avviare il codice con npm start.
index.js
Esaminiamo il codice in parti, in modo da capire meglio cosa fa questo programma.
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());
Innanzitutto, richiediamo le dipendenze necessarie e creiamo la nostra applicazione web Express, indicando che vogliamo utilizzare il parser del corpo JSON, poiché le richieste in entrata sono in realtà solo payload JSON inviati tramite una richiesta POST alla nostra applicazione.
app.post('/', async (req, res) => {
try {
// ...
} catch (err) {
console.log(`Error: creating the thumbnail: ${err}`);
console.error(err);
res.status(500).send(err);
}
});
Riceviamo questi payload in entrata nell'URL di base / e inseriamo il nostro codice in una logica di gestione degli errori, per avere informazioni migliori sul motivo per cui qualcosa potrebbe non funzionare nel nostro codice esaminando i log che saranno visibili dall'interfaccia Stackdriver Logging nella console web di 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}`);
Sulla piattaforma Cloud Run, i messaggi Pub/Sub vengono inviati tramite richieste HTTP POST, come payload JSON del modulo:
{
"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"
}
Ma ciò che è davvero interessante in questo documento JSON è ciò che è contenuto nell'attributo message.data, che è solo una stringa, ma che codifica il payload effettivo in Base 64. Ecco perché il nostro codice riportato sopra decodifica i contenuti in base64 di questo attributo. L'attributo data, una volta decodificato, contiene un altro documento JSON che rappresenta i dettagli dell'evento Cloud Storage, che, tra gli altri metadati, indica il nome del file e il nome del bucket.
{
"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="
}
Ci interessano i nomi delle immagini e dei bucket, perché il nostro codice recupererà l'immagine dal bucket per il trattamento delle miniature:
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}`);
Recuperiamo il nome del bucket di archiviazione di output da una variabile di ambiente.
Abbiamo il bucket di origine la cui creazione di file ha attivato il nostro servizio Cloud Run e il bucket di destinazione in cui archivieremo l'immagine risultante. Utilizziamo l'API integrata path per la gestione dei file locali, poiché la libreria imagemagick creerà la miniatura localmente nella directory temporanea /tmp. await per una chiamata asincrona per scaricare il file immagine caricato.
const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
srcPath: originalFile,
dstPath: thumbFile,
width: 400,
height: 400
});
console.log(`Created local thumbnail in ${thumbFile}`);
Il modulo imagemagick non è molto compatibile con async / await, quindi lo stiamo inserendo in una promessa JavaScript (fornita dal modulo Bluebird). Poi chiamiamo la funzione di ridimensionamento / ritaglio asincrono che abbiamo creato con i parametri per i file di origine e di destinazione, nonché le dimensioni della miniatura che vogliamo creare.
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
Una volta caricato il file della miniatura su Cloud Storage, aggiorneremo anche i metadati in Cloud Firestore per aggiungere un flag booleano che indica che la miniatura per questa immagine è stata effettivamente generata:
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`);
Al termine della richiesta, rispondiamo alla richiesta POST HTTP che il file è stato elaborato correttamente.
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started thumbnail generator on port ${PORT}`);
});
Alla fine del file di origine, abbiamo le istruzioni per fare in modo che Express avvii effettivamente la nostra applicazione web sulla porta predefinita 8080.
7. Testare localmente
Testa il codice localmente per assicurarti che funzioni prima del deployment nel cloud.
All'interno della cartella thumbnails/nodejs, installa le dipendenze npm e avvia il server:
npm install; npm start
Se tutto è andato a buon fine, il server dovrebbe avviarsi sulla porta 8080:
Started thumbnail generator on port 8080
Usa CTRL-C per uscire.
8. Crea e pubblica l'immagine container
Cloud Run esegue i container, ma prima devi creare l'immagine container (definita in Dockerfile). Google Cloud Build può essere utilizzato per creare immagini container e poi ospitarle in Google Container Registry.
All'interno della cartella thumbnails/nodejs in cui si trova Dockerfile, esegui il comando seguente per creare l'immagine container:
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
Dopo un minuto o due, la build dovrebbe riuscire:

Anche la sezione "Cronologia" di Cloud Build dovrebbe mostrare la build riuscita:

Se fai clic sull'ID build per visualizzare la visualizzazione dei dettagli, nella scheda "artefatti build" dovresti vedere che l'immagine container è stata caricata in Cloud Registry (GCR):

Se vuoi, puoi verificare che l'immagine container venga eseguita localmente in Cloud Shell:
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
Dovrebbe avviare il server sulla porta 8080 nel container:
Started thumbnail generator on port 8080
Usa CTRL-C per uscire.
9. Esegui il deployment in Cloud Run
Prima di eseguire il deployment in Cloud Run, imposta la regione Cloud Run su una delle regioni supportate e la piattaforma su managed:
gcloud config set run/region europe-west1 gcloud config set run/platform managed
Puoi verificare che la configurazione sia impostata:
gcloud config list ... [run] platform = managed region = europe-west1
Esegui questo comando per eseguire il deployment dell'immagine container su 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
Prendi nota del flag --no-allow-unauthenticated. In questo modo, il servizio Cloud Run diventa un servizio interno che verrà attivato solo da service account specifici.
Se il deployment è riuscito, dovresti visualizzare l'output seguente:

Se vai all'interfaccia utente della console Cloud, dovresti anche vedere che il servizio è stato implementato correttamente:

10. Eventi Cloud Storage a Cloud Run tramite Pub/Sub
Il servizio è pronto, ma devi ancora creare eventi Cloud Storage per il servizio Cloud Run appena creato. Cloud Storage può inviare eventi di creazione di file tramite Cloud Pub/Sub, ma sono necessari alcuni passaggi per far funzionare questa funzionalità.
Crea un argomento Pub/Sub come pipeline di comunicazione:
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
Crea notifiche Pub/Sub quando i file vengono archiviati nel bucket:
BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES
Crea un service account per la sottoscrizione Pub/Sub che creeremo in un secondo momento:
SERVICE_ACCOUNT=$TOPIC_NAME-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--display-name "Cloud Run Pub/Sub Invoker"
Concedi al service account l'autorizzazione per richiamare un servizio 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
Se hai attivato l'account di servizio Pub/Sub il giorno 8 aprile 2021 o in una data precedente, concedi il ruolo iam.serviceAccountTokenCreator all'account di servizio 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
La propagazione delle modifiche IAM può richiedere alcuni minuti.
Infine, crea un abbonamento Pub/Sub con il service account:
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
Puoi verificare che sia stato creato un abbonamento. Vai a Pub/Sub nella console, seleziona l'argomento gcs-events e in basso dovresti vedere l'abbonamento:

11. Testare il servizio
Per verificare se la configurazione funziona, carica una nuova immagine nel bucket uploaded-pictures e controlla nel bucket thumbnails che le nuove immagini ridimensionate vengano visualizzate come previsto.
Puoi anche controllare i log per visualizzare i messaggi di logging man mano che vengono eseguiti i vari passaggi del servizio Cloud Run:

12. Pulizia (facoltativo)
Se non intendi continuare con gli altri lab della serie, puoi eseguire la pulizia delle risorse per risparmiare sui costi e per essere un buon cittadino del cloud. Puoi ripulire le risorse singolarmente nel seguente modo.
Elimina il bucket:
gsutil rb gs://$BUCKET_THUMBNAILS
Elimina il servizio:
gcloud run services delete $SERVICE_NAME -q
Elimina l'argomento Pub/Sub:
gcloud pubsub topics delete $TOPIC_NAME
In alternativa, puoi eliminare l'intero progetto:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
13. Complimenti!
Ora è tutto pronto:
- È stata creata una notifica in Cloud Storage che invia messaggi Pub/Sub su un argomento quando viene caricata una nuova immagine.
- Definisci gli account e i binding IAM richiesti (a differenza di Cloud Functions, dove tutto è automatizzato, qui la configurazione è manuale).
- È stato creato un abbonamento in modo che il nostro servizio Cloud Run riceva i messaggi Pub/Sub.
- Ogni volta che viene caricata una nuova immagine nel bucket, le dimensioni vengono modificate grazie al nuovo servizio Cloud Run.
Argomenti trattati
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub