1. מבוא
סקירה כללית
ב-Codelab הזה, תגדירו את Cloud Run לפיתוח ופריסה אוטומטית של גרסאות חדשות של האפליקציה שלכם בכל פעם שדוחפים שינויים בקוד המקור למאגר ב-GitHub.
אפליקציית ההדגמה הזו שומרת נתוני משתמש ב-Firestore, אבל רק חלק מהנתונים נשמרים כמו שצריך. כאן מגדירים פריסות רציפות, כך שכאשר דוחפים תיקון באגים למאגר שלכם ב-GitHub, התיקון יהיה זמין באופן אוטומטי בגרסה חדשה.
מה תלמדו
- כתיבה של אפליקציית אינטרנט Express באמצעות Cloud Shell Editor
- חיבור חשבון GitHub ל-Google Cloud כדי לאפשר פריסות רציפות
- פריסה אוטומטית של האפליקציה ב-Cloud Run
- הסבר איך משתמשים ב-HTMX וב-TailwindCSS
2. הגדרה ודרישות
דרישות מוקדמות
- יש לכם חשבון GitHub ואתם יודעים איך ליצור ולדחוף קוד למאגרים.
- אתם מחוברים למסוף Cloud.
- פרסתם בעבר שירות של Cloud Run. לדוגמה, אפשר להיעזר במדריך למתחילים לפריסת שירות אינטרנט מקוד מקור כדי להתחיל בעבודה.
הפעלת Cloud Shell
- במסוף Cloud, לוחצים על Activate Cloud Shell .
אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים שמתוארת בו. אם הוצג לכם מסך ביניים, לוחצים על המשך.
ההקצאה וההתחברות ל-Cloud Shell נמשכת כמה דקות.
במכונה הווירטואלית הזו נמצאים כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. אם לא את כולן, ניתן לבצע חלק גדול מהעבודה ב-Codelab הזה באמצעות דפדפן.
אחרי ההתחברות ל-Cloud Shell, אתם אמורים לראות שהפרויקט מאומת ושהפרויקט מוגדר לפי מזהה הפרויקט שלכם.
- מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שהאימות בוצע:
gcloud auth list
פלט הפקודה
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שהפקודה ב-gcloud יודעת על הפרויקט שלכם:
gcloud config list project
פלט הפקודה
[core] project = <PROJECT_ID>
אם היא לא נמצאת שם, תוכלו להגדיר אותה באמצעות הפקודה הבאה:
gcloud config set project <PROJECT_ID>
פלט הפקודה
Updated property [core/project].
3. הפעלת ממשקי API והגדרת משתני סביבה
הפעלת ממשקי API
ה-Codelab הזה מחייב שימוש בממשקי ה-API הבאים. כדי להפעיל את ממשקי ה-API האלה, מריצים את הפקודה הבאה:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ firestore.googleapis.com \ iamcredentials.googleapis.com
הגדרה של משתני סביבה
אתם יכולים להגדיר משתני סביבה שישמשו ב-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. יצירה של חשבון שירות
חשבון השירות הזה ישמש את Cloud Run כדי לקרוא ל-Vertex AI Gemini API. לחשבון השירות הזה יהיו גם הרשאות לקרוא ולכתוב ב-Firestore ולקרוא סודות מ-Secret Manager.
קודם כול, יוצרים את חשבון השירות באמצעות הפקודה הבאה:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run access to Firestore"
עכשיו, מעניקים לחשבון השירות גישת קריאה וכתיבה ל-Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
5. יצירה והגדרה של פרויקט Firebase
- במסוף Firebase, לוחצים על הוספת פרויקט.
- הזן <YOUR_PROJECT_ID> כדי להוסיף את Firebase לאחד מהפרויקטים הקיימים שלכם ב-Google Cloud
- אם מופיעה בקשה, קוראים את התנאים של Firebase ומאשרים אותם.
- לוחצים על המשך.
- לוחצים על Confirm Plan (אישור התוכנית) כדי לאשר את תוכנית החיוב של Firebase.
- לא חובה להפעיל את Google Analytics ב-Codelab הזה.
- לוחצים על הוספת Firebase.
- לאחר יצירת הפרויקט, לוחצים על Continue.
- בתפריט Build, לוחצים על Firestore repository.
- לוחצים על Create dataset.
- בוחרים את האזור שלכם מהתפריט הנפתח מיקום ולוחצים על הבא.
- משתמשים באפשרות ברירת המחדל Start inProduction mode ואז לוחצים על Create.
6. כתיבת האפליקציה
קודם כל, יוצרים ספרייה עבור קוד המקור וה-cd בספרייה הזו.
mkdir cloud-run-github-cd-demo && cd $_
לאחר מכן, יוצרים קובץ package.json
עם התוכן הבא:
{ "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" } }
קודם כול, יוצרים קובץ מקור app.js
עם התוכן שלמטה. הקובץ הזה מכיל את נקודת הכניסה לשירות ומכיל את הלוגיקה העיקרית של האפליקציה.
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); });
יצירת קובץ בשם 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>`;
יצירת קובץ input.css
ל-tailwindCSS
@tailwind base; @tailwind components; @tailwind utilities;
ויוצרים את הקובץ tailwind.config.js
ל-tailwindCSS
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
ויוצרים קובץ .gitignore
.
node_modules/ npm-debug.log coverage/ package-lock.json .DS_Store
בשלב הזה צריך ליצור ספריית public
חדשה.
mkdir public cd public
ובספרייה הציבורית הזו, יוצרים את הקובץ index.html
עבור ממשק הקצה, שישתמש ב-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. הרצה מקומית של האפליקציה
בקטע הזה, תפעילו את האפליקציה באופן מקומי כדי לוודא שקיים באג באפליקציה כשהמשתמש מנסה לשמור נתונים.
בשלב הראשון, תצטרכו את התפקיד 'משתמש ב-Datastore' כדי לגשת ל-Firestore (אם אתם משתמשים בזהות שלכם לצורך אימות, למשל אם אתם משתמשים ב-Cloud Shell), או שתוכלו להתחזות לחשבון המשתמש שנוצר קודם לכן.
שימוש ב-ADC כשמריצים באופן מקומי
אם אתם משתמשים ב-Cloud Shell, סימן שאתם כבר משתמשים במכונה וירטואלית של Google Compute Engine. Application Default Credentials (ADC) יתבסס באופן אוטומטי על פרטי הכניסה שמשויכים למכונה הווירטואלית הזו (כפי שמוצג בהרצה של gcloud auth list
), ולכן לא צריך להשתמש בפקודה gcloud auth application-default login
. עם זאת, הזהות שלכם עדיין תצטרך את התפקיד 'משתמש ב-Datastore'. אפשר לדלג למטה לקטע הפעלת האפליקציה באופן מקומי.
עם זאת, אם אתם מריצים אותו בטרמינל המקומי (כלומר לא ב-Cloud Shell), תצטרכו להשתמש ב-Application Default Credentials כדי לבצע אימות ל-Google APIs. 1) להתחבר באמצעות פרטי הכניסה שלך (בתנאי שהתפקיד 'משתמש ב-Datastore') או 2) אפשר להתחבר באמצעות התחזות לחשבון השירות שמשמש ב-Codelab הזה.
אפשרות 1) שימוש בפרטי הכניסה ל-ADC
אם תרצו להשתמש בפרטי הכניסה שלכם, תוכלו קודם להריץ את gcloud auth list
כדי לאמת את תהליך האימות ב-gcloud. בשלב הבא, ייתכן שתצטרכו להעניק לזהות שלכם את התפקיד Vertex AI User. אם לזהות שלכם יש את התפקיד 'בעלים', כבר יש לכם את תפקיד המשתמש הזה ב-Datastore. אם לא, תוכלו להריץ את הפקודה הזו כדי להקצות לזהות שלכם את תפקיד המשתמש ב-Vertex AI ואת התפקיד 'משתמש ב-Datastore'.
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
לאחר מכן מריצים את הפקודה הבאה
gcloud auth application-default login
אפשרות 2) התחזות לחשבון שירות ל-ADC
אם אתם רוצים להשתמש בחשבון השירות שנוצר ב-Codelab הזה, לחשבון המשתמש שלכם צריך להיות התפקיד 'יצירת אסימונים בחשבון שירות'. כדי לקבל את התפקיד הזה, מריצים את הפקודה הבאה:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
בשלב הבא, מריצים את הפקודה הבאה כדי להשתמש ב-ADC עם חשבון השירות
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
הפעלה מקומית של האפליקציה
עכשיו צריך לוודא שנמצאים בתיקיית השורש cloud-run-github-cd-demo
של ה-Codelab.
cd .. && pwd
עכשיו תתקין את יחסי התלות.
npm install
לסיום, אפשר להפעיל את האפליקציה על ידי הרצת הסקריפט הבא. הסקריפט הזה גם יפיק את הקובץ export.css מ-tailwindCSS.
npm run dev
עכשיו פותחים את דפדפן האינטרנט בכתובת http://localhost:8080. אם אתם משתמשים ב-Cloud Shell, אתם יכולים לפתוח את האתר על ידי פתיחת לחצן Web Preview ובחירת האפשרות Preview Port 8080.
מזינים טקסט לשדות הקלט של השם והעיר ולוחצים על 'שמירה'. אחר כך צריך לרענן את הדף. תוכלו לראות ששדה העיר לא נותר. הבאג הזה יתוקן בקטע הבא.
מפסיקים את ההפעלה של האפליקציה האקספרס באופן מקומי (למשל, Ctrl^c ב-MacOS).
8. יצירת מאגר נתונים ב-GitHub
בספרייה המקומית, יוצרים מאגר נתונים חדש עם שם ההסתעפות הראשי שמוגדר כברירת מחדל.
git init git branch -M main
שומרים את ה-codebase הנוכחי שמכיל את הבאג. הבאג יתוקן לאחר הגדרת הפריסה הרציפה.
git add . git commit -m "first commit for express application"
נכנסים ל-GitHub ויוצרים מאגר ריק שהוא פרטי שלכם או ציבורי. המלצת ה-Codelab הזו היא לתת למאגר שלך שם cloud-run-auto-deploy-codelab
.כדי ליצור מאגר ריק, צריך להשאיר את כל הגדרות ברירת המחדל לא מסומנות או כ-None כך שאף תוכן לא יהיה במאגר כברירת מחדל בזמן יצירה, לדוגמה.
אם השלמתם את השלב הזה בצורה נכונה, יופיעו ההוראות הבאות בדף הריק של המאגר:
כדי לבצע את ההוראות לדחיפת מאגר קיים משורת הפקודה, מריצים את הפקודות הבאות:
קודם כול, מוסיפים את המאגר המרוחק על ידי הרצה של
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
לאחר מכן מעבירים את ההסתעפות הראשית למאגר ב-upstream.
git push -u origin main
9. הגדרה של פריסה רציפה (CD)
עכשיו, כשיש לכם קוד ב-GitHub, אתם יכולים להגדיר פריסה רציפה (CD). נכנסים אל Cloud Console for Cloud Run.
- לוחצים על Create a Service (יצירת שירות).
- לוחצים על Continuely Deploy from a repository.
- לוחצים על הגדרת Cloud BUILD.
- בקטע 'מאגר מקור'
- בחירת GitHub בתור ספק המאגר
- לוחצים על ניהול מאגרים מקושרים כדי להגדיר גישה ל-Cloud Build למאגר.
- בוחרים את המאגר ולוחצים על Next.
- בקטע 'הגדרות build'
- צריך להשאיר את Branch בתור ^main$
- בסוג Build, בוחרים באפשרות Go, Node.js, Python, Java, .NET Core, Ruby או PHP דרך ה-buildpacks של Google Cloud.
- יציאה מספריית ההקשר של build בתור
/
- לוחצים על שמירה.
- בקטע 'אימות'
- לוחצים על יש לאפשר הפעלות לא מאומתות.
- בקטע 'קונטיינרים', נפחי אחסון, רשתות, אבטחה
- בכרטיסייה 'אבטחה', בוחרים את חשבון השירות שיצרתם בשלב קודם, למשל,
Cloud Run access to Firestore
- בכרטיסייה 'אבטחה', בוחרים את חשבון השירות שיצרתם בשלב קודם, למשל,
- לוחצים על יצירה.
הפעולה הזו תגרום לפריסת שירות Cloud Run שמכיל את הבאג שיתקן בקטע הבא.
10. תיקון הבאג
תיקון הבאג בקוד
ב-Cloud Shell Editor, פותחים את הקובץ app.js
ועוברים לתגובה '//TODO: fix this bug
'.
שנה את השורה הבאה מ-
//TODO: fix this bug await doc.set({ name: name });
עד
//fixed town bug await doc.set({ name: name, town: town });
כדי לאמת את התיקון, יש להריץ את
npm run start
ופותחים את דפדפן האינטרנט. שומרים שוב את הנתונים של העיר ומעדכנים אותם. תוכלו לראות שהנתונים של העיר החדשה שהוזנו נשמרו באופן תקין לאחר הרענון.
לאחר שאימתתם את התיקון, אתם מוכנים לפרוס אותו. קודם מבצעים את התיקון.
git add . git commit -m "fixed town bug"
ואז מעבירים אותו למאגר ה-upstream ב-GitHub.
git push origin main
הפריסה של השינויים תתבצע באופן אוטומטי ב-Cloud Build. אתם יכולים להיכנס למסוף Cloud של שירות Cloud Run כדי לעקוב אחרי השינויים בפריסה.
אימות התיקון בסביבת הייצור
ברגע שהגרסה השנייה במסוף Cloud לשירות Cloud Run תוצג, היא תציג 100% מתנועת הגולשים, למשל. https://console.cloud.google.com/run/detail/<YOUR_REGION>/<YOUR_SERVICE_NAME>/revisions, ניתן לפתוח את כתובת ה-URL של שירות Cloud Run בדפדפן ולוודא שנתוני העיר החדשים שהוזנו נשמרים לאחר רענון הדף.
11. מעולה!
מזל טוב, השלמת את Codelab!
מומלץ לעיין במסמכים בנושא Cloud Run ופריסה רציפה (CD) מ-Git.
אילו נושאים דיברנו?
- כתיבה של אפליקציית אינטרנט Express באמצעות Cloud Shell Editor
- חיבור חשבון GitHub ל-Google Cloud כדי לאפשר פריסות רציפות
- פריסה אוטומטית של האפליקציה ב-Cloud Run
- הסבר איך משתמשים ב-HTMX וב-TailwindCSS
12. הסרת המשאבים
כדי להימנע מחיובים לא מכוונים (לדוגמה, אם שירותי Cloud Run מופעלים בטעות יותר פעמים מהקצאת ההפעלה החודשית של Cloud Run בתוכנית ללא תשלום), אפשר למחוק את Cloud Run או למחוק את הפרויקט שיצרתם בשלב 2.
כדי למחוק את שירות Cloud Run, נכנסים למסוף Cloud Run Cloud בכתובת https://console.cloud.google.com/run ומוחקים את שירות Cloud Run שיצרתם ב-Codelab הזה, למשל. למחוק את השירות cloud-run-auto-deploy-codelab
.
אם בוחרים למחוק את הפרויקט כולו, נכנסים לכתובת https://console.cloud.google.com/cloud-resource-manager, בוחרים את הפרויקט שיצרתם בשלב 2 ובוחרים באפשרות 'מחיקה'. אם תמחקו את הפרויקט, יהיה צריך לבצע שינויים בפרויקטים ב-Cloud SDK. כדי להציג את הרשימה של כל הפרויקטים הזמינים, אפשר להריץ את הפקודה gcloud projects list
.