1. Panoramica
In questo codelab, continuerai a lavorare sul lab precedente e aggiungerai un servizio di generazione di miniature. Il servizio di generazione di miniature è un contenitore web che prende le immagini di grandi dimensioni e crea le miniature.
Quando 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 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 già 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 l'impostazione). La console Cloud genera automaticamente una stringa univoca; in genere non ti interessa quale sia. Nella maggior parte dei codelab, dovrai fare riferimento all'ID progetto (in genere identificato come
PROJECT_ID), quindi, se non ti piace, generane un altro casuale oppure puoi provare il tuo e verificare se è disponibile. Dopo la creazione del progetto, l'ID progetto è "bloccato". - Esiste un terzo valore, un numero di progetto , utilizzato da alcune API. Scopri di più su tutti e tre i valori nella documentazione.
- Dopodiché, dovrai abilitare la fatturazione nella console Cloud 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 addebiti di fatturazione oltre questo tutorial, segui le istruzioni di "pulizia" alla fine del codelab. I nuovi utenti di Google Cloud possono beneficiare del programma di 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 Google Cloud, 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 qualcosa di simile a questo:

Questa macchina virtuale è caricata con tutti gli strumenti di sviluppo di cui avrai bisogno. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni e l'autenticazione della rete. Tutto il lavoro in 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 l'operazione 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à GOOGLE_CLOUD_PROJECT impostato sull'ID progetto univoco. Puoi aggiungerlo al nome del bucket. Quindi, crea un bucket pubblico su più regioni 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
Avrai il seguente layout di file per il servizio:
services
|
├── thumbnails
|
├── nodejs
|
├── Dockerfile
├── index.js
├── package.json
Nella cartella thumbnails/nodejs sono presenti 3 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 immagine originali e delle miniature. Prima di avviare il codice con npm start, vengono installati i moduli NPM necessari per il nostro codice.
index.js
Esploriamo 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, oltre a indicare 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 sull'URL di base / e racchiudiamo il nostro codice con 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 di Stackdriver Logging nella console web 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 è in realtà ciò che è contenuto nell'attributo message.data, che è solo una stringa, ma che codifica il payload effettivo in Base64. Ecco perché il nostro codice sopra decodifica il contenuto 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 dell'immagine e del bucket, poiché 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}`);
Stiamo recuperando 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 memorizzeremo l'immagine risultante. Stiamo utilizzando l'API integrata path per la gestione dei file locali, poiché la libreria imagemagick creerà la miniatura localmente nella directory temporanea /tmp. Attendiamo await 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 racchiudiamo in una promessa JavaScript (fornita dal modulo Bluebird). Quindi chiamiamo la funzione di ridimensionamento / ritaglio asincrona 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 in 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 HTTP POST 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 avviare effettivamente l'applicazione web Express sulla porta predefinita 8080.
7. Prova localmente
Prova il codice localmente per assicurarti che funzioni prima di eseguire il deployment nel cloud.
Nella cartella thumbnails/nodejs, installa le dipendenze npm e avvia il server:
npm install; npm start
Se tutto è andato a buon fine, il server dovrebbe essere avviato sulla porta 8080:
Started thumbnail generator on port 8080
Utilizza 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.
Nella 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 i 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
Il server dovrebbe essere avviato sulla porta 8080 nel container:
Started thumbnail generator on port 8080
Utilizza 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 il comando seguente per eseguire il deployment dell'immagine container in 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 account di servizio specifici.
Se il deployment è andato a buon fine, dovresti vedere l'output seguente:

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

10. Eventi Cloud Storage in Cloud Run tramite Pub/Sub
Il servizio è pronto, ma devi comunque fare in modo che gli eventi Cloud Storage vengano inviati al 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 account di servizio 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 il service account Pub/Sub l'8 aprile 2021 o in una data precedente, concedi il ruolo iam.serviceAccountTokenCreator al service account 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 potrebbe richiedere alcuni minuti.
Infine, crea una sottoscrizione 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 stata creata una sottoscrizione. Vai a Pub/Sub nella console, seleziona l'argomento gcs-events e in basso dovresti vedere la sottoscrizione:

11. Verifica 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. Liberare spazio (facoltativo)
Se non intendi continuare con gli altri lab della serie, puoi liberare spazio dalle risorse per ridurre i costi e per essere un buon cittadino del cloud. Puoi liberare spazio dalle risorse singolarmente come segue.
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 a posto:
- Hai creato una notifica in Cloud Storage che invia messaggi Pub/Sub a un argomento quando viene caricata una nuova immagine.
- Hai definito i binding e gli account IAM richiesti (a differenza di Cloud Functions, dove tutto è automatizzato, qui la configurazione è manuale).
- Hai creato una sottoscrizione in modo che il nostro servizio Cloud Run riceva i messaggi Pub/Sub.
- Ogni volta che viene caricata una nuova immagine nel bucket, l'immagine viene ridimensionata grazie al nuovo servizio Cloud Run.
Argomenti trattati
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub