1. Introduzione
Panoramica
In questo codelab, configurerai Cloud Run per creare ed eseguire automaticamente il deployment delle nuove versioni della tua applicazione ogni volta che esegui il push delle modifiche al codice sorgente in un repository GitHub.
Questa applicazione demo salva i dati degli utenti su Firestore. Tuttavia, viene salvata correttamente solo una parte parziale dei dati. Configurerai deployment continui in modo che, quando esegui il push di una correzione di bug nel tuo repository GitHub, la correzione diventi automaticamente disponibile in una nuova revisione.
Cosa imparerai a fare
- Scrivi un'applicazione web Express con l'editor di Cloud Shell
- Connetti il tuo account GitHub a Google Cloud per deployment continui
- Esegui automaticamente il deployment dell'applicazione in Cloud Run
- Scopri come utilizzare HTMX e TailwindCSS
2. Configurazione e requisiti
Prerequisiti
- Hai un account GitHub e conosci la creazione e il push di codice nei repository.
- Hai eseguito l'accesso alla console Cloud.
- Hai già eseguito il deployment di un servizio Cloud Run. Ad esempio, per iniziare, puoi seguire la guida rapida al deployment di un servizio web dal codice sorgente.
Attiva Cloud Shell
- Dalla console Cloud, fai clic su Attiva Cloud Shell .
Se è la prima volta che avvii Cloud Shell, ti verrà mostrata una schermata intermedia che descrive di cosa si tratta. Se ti è stata presentata una schermata intermedia, fai clic su Continua.
Il provisioning e la connessione a Cloud Shell dovrebbero richiedere solo qualche istante.
Questa macchina virtuale viene caricata con tutti gli strumenti di sviluppo necessari. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni di rete e l'autenticazione. Gran parte, se non tutto, del lavoro in questo codelab può essere svolto con un browser.
Una volta stabilita la connessione a Cloud Shell, dovresti vedere che hai eseguito l'autenticazione e che il progetto è impostato sul tuo ID progetto.
- Esegui questo comando in Cloud Shell per verificare che l'account sia autenticato:
gcloud auth list
Output comando
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Esegui questo comando in Cloud Shell per confermare che il comando gcloud è a conoscenza del tuo progetto:
gcloud config list project
Output comando
[core] project = <PROJECT_ID>
In caso contrario, puoi impostarlo con questo comando:
gcloud config set project <PROJECT_ID>
Output comando
Updated property [core/project].
3. Abilita le API e imposta le variabili di ambiente
Abilita API
Questo codelab richiede l'utilizzo delle API seguenti. Puoi abilitare queste API eseguendo questo comando:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ firestore.googleapis.com \ iamcredentials.googleapis.com
Configura le variabili di ambiente
Puoi impostare le variabili di ambiente che verranno utilizzate in questo codelab.
REGION=<YOUR-REGION> PROJECT_ID=<YOUR-PROJECT-ID> PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)') SERVICE_ACCOUNT="firestore-accessor" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
4. Crea un account di servizio
Questo account di servizio verrà utilizzato da Cloud Run per chiamare l'API Gemini di Vertex AI. Questo account di servizio avrà anche le autorizzazioni per leggere e scrivere su Firestore e leggere i secret da Secret Manager.
Innanzitutto, crea l'account di servizio eseguendo questo comando:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run access to Firestore"
Ora concedi all'account di servizio l'accesso in lettura e scrittura a Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
5. Crea e configura un progetto Firebase
- Nella console Firebase, fai clic su Aggiungi progetto.
- Inserisci <YOUR_PROJECT_ID> per aggiungere Firebase a uno dei tuoi progetti Google Cloud esistenti
- Se richiesto, leggi e accetta i Termini di Firebase.
- Fai clic su Continua.
- Fai clic su Conferma piano per confermare il piano di fatturazione Firebase.
- Attivare Google Analytics per questo codelab è facoltativo.
- Fai clic su Aggiungi Firebase.
- Una volta creato il progetto, fai clic su Continua.
- Dal menu Crea, fai clic su Database Firestore.
- Fai clic su Crea database.
- Scegli la tua regione dall'elenco a discesa Località, poi fai clic su Avanti.
- Utilizza l'impostazione predefinita Avvia in modalità di produzione, quindi fai clic su Crea.
6. Scrivi l'applicazione
Per prima cosa, crea una directory per il codice sorgente e accedi a quella directory.
mkdir cloud-run-github-cd-demo && cd $_
Quindi, crea un file package.json
con i seguenti contenuti:
{ "name": "cloud-run-github-cd-demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node app.js", "nodemon": "nodemon app.js", "tailwind-dev": "npx tailwindcss -i ./input.css -o ./public/output.css --watch", "tailwind": "npx tailwindcss -i ./input.css -o ./public/output.css", "dev": "npm run tailwind && npm run nodemon" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@google-cloud/firestore": "^7.3.1", "axios": "^1.6.7", "express": "^4.18.2", "htmx.org": "^1.9.10" }, "devDependencies": { "nodemon": "^3.1.0", "tailwindcss": "^3.4.1" } }
Innanzitutto, crea un file sorgente app.js
con i contenuti seguenti. Questo file contiene il punto di ingresso del servizio e la logica principale dell'app.
const express = require("express"); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); const path = require("path"); const { get } = require("axios"); const { Firestore } = require("@google-cloud/firestore"); const firestoreDb = new Firestore(); const fs = require("fs"); const util = require("util"); const { spinnerSvg } = require("./spinnerSvg.js"); const service = process.env.K_SERVICE; const revision = process.env.K_REVISION; app.use(express.static("public")); app.get("/edit", async (req, res) => { res.send(`<form hx-post="/update" hx-target="this" hx-swap="outerHTML"> <div> <p> <label>Name</label> <input class="border-2" type="text" name="name" value="Cloud"> </p><p> <label>Town</label> <input class="border-2" type="text" name="town" value="Nibelheim"> </p> </div> <div class="flex items-center mr-[10px] mt-[10px]"> <button class="btn bg-blue-500 text-white px-4 py-2 rounded-lg text-center text-sm font-medium mr-[10px]">Submit</button> <button class="btn bg-gray-200 text-gray-800 px-4 py-2 rounded-lg text-center text-sm font-medium mr-[10px]" hx-get="cancel">Cancel</button> ${spinnerSvg} </div> </form>`); }); app.post("/update", async function (req, res) { let name = req.body.name; let town = req.body.town; const doc = firestoreDb.doc(`demo/${name}`); //TODO: fix this bug await doc.set({ name: name /* town: town */ }); res.send(`<div hx-target="this" hx-swap="outerHTML" hx-indicator="spinner"> <p> <div><label>Name</label>: ${name}</div> </p><p> <div><label>Town</label>: ${town}</div> </p> <button hx-get="/edit" class="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium mt-[10px]" > Click to update </button> </div>`); }); app.get("/cancel", (req, res) => { res.send(`<div hx-target="this" hx-swap="outerHTML"> <p> <div><label>Name</label>: Cloud</div> </p><p> <div><label>Town</label>: Nibelheim</div> </p> <div> <button hx-get="/edit" class="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium mt-[10px]" > Click to update </button> </div> </div>`); }); const port = parseInt(process.env.PORT) || 8080; app.listen(port, async () => { console.log(`booth demo: listening on port ${port}`); //serviceMetadata = helper(); }); app.get("/helper", async (req, res) => { let region = ""; let projectId = ""; let div = ""; try { // Fetch the token to make a GCF to GCF call const response1 = await get( "http://metadata.google.internal/computeMetadata/v1/project/project-id", { headers: { "Metadata-Flavor": "Google" } } ); // Fetch the token to make a GCF to GCF call const response2 = await get( "http://metadata.google.internal/computeMetadata/v1/instance/region", { headers: { "Metadata-Flavor": "Google" } } ); projectId = response1.data; let regionFull = response2.data; const index = regionFull.lastIndexOf("/"); region = regionFull.substring(index + 1); div = ` <div> This created the revision <code>${revision}</code> of the Cloud Run service <code>${service}</code> in <code>${region}</code> for project <code>${projectId}</code>. </div>`; } catch (ex) { // running locally div = `<div> This is running locally.</div>`; } res.send(div); });
Crea un file denominato spinnerSvg.js
module.exports.spinnerSvg = `<svg id="spinner" alt="Loading..." class="htmx-indicator animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg>`;
Crea un file input.css
per tailwindCSS
@tailwind base; @tailwind components; @tailwind utilities;
Crea il file tailwind.config.js
per tailwindCSS
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
e crea un file .gitignore
.
node_modules/ npm-debug.log coverage/ package-lock.json .DS_Store
Ora crea una nuova directory public
.
mkdir public cd public
E all'interno della directory pubblica, crea il file index.html
per il front-end, che utilizzerà il formato htmx.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous" ></script> <link href="./output.css" rel="stylesheet" /> <title>Demo 1</title> </head> <body class="font-sans bg-body-image bg-cover bg-center leading-relaxed" > <div class="container max-w-[700px] mt-[50px] ml-auto mr-auto"> <div class="hero flex items-center"> <div class="message text-base text-center mb-[24px]"> <h1 class="text-2xl font-bold mb-[10px]"> It's running! </h1> <div class="congrats text-base font-normal"> Congratulations, you successfully deployed your service to Cloud Run. </div> </div> </div> <div class="details mb-[20px]"> <p> <div hx-trigger="load" hx-get="/helper" hx-swap="innerHTML" hx-target="this">Hello</div> </p> </div> <p class="callout text-sm text-blue-700 font-bold pt-4 pr-6 pb-4 pl-10 leading-tight" > You can deploy any container to Cloud Run that listens for HTTP requests on the port defined by the <code>PORT</code> environment variable. Cloud Run will scale automatically based on requests and you never have to worry about infrastructure. </p> <h1 class="text-2xl font-bold mt-[40px] mb-[20px]"> Persistent Storage Example using Firestore </h1> <div hx-target="this" hx-swap="outerHTML"> <p> <div><label>Name</label>: Cloud</div> </p><p> <div><label>Town</label>: Nibelheim</div> </p> <div> <button hx-get="/edit" class="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium mt-[10px]" > Click to update </button> </div> </div> <h1 class="text-2xl font-bold mt-[40px] mb-[20px]"> What's next </h1> <p class="next text-base mt-4 mb-[20px]"> You can build this demo yourself! </p> <p class="cta"> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg text-center text-sm font-medium" > VIEW CODELAB </button> </p> </div> </body> </html>
7. Esegui l'applicazione in locale
In questa sezione, eseguirai l'applicazione localmente per verificare la presenza di un bug nell'applicazione quando l'utente tenta di salvare i dati.
Innanzitutto, dovrai disporre del ruolo Utente Datastore per accedere a Firestore (se utilizzi la tua identità per l'autenticazione, ad esempio se esegui l'operazione in Cloud Shell) oppure puoi impersonare l'account utente creato in precedenza.
Utilizzo di ADC durante l'esecuzione in locale
Se l'esecuzione è in Cloud Shell, significa che è già in esecuzione su una macchina virtuale Google Compute Engine. Le tue credenziali associate a questa macchina virtuale (come mostrato dall'esecuzione di gcloud auth list
) verranno utilizzate automaticamente da Credenziali predefinite dell'applicazione (ADC), quindi non è necessario utilizzare il comando gcloud auth application-default login
. Tuttavia, per la tua identità sarà comunque necessario il ruolo Utente Datastore. Puoi andare direttamente alla sezione Eseguire l'app in locale.
Se invece è in esecuzione sul terminale locale (ovvero non in Cloud Shell), dovrai utilizzare le Credenziali predefinite dell'applicazione per autenticarti alle API di Google. Puoi 1) accedere utilizzando le tue credenziali (a condizione che tu disponga del ruolo Utente Datastore) o 2) accedere impersonando l'account di servizio utilizzato in questo codelab.
Opzione 1) Utilizzare le tue credenziali per ADC
Se vuoi utilizzare le tue credenziali, puoi prima eseguire gcloud auth list
per verificare il modo in cui hai eseguito l'autenticazione in gcloud. Successivamente, potresti dover concedere alla tua identità il ruolo Vertex AI User. Se la tua identità ha il ruolo Proprietario, hai già questo ruolo utente Utente Datastore. In caso contrario, puoi eseguire questo comando per concedere alla tua identità il ruolo utente Vertex AI e il ruolo Utente Datastore.
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
Poi esegui questo comando
gcloud auth application-default login
Opzione 2) Furto d'identità di un account di servizio per ADC
Se vuoi utilizzare l'account di servizio creato in questo codelab, il tuo account utente dovrà avere il ruolo Creatore token account di servizio. Puoi ottenere questo ruolo eseguendo questo comando:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Successivamente, eseguirai questo comando per utilizzare ADC con l'account di servizio
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Esegui l'app localmente
Dopodiché, assicurati di essere nella directory principale cloud-run-github-cd-demo
del codelab.
cd .. && pwd
Ora installerai le dipendenze.
npm install
Infine, puoi avviare l'app eseguendo lo script riportato di seguito. Questo script genererà anche il file output.css da tailwindCSS.
npm run dev
Ora apri il browser web su http://localhost:8080. Se sei in Cloud Shell, puoi aprire il sito web aprendo il pulsante Anteprima web e selezionando Porta di anteprima 8080.
Inserisci il testo per i campi di immissione del nome e della città e fai clic su Salva. Quindi aggiorna la pagina. Noterai che il campo della città non è rimasto. Lo correggerai nella sezione successiva.
Interrompi l'esecuzione in locale dell'app express (ad es. Ctrl^c su MacOS).
8. Crea un repository GitHub
Nella tua directory locale, crea un nuovo repository con main come nome del ramo predefinito.
git init git branch -M main
Esegui il commit del codebase corrente che contiene il bug. correggerai il bug dopo aver configurato il deployment continuo.
git add . git commit -m "first commit for express application"
Vai a GitHub e crea un repository vuoto, privato o pubblico. Questo codelab consiglia di assegnare un nome al repository cloud-run-auto-deploy-codelab
. Per creare un repository vuoto, lasci tutte le impostazioni predefinite deselezionate o non vuoi impostare nessuna, in modo che, per impostazione predefinita, nessun contenuto sia presente nel repository al momento della creazione, ad esempio
Se hai completato questo passaggio correttamente, vedrai le seguenti istruzioni nella pagina del repository vuota:
Segui le istruzioni per eseguire il push di un repository esistente dalla riga di comando eseguendo questi comandi:
Innanzitutto, aggiungi il repository remoto eseguendo
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
quindi esegui il push del ramo principale al repository a monte.
git push -u origin main
9. Configura il deployment continuo
Ora che hai il codice in GitHub, puoi configurare il deployment continuo. Vai alla console Cloud per Cloud Run.
- Fai clic su Crea un servizio
- Fai clic su Esegui il deployment continuo da un repository
- Fai clic su CONFIGURA CLOUD BUILD.
- In Repository di codice sorgente
- Seleziona GitHub come provider del repository
- Fai clic su Gestisci repository connessi per configurare l'accesso di Cloud Build al repository
- Seleziona il tuo repository e fai clic su Avanti
- In Configurazione build
- Lascia Ramo impostato su ^main$
- Per Tipo di build, seleziona Go, Node.js, Python, Java, .NET Core, Ruby o PHP tramite i buildpack di Google Cloud
- Lascia la directory di contesto della build su
/
- Fai clic su Salva.
- In Autenticazione
- Fai clic su Consenti chiamate non autenticate.
- In Container, Volumi, Networking, Sicurezza
- Nella scheda Sicurezza, seleziona l'account di servizio creato in un passaggio precedente, ad esempio
Cloud Run access to Firestore
- Nella scheda Sicurezza, seleziona l'account di servizio creato in un passaggio precedente, ad esempio
- Fai clic su CREA.
In questo modo verrà eseguito il deployment del servizio Cloud Run contenente il bug che verrà corretto nella prossima sezione.
10. Correggere il bug
Correggere il bug nel codice
Nell'editor di Cloud Shell, apri il file app.js
e vai al commento //TODO: fix this bug
modifica la riga seguente da
//TODO: fix this bug await doc.set({ name: name });
a
//fixed town bug await doc.set({ name: name, town: town });
Verifica la correzione eseguendo
npm run start
e apri il browser web. Salva di nuovo i dati per la città e aggiorna. I dati relativi alla città appena inseriti sono mantenuti correttamente dopo l'aggiornamento.
Ora che hai verificato la correzione, puoi eseguirne il deployment. Innanzitutto, esegui il commit della correzione.
git add . git commit -m "fixed town bug"
ed eseguirne il push nel repository upstream su GitHub.
git push origin main
Cloud Build eseguirà automaticamente il deployment delle modifiche. Puoi accedere alla console Cloud per il tuo servizio Cloud Run per monitorare le modifiche al deployment.
Verifica la correzione in produzione
Quando la console Cloud per il tuo servizio Cloud Run mostra che una seconda revisione sta gestendo il 100% del traffico, ad esempio https://console.cloud.google.com/run/detail/<YOUR_REGION>/<YOUR_SERVICE_NAME>/revisions, puoi aprire l'URL del servizio Cloud Run nel browser e verificare che i dati della città appena inseriti siano persistenti dopo l'aggiornamento della pagina.
11. Complimenti!
Complimenti per aver completato il codelab.
Ti consigliamo di consultare la documentazione Cloud Run e il deployment continuo da Git.
Argomenti trattati
- Scrivi un'applicazione web Express con l'editor di Cloud Shell
- Connetti il tuo account GitHub a Google Cloud per deployment continui
- Esegui automaticamente il deployment dell'applicazione in Cloud Run
- Scopri come utilizzare HTMX e TailwindCSS
12. Esegui la pulizia
Per evitare addebiti involontari, ad esempio se i servizi Cloud Run vengono inavvertitamente richiamati più volte rispetto all'allocazione mensile dei callout Cloud Run nel livello senza costi, puoi eliminare Cloud Run o eliminare il progetto che hai creato nel passaggio 2.
Per eliminare il servizio Cloud Run, vai alla console Cloud Run all'indirizzo https://console.cloud.google.com/run ed elimina il servizio Cloud Run che hai creato in questo codelab, ad esempio: Elimina il servizio cloud-run-auto-deploy-codelab
.
Se scegli di eliminare l'intero progetto, puoi andare all'indirizzo https://console.cloud.google.com/cloud-resource-manager, selezionare il progetto che hai creato nel passaggio 2 e scegliere Elimina. Se elimini il progetto, dovrai modificarli in Cloud SDK. Puoi visualizzare l'elenco di tutti i progetti disponibili eseguendo gcloud projects list
.