1. Introduzione
Panoramica
In questo codelab, configurerai Cloud Run per creare ed eseguire il deployment automaticamente di 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 utente in Firestore, ma solo una parte dei dati viene salvata correttamente. Configurerai i deployment continui in modo che, quando esegui il push di una correzione di bug nel repository GitHub, la correzione diventi automaticamente disponibile in una nuova revisione.
Cosa imparerai a fare
- Scrivere un'applicazione web Express con l'editor di Cloud Shell
- Collega il tuo account GitHub a Google Cloud per i 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 sai come creare ed eseguire il push del codice nei repository.
- Hai eseguito l'accesso a Cloud Console.
- Hai già eseguito il deployment di un servizio Cloud Run. Ad esempio, puoi seguire la guida rapida per il deployment di un servizio web dal codice sorgente per iniziare.
Attiva Cloud Shell
- Nella console Cloud, fai clic su Attiva Cloud Shell
.

Se è la prima volta che avvii Cloud Shell, viene visualizzata una schermata intermedia che ne descrive le funzionalità. Se è stata visualizzata una schermata intermedia, fai clic su Continua.

Bastano pochi istanti per eseguire il provisioning e connettersi a Cloud Shell.

Questa macchina virtuale è 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 e l'autenticazione della rete. Gran parte del lavoro per questo codelab, se non tutto, può essere svolto con un browser.
Una volta eseguita la connessione a Cloud Shell, dovresti vedere che il tuo account è autenticato e 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 verificare che il comando gcloud conosca il 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 seguenti API. Puoi abilitare queste API eseguendo il seguente 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 service account verrà utilizzato da Cloud Run per chiamare l'API Gemini di Vertex AI. Questo service account avrà anche le autorizzazioni per leggere e scrivere in Firestore e leggere i secret da Secret Manager.
Innanzitutto, crea il service account eseguendo questo comando:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run access to Firestore"
Ora concedi al service account 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.
- L'abilitazione di Google Analytics per questo codelab è facoltativa.
- Fai clic su Aggiungi Firebase.
- Una volta creato il progetto, fai clic su Continua.
- Nel menu Build, fai clic su Database Firestore.
- Fai clic su Crea database.
- Scegli la tua regione dal menu a discesa Località, poi fai clic su Avanti.
- Utilizza l'opzione predefinita Avvia in modalità di produzione, quindi fai clic su Crea.
6. Scrivi l'applicazione
Innanzitutto, crea una directory per il codice sorgente e accedi tramite cd.
mkdir cloud-run-github-cd-demo && cd $_
Quindi, crea un file package.json con il seguente contenuto:
{
"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 di origine app.js con il seguente contenuto. 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>`;
Creare un file input.css per tailwindCSS
@tailwind base; @tailwind components; @tailwind utilities;
e 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
All'interno di questa directory pubblica, crea il file index.html per il front-end, che utilizzerà 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 localmente
In questa sezione, eseguirai l'applicazione localmente per verificare che ci sia un bug nell'applicazione quando l'utente tenta di salvare i dati.
Innanzitutto, devi 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 simulare l'identità dell'account utente creato in precedenza.
Utilizzo di ADC durante l'esecuzione locale
Se esegui Cloud Shell, la macchina virtuale Google Compute Engine è già in esecuzione. Le credenziali associate a questa macchina virtuale (come mostrato dall'esecuzione di gcloud auth list) verranno utilizzate automaticamente dalle credenziali predefinite dell'applicazione (ADC), pertanto non è necessario utilizzare il comando gcloud auth application-default login. Tuttavia, la tua identità dovrà comunque avere il ruolo Utente Datastore. Puoi passare direttamente alla sezione Esegui l'app localmente.
Tuttavia, se esegui l'operazione sul terminale locale (ovvero non in Cloud Shell), devi utilizzare le Credenziali predefinite dell'applicazione per l'autenticazione alle API di Google. Puoi 1) accedere utilizzando le tue credenziali (a condizione che tu disponga del ruolo Utente Datastore) oppure 2) accedere simulando l'identità del service account utilizzato in questo codelab.
Opzione 1: utilizzo delle credenziali per le Credenziali predefinite dell'applicazione
Se vuoi utilizzare le tue credenziali, puoi prima eseguire gcloud auth list per verificare la tua autenticazione in gcloud. Successivamente, potrebbe essere necessario concedere all'identità il ruolo Utente Vertex AI. Se la tua identità ha il ruolo Proprietario, disponi già di questo ruolo utente Datastore User. 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
Quindi esegui questo comando
gcloud auth application-default login
Opzione 2: simulare l'identità di un service account per ADC
Se vuoi utilizzare il service account creato in questo codelab, il tuo account utente deve disporre del ruolo Creatore token service account. Puoi ottenere questo ruolo eseguendo il seguente comando:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Successivamente, esegui 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
Successivamente, assicurati di trovarti nella directory principale cloud-run-github-cd-demo del codelab.
cd .. && pwd
Ora installerai le dipendenze.
npm install
Infine, puoi avviare l'app eseguendo il seguente script. Questo script genererà anche il file output.css da tailwindCSS.
npm run dev
Ora apri il browser web all'indirizzo http://localhost:8080. Se ti trovi in Cloud Shell, puoi aprire il sito web aprendo il pulsante Anteprima web e selezionando Anteprima porta 8080.

Inserisci il testo per i campi di input nome e città e fai clic su Salva. Poi aggiorna la pagina. Noterai che il campo della città non è stato mantenuto. Correggerai questo bug nella sezione successiva.
Interrompi l'esecuzione locale dell'app express (ad es. Ctrl^c su macOS).
8. Creare un repository GitHub
Nella directory locale, crea un nuovo repository con main come nome del branch predefinito.
git init git branch -M main
Esegui il commit della codebase attuale che contiene il bug. Correggerai il bug dopo aver configurato il deployment continuo.
git add . git commit -m "first commit for express application"
Vai su GitHub e crea un repository vuoto privato o pubblico. Questo codelab consiglia di denominare il repository cloud-run-auto-deploy-codelab Per creare un repository vuoto, lascia tutte le impostazioni predefinite deselezionate o impostate su Nessuno in modo che non ci siano contenuti nel repository per impostazione predefinita al momento della creazione, ad esempio:

Se hai completato correttamente questo passaggio, nella pagina del repository vuoto vedrai le seguenti istruzioni:

Segui le istruzioni per eseguire il push di un repository esistente dalla riga di comando eseguendo i seguenti comandi:
Per prima cosa, aggiungi il repository remoto eseguendo
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
quindi esegui il push del ramo principale nel repository upstream.
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 origine
- Seleziona GitHub come provider di repository
- Fai clic su Gestisci repository connessi per configurare l'accesso di Cloud Build al repository.
- Seleziona il repository e fai clic su Avanti.
- In Configurazione build
- Lascia Branch come ^main$
- Per Tipo di build, seleziona Go, Node.js, Python, Java, .NET Core, Ruby o PHP tramite i buildpack Google Cloud
- Lascia la directory contesto della build come
/ - Fai clic su Salva.
- Nella sezione Autenticazione
- Fai clic su Consenti chiamate non autenticate.
- In Container, volumi, networking, sicurezza
- Nella scheda Sicurezza, seleziona il service account che hai creato in un passaggio precedente, ad esempio
Cloud Run access to Firestore.
- Nella scheda Sicurezza, seleziona il service account che hai creato in un passaggio precedente, ad esempio
- Fai clic su CREA.
Verrà eseguito il deployment del servizio Cloud Run contenente il bug che correggerai nella sezione successiva.
10. Correggi il bug
Correggi il bug nel codice
Nell'editor di Cloud Shell, apri il file app.js e vai al commento che dice //TODO: fix this bug
modifica la seguente riga 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. Vedrai che i dati della città appena inseriti sono stati 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"
e poi eseguine il push nel repository upstream su GitHub.
git push origin main
Cloud Build eseguirà automaticamente il deployment delle modifiche. Puoi andare alla console Cloud per il tuo servizio Cloud Run per monitorare le modifiche al deployment.
Verifica la correzione in produzione
Quando Cloud Console per il servizio Cloud Run mostra che una seconda revisione ora gestisce 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 vengano mantenuti dopo l'aggiornamento della pagina.
11. Complimenti!
Congratulazioni per aver completato il codelab.
Ti consigliamo di consultare la documentazione relativa a Cloud Run e al deployment continuo da git.
Argomenti trattati
- Scrivere un'applicazione web Express con l'editor di Cloud Shell
- Collega il tuo account GitHub a Google Cloud per i 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 richiamati inavvertitamente più volte rispetto all'allocazione mensile di chiamate di Cloud Run nel livello senza costi), puoi eliminare Cloud Run o 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 alla pagina 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 cambiare progetto in Cloud SDK. Puoi visualizzare l'elenco di tutti i progetti disponibili eseguendo gcloud projects list.