איך לפרוס באופן אוטומטי את השינויים מ-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 Console, לוחצים על 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. כדי לאשר את תוכנית החיוב ב-Firebase, לוחצים על אישור התוכנית.
  6. הפעלת Google Analytics עבור ה-codelab הזה היא אופציונלית.
  7. לוחצים על הוספת Firebase.
  8. אחרי שהפרויקט נוצר, לוחצים על המשך.
  9. בתפריט Build (פיתוח), לוחצים על Firestore database (מסד נתונים של Firestore).
  10. לוחצים על יצירת מסד נתונים.
  11. בוחרים את האזור מהתפריט הנפתח מיקום ולוחצים על הבא.
  12. משתמשים באפשרות ברירת המחדל התחלה במצב ייצור ולוחצים על יצירה.

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 User כדי לגשת ל-Firestore (אם אתם משתמשים בזהות שלכם לאימות, למשל אם אתם מריצים את הפקודה ב-Cloud Shell), או שתוכלו להתחזות לחשבון המשתמש שיצרתם קודם.

שימוש ב-ADC בזמן הרצה מקומית

אם אתם מריצים את הפקודה ב-Cloud Shell, אתם כבר מריצים אותה במכונה וירטואלית של Google Compute Engine. פרטי הכניסה שמשויכים למכונה הווירטואלית הזו (כפי שמוצג בהרצת הפקודה gcloud auth list) ישמשו באופן אוטומטי את Application Default Credentials (ADC), כך שלא צריך להשתמש בפקודה gcloud auth application-default login. עם זאת, עדיין צריך להקצות לזהות שלכם את התפקיד Datastore User. אפשר לדלג לקטע הפעלת האפליקציה באופן מקומי.

אבל אם אתם מריצים את הפקודה במסוף המקומי (כלומר לא ב-Cloud Shell), תצטרכו להשתמש ב-Application Default Credentials כדי לבצע אימות ל-Google APIs. יש שתי אפשרויות: 1) להתחבר באמצעות פרטי הכניסה (אם יש לכם את התפקיד Datastore User) או 2) להתחבר באמצעות התחזות לחשבון השירות שבו נעשה שימוש ב-codelab הזה.

אפשרות 1) שימוש בפרטי הכניסה שלכם ל-ADC

אם רוצים להשתמש באמצעי האימות, אפשר קודם להריץ את הפקודה gcloud auth list כדי לבדוק איך מתבצע האימות ב-gcloud. לאחר מכן, יכול להיות שתצטרכו להקצות לזהות שלכם את התפקיד 'משתמש ב-Vertex AI'. אם לישות שלכם יש את התפקיד Owner, כבר יש לה את תפקיד המשתמש Datastore User. אם לא, אפשר להריץ את הפקודה הזו כדי להעניק לזהות שלכם את תפקיד המשתמש ב-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 הזה, לחשבון המשתמש צריך להיות מוקצה התפקיד Service Account Token Creator. כדי לקבל את התפקיד הזה, מריצים את הפקודה הבאה:

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

לבסוף, מריצים את הסקריפט הבא כדי להפעיל את האפליקציה. הסקריפט הזה ייצור גם את הקובץ output.css מ-tailwindCSS.

npm run dev

עכשיו פותחים את דפדפן האינטרנט בכתובת http://localhost:8080. אם אתם ב-Cloud Shell, אתם יכולים לפתוח את האתר באמצעות פתיחת הלחצן 'תצוגה מקדימה של אתר' ובחירה באפשרות 'תצוגה מקדימה של יציאה 8080'.

תצוגה מקדימה באינטרנט – לחצן לתצוגה מקדימה ביציאה 8080

מזינים טקסט בשדות הקלט של השם והעיר ולוחצים על 'שמירה'. לאחר מכן מרעננים את הדף. תראו ששדה העיר לא נשמר. בקטע הבא נסביר איך לפתור את הבאג הזה.

מפסיקים את ההרצה המקומית של אפליקציית Express (למשל, Ctrl^c ב-MacOS).

8. יצירת מאגר ב-GitHub

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

git init
git branch -M main

מבצעים קומיט של בסיס הקוד הנוכחי שמכיל את הבאג. תתקנו את הבאג אחרי שתגדירו פריסה רציפה.

git add .
git commit -m "first commit for express application"

עוברים אל GitHub ויוצרים מאגר ריק שהוא פרטי או ציבורי. ב-codelab הזה מומלץ לתת למאגר השם cloud-run-auto-deploy-codelab כדי ליצור מאגר ריק, משאירים את כל הגדרות ברירת המחדל לא מסומנות או מגדירים אותן כ'ללא', כך שלא יהיה תוכן במאגר כברירת מחדל כשהוא נוצר, למשל:

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

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

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

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

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

git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>

ואז מעלים את הענף הראשי למאגר המרוחק.

git push -u origin main

9. הגדרת פריסה רציפה

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

  • לוחצים על 'יצירת שירות'.
  • לוחצים על פריסה רציפה ממאגר.
  • לוחצים על הגדרת Cloud Build.
  • בקטע Source repository
    • בחירת GitHub כספק המאגר
    • לוחצים על Manage connected repositories (ניהול מאגרי נתונים מקושרים) כדי להגדיר את הגישה של Cloud Build למאגר.
    • בוחרים את המאגר ולוחצים על הבא.
  • בקטע Build Configuration (הגדרת בנייה)
    • השארת הענף כ-^main$
    • בקטע Build Type (סוג יצירה), בוחרים באפשרות Go, Node.js, Python, Java, .NET Core, Ruby or PHP via Google Cloud's buildpacks (יצירה באמצעות buildpacks של Google Cloud בשפות Go,‏ Node.js,‏ Python,‏ Java,‏ ‎.NET Core,‏ Ruby או PHP).
  • השארת ספריית ההקשר של ה-Build כ-/
  • לוחצים על שמירה.
  • בקטע 'אימות'
    • לוחצים על Allow unauthenticated invocations (מתן הרשאה להפעלות לא מאומתות).
  • בקטעים Container(s), Volumes, Networking, Security
    • בכרטיסייה Security (אבטחה), בוחרים את חשבון השירות שיצרתם בשלב קודם, למשל 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"

ואז דוחפים אותו למאגר במעלה הזרם ב-GitHub.

git push origin main

השינויים ייפרסו באופן אוטומטי באמצעות Cloud Build. אפשר להיכנס ל-Cloud Console בשביל שירות 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 ופריסה רציפה מ-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 Console בכתובת 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.