1. Introduction
Présentation
Dans cet atelier de programmation, vous allez configurer Cloud Run pour créer et déployer automatiquement de nouvelles versions de votre application chaque fois que vous transférez les modifications du code source vers un dépôt GitHub.
Cette application de démonstration enregistre les données utilisateur dans Firestore, mais seule une partie des données est correctement enregistrée. Vous configurerez des déploiements continus de sorte que lorsque vous transférerez un correctif de bug dans votre dépôt GitHub, le correctif sera automatiquement disponible dans une nouvelle révision.
Points abordés
- Écrire une application Web Express avec l'éditeur Cloud Shell
- Connectez votre compte GitHub à Google Cloud pour les déploiements continus
- Déployer automatiquement votre application sur Cloud Run
- En savoir plus sur l'utilisation de HTMX et de TailwindCSS
2. Préparation
Prérequis
- Vous possédez un compte GitHub, et vous savez comment créer et transférer du code vers des dépôts.
- Vous êtes connecté à la console Cloud.
- Vous avez déjà déployé un service Cloud Run. Par exemple, vous pouvez suivre le guide de démarrage rapide pour déployer un service Web depuis le code source.
Activer Cloud Shell
- Dans Cloud Console, cliquez sur Activer Cloud Shell .
Si vous démarrez Cloud Shell pour la première fois, un écran intermédiaire vous explique de quoi il s'agit. Si un écran intermédiaire s'est affiché, cliquez sur Continuer.
Le provisionnement et la connexion à Cloud Shell ne devraient pas prendre plus de quelques minutes.
Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute dans Google Cloud, ce qui améliore considérablement les performances du réseau et l'authentification. Une grande partie, voire la totalité, de votre travail dans cet atelier de programmation peut être effectué dans un navigateur.
Une fois connecté à Cloud Shell, vous êtes authentifié et le projet est défini sur votre ID de projet.
- Exécutez la commande suivante dans Cloud Shell pour vérifier que vous êtes authentifié :
gcloud auth list
Résultat de la commande
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Exécutez la commande suivante dans Cloud Shell pour vérifier que la commande gcloud connaît votre projet:
gcloud config list project
Résultat de la commande
[core] project = <PROJECT_ID>
Si vous obtenez un résultat différent, exécutez cette commande :
gcloud config set project <PROJECT_ID>
Résultat de la commande
Updated property [core/project].
3. Activer les API et définir des variables d'environnement
Activer les API
Cet atelier de programmation nécessite l'utilisation des API suivantes. Vous pouvez activer ces API en exécutant la commande suivante:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ firestore.googleapis.com \ iamcredentials.googleapis.com
Configurer des variables d'environnement
Vous pouvez définir les variables d'environnement qui seront utilisées tout au long de cet atelier de programmation.
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. Créer un compte de service
Ce compte de service permettra à Cloud Run d'appeler l'API Gemini Vertex AI. Ce compte de service sera également autorisé à lire et écrire dans Firestore, et à lire des secrets depuis Secret Manager.
Commencez par créer le compte de service en exécutant la commande suivante:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run access to Firestore"
Accordez maintenant au compte de service un accès en lecture et en écriture à Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
5. Créer et configurer un projet Firebase
- Dans la console Firebase, cliquez sur Ajouter un projet.
- Saisissez <YOUR_PROJECT_ID>. pour ajouter Firebase à l'un de vos projets Google Cloud existants
- Si vous y êtes invité, lisez et acceptez les Conditions d'utilisation de Firebase.
- Cliquez sur Continuer.
- Cliquez sur Confirmer le mode de facturation pour confirmer le mode de facturation Firebase.
- L'activation de Google Analytics pour cet atelier de programmation est facultative.
- Cliquez sur Ajouter Firebase :
- Une fois le projet créé, cliquez sur Continuer.
- Dans le menu Build (Compiler), cliquez sur Firestore Database (Base de données Firestore).
- Cliquez sur Créer une base de données.
- Choisissez votre région dans le menu déroulant Emplacement, puis cliquez sur Suivant.
- Utilisez l'option par défaut Start in production mode (Démarrer en mode production), puis cliquez sur Create (Créer).
6. Écrire l'application
Tout d'abord, créez un répertoire pour le code source et utilisez la commande cd pour y accéder.
mkdir cloud-run-github-cd-demo && cd $_
Ensuite, créez un fichier package.json
avec le contenu suivant:
{ "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" } }
Commencez par créer un fichier source app.js
avec le contenu ci-dessous. Ce fichier contient le point d'entrée du service et la logique principale de l'application.
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); });
Créer un fichier intitulé 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>`;
Créer un fichier input.css
pour tailwindCSS
@tailwind base; @tailwind components; @tailwind utilities;
Et créer le fichier tailwind.config.js
pour tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Créez ensuite un fichier .gitignore
.
node_modules/ npm-debug.log coverage/ package-lock.json .DS_Store
Maintenant, créez un répertoire public
.
mkdir public cd public
Dans ce répertoire public, créez le fichier index.html
du frontal, qui utilisera 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. Exécuter l'application en local
Dans cette section, vous allez exécuter l'application en local pour vérifier qu'elle présente un bug lorsque l'utilisateur tente d'enregistrer des données.
Tout d'abord, vous devez disposer du rôle d'utilisateur Datastore pour accéder à Firestore (si vous utilisez votre identité pour l'authentification, par exemple lorsque vous exécutez Cloud Shell, par exemple) ou vous pouvez emprunter l'identité du compte utilisateur créé précédemment.
Utiliser ADC en cas d'exécution locale
Si vous exécutez Cloud Shell, vous exécutez déjà le projet sur une machine virtuelle Google Compute Engine. Vos identifiants associés à cette machine virtuelle (comme indiqué en exécutant gcloud auth list
) seront automatiquement utilisés par les identifiants par défaut de l'application (ADC). Il n'est donc pas nécessaire d'exécuter la commande gcloud auth application-default login
. Toutefois, votre identité devra toujours disposer du rôle d'utilisateur Datastore. Vous pouvez passer à la section Exécuter l'application en local.
Toutefois, si vous exécutez l'application sur votre terminal local (c'est-à-dire sans passer par Cloud Shell), vous devez utiliser les identifiants par défaut de l'application pour vous authentifier auprès des API Google. Vous pouvez 1) vous connecter à l'aide de vos identifiants (à condition de disposer du rôle d'utilisateur Datastore) ou 2) vous connecter en empruntant l'identité du compte de service utilisé dans cet atelier de programmation.
Option 1 : Utiliser vos identifiants pour l'ADC
Si vous souhaitez utiliser vos identifiants, vous pouvez d'abord exécuter gcloud auth list
pour vérifier votre authentification dans gcloud. Ensuite, vous devrez peut-être attribuer à votre identité le rôle "Utilisateur Vertex AI". Si votre identité dispose du rôle Propriétaire, vous disposez déjà de ce rôle utilisateur d'utilisateur Datastore. Si ce n'est pas le cas, vous pouvez exécuter cette commande pour attribuer à votre identité les rôles d'utilisateur Vertex AI et d'utilisateur Datastore.
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
Exécutez ensuite la commande suivante :
gcloud auth application-default login
Option 2 : Emprunter l'identité d'un compte de service pour ADC
Si vous souhaitez utiliser le compte de service créé dans cet atelier de programmation, votre compte utilisateur doit disposer du rôle Créateur de jetons du compte de service. Vous pouvez obtenir ce rôle en exécutant la commande suivante:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Ensuite, vous exécuterez la commande suivante pour utiliser ADC avec le compte de service
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Exécuter l'application en local
Vérifiez ensuite que vous êtes bien dans le répertoire racine cloud-run-github-cd-demo
de votre atelier de programmation.
cd .. && pwd
Maintenant, vous allez installer des dépendances.
npm install
Enfin, vous pouvez démarrer l'application en exécutant le script suivant. Ce script générera également le fichier output.css à partir de tailwindCSS.
npm run dev
À présent, accédez à http://localhost:8080 dans votre navigateur Web. Si vous utilisez Cloud Shell, vous pouvez ouvrir le site Web en ouvrant le bouton "Aperçu sur le Web", puis en sélectionnant "Prévisualiser le port 8080".
Saisissez le texte dans les champs de saisie du nom et de la ville, puis cliquez sur "Enregistrer". Actualisez ensuite la page. Vous remarquerez que le champ "city" n'est pas persistant. Vous corrigerez ce bug dans la section suivante.
Arrêtez l'exécution locale de l'application Express (par exemple, Ctrl^c sous macOS).
8. Créer un dépôt GitHub
Dans votre répertoire local, créez un dépôt avec "main" comme nom de branche par défaut.
git init git branch -M main
Validez le codebase actuel contenant le bug. Vous corrigerez le bug une fois le déploiement continu configuré.
git add . git commit -m "first commit for express application"
Accédez à GitHub et créez un dépôt vide, privé ou public. Cet atelier de programmation recommande de nommer votre dépôt cloud-run-auto-deploy-codelab
.Pour créer un dépôt vide, vous devez laisser tous les paramètres par défaut décochés ou les définir sur "aucun" afin qu'aucun contenu ne soit dans le dépôt par défaut lors de sa création.
Si vous avez correctement effectué cette étape, les instructions suivantes s'afficheront sur la page du dépôt vide:
Suivez les instructions pour envoyer un dépôt existant à partir de la ligne de commande en exécutant les commandes suivantes:
Tout d'abord, ajoutez le dépôt distant en exécutant
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
puis transférez la branche principale vers le dépôt en amont.
git push -u origin main
9. Configurer le déploiement continu
Maintenant que vous avez du code dans GitHub, vous pouvez configurer le déploiement continu. Accédez à la console Cloud pour Cloud Run.
- Cliquez sur "Créer un service".
- Cliquez sur Déployer en continu depuis un dépôt.
- Cliquez sur CONFIGURER CLOUD Build.
- Sous "Source Repository" (Dépôt source) :
- Sélectionner GitHub comme fournisseur de dépôts
- Cliquez sur Gérer les dépôts connectés pour configurer l'accès de Cloud Build au dépôt.
- Sélectionnez votre dépôt, puis cliquez sur Suivant.
- Sous "Configuration de la compilation"
- Laisser la branche en tant que ^main$
- Dans "Type de compilation", sélectionnez Go, Node.js, Python, Java, .NET Core, Ruby ou PHP via les buildpacks de Google Cloud.
- Laissez le répertoire de contexte de compilation sur
/
- Cliquez sur Enregistrer.
- Sous "Authentification"
- Cliquez sur Allow unauthenticated invocations (Autoriser les appels non authentifiés).
- Sous Conteneur(s), Volumes, Mise en réseau et Sécurité
- Dans l'onglet "Sécurité", sélectionnez le compte de service que vous avez créé précédemment, par exemple
Cloud Run access to Firestore
- Dans l'onglet "Sécurité", sélectionnez le compte de service que vous avez créé précédemment, par exemple
- Cliquez sur CRÉER.
Le service Cloud Run contenant le bug que vous allez corriger dans la section suivante sera déployé.
10. Corriger le bug
Corriger le bug dans le code
Dans l'éditeur Cloud Shell, ouvrez le fichier app.js
et accédez au commentaire //TODO: fix this bug
.
remplacez la ligne suivante
//TODO: fix this bug await doc.set({ name: name });
pour
//fixed town bug await doc.set({ name: name, town: town });
Vérifiez le correctif en exécutant
npm run start
et ouvrez votre navigateur Web. Enregistrez à nouveau les données pour la ville, puis actualisez la page. Lorsque vous actualisez la page, les nouvelles données sur les villes que vous venez de saisir ont été conservées correctement.
Maintenant que vous avez vérifié votre correction, vous êtes prêt à la déployer. Commencez par valider la correction.
git add . git commit -m "fixed town bug"
puis les transférer vers le dépôt en amont sur GitHub.
git push origin main
Cloud Build déploiera automatiquement vos modifications. Vous pouvez accéder à la console Cloud de votre service Cloud Run pour surveiller les modifications apportées au déploiement.
Vérifier le correctif en production
Une fois que la console Cloud de votre service Cloud Run indique qu'une deuxième révision diffuse désormais 100% du trafic (par exemple, https://console.cloud.google.com/run/detail/<VOTRE_RÉGION>/<NOM_DE_VOTRE_SERVICE>/revisions, vous pouvez ouvrir l'URL du service Cloud Run dans votre navigateur et vérifier que les nouvelles données de ville saisies sont conservées après avoir actualisé la page.
11. Félicitations !
Félicitations ! Vous avez terminé cet atelier de programmation.
Nous vous recommandons de consulter la documentation concernant Cloud Run et le déploiement continu à partir de git.
Points abordés
- Écrire une application Web Express avec l'éditeur Cloud Shell
- Connectez votre compte GitHub à Google Cloud pour les déploiements continus
- Déployer automatiquement votre application sur Cloud Run
- En savoir plus sur l'utilisation de HTMX et de TailwindCSS
12. Effectuer un nettoyage
Pour éviter des frais accidentels (par exemple, si les services Cloud Run sont invoqués plus de fois que l'allocation mensuelle des appels Cloud Run dans le niveau sans frais), vous pouvez supprimer Cloud Run ou le projet créé à l'étape 2.
Pour supprimer le service Cloud Run, accédez à la console Cloud Run à l'adresse https://console.cloud.google.com/run et supprimez le service Cloud Run que vous avez créé dans cet atelier de programmation, par exemple supprimez le service cloud-run-auto-deploy-codelab
.
Si vous choisissez de supprimer l'intégralité du projet, vous pouvez accéder à https://console.cloud.google.com/cloud-resource-manager, sélectionner le projet que vous avez créé à l'étape 2, puis cliquer sur "Supprimer". Si vous supprimez le projet, vous devrez le modifier dans Cloud SDK. Vous pouvez afficher la liste de tous les projets disponibles en exécutant gcloud projects list
.