איך לפרוס באופן אוטומטי את השינויים מ-GitHub ל-Cloud Run באמצעות Cloud Build

1. מבוא

סקירה כללית

ב-Codelab הזה, תגדירו את Cloud Run לפיתוח ופריסה אוטומטית של גרסאות חדשות של האפליקציה שלכם בכל פעם שדוחפים שינויים בקוד המקור למאגר ב-GitHub.

אפליקציית ההדגמה הזו שומרת נתוני משתמש ב-Firestore, אבל רק חלק מהנתונים נשמרים כמו שצריך. כאן מגדירים פריסות רציפות, כך שכאשר דוחפים תיקון באגים למאגר שלכם ב-GitHub, התיקון יהיה זמין באופן אוטומטי בגרסה חדשה.

מה תלמדו

  • כתיבה של אפליקציית אינטרנט Express באמצעות Cloud Shell Editor
  • חיבור חשבון GitHub ל-Google Cloud כדי לאפשר פריסות רציפות
  • פריסה אוטומטית של האפליקציה ב-Cloud Run
  • הסבר איך משתמשים ב-HTMX וב-TailwindCSS

2. הגדרה ודרישות

דרישות מוקדמות

הפעלת Cloud Shell

  1. במסוף Cloud, לוחצים על Activate Cloud Shell d1264ca30785e435.png.

cb81e7c8e34bc8d.png

אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים שמתוארת בו. אם הוצג לכם מסך ביניים, לוחצים על המשך.

d95252b003979716.png

ההקצאה וההתחברות ל-Cloud Shell נמשכת כמה דקות.

7833d5e1c5d18f54.png

במכונה הווירטואלית הזו נמצאים כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. אם לא את כולן, ניתן לבצע חלק גדול מהעבודה ב-Codelab הזה באמצעות דפדפן.

אחרי ההתחברות ל-Cloud Shell, אתם אמורים לראות שהפרויקט מאומת ושהפרויקט מוגדר לפי מזהה הפרויקט שלכם.

  1. מריצים את הפקודה הבאה ב-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`
  1. מריצים את הפקודה הבאה ב-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

  1. במסוף Firebase, לוחצים על הוספת פרויקט.
  2. הזן <YOUR_PROJECT_ID> כדי להוסיף את Firebase לאחד מהפרויקטים הקיימים שלכם ב-Google Cloud
  3. אם מופיעה בקשה, קוראים את התנאים של Firebase ומאשרים אותם.
  4. לוחצים על המשך.
  5. לוחצים על Confirm Plan (אישור התוכנית) כדי לאשר את תוכנית החיוב של Firebase.
  6. לא חובה להפעיל את Google Analytics ב-Codelab הזה.
  7. לוחצים על הוספת Firebase.
  8. לאחר יצירת הפרויקט, לוחצים על Continue.
  9. בתפריט Build, לוחצים על Firestore repository.
  10. לוחצים על Create dataset.
  11. בוחרים את האזור שלכם מהתפריט הנפתח מיקום ולוחצים על הבא.
  12. משתמשים באפשרות ברירת המחדל 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.

תצוגה מקדימה לאינטרנט – תצוגה מקדימה בלחצן ליציאה 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 כך שאף תוכן לא יהיה במאגר כברירת מחדל בזמן יצירה, לדוגמה.

הגדרות ברירת המחדל של GitHub

אם השלמתם את השלב הזה בצורה נכונה, יופיעו ההוראות הבאות בדף הריק של המאגר:

הוראות ריקות ממאגר GitHub

כדי לבצע את ההוראות לדחיפת מאגר קיים משורת הפקודה, מריצים את הפקודות הבאות:

קודם כול, מוסיפים את המאגר המרוחק על ידי הרצה של

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.