איך לפרוס באופן אוטומטי את השינויים מ-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. כתיבת הבקשה

קודם יוצרים ספרייה לקוד המקור ועוברים לספרייה הזו.

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'. אפשר לדלג לקטע הפעלת האפליקציה באופן מקומי.

אבל אם אתם מריצים את הפקודה במסוף המקומי (כלומר לא ב-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 ואת תפקיד המשתמש במאגר הנתונים.

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

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

npm run dev

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

web preview - preview on port 8080 button

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

מפסיקים את ההפעלה של אפליקציית 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 (‏Go,‏ Node.js,‏ Python,‏ Java,‏ ‎.NET Core,‏ Ruby או PHP באמצעות buildpacks של Google Cloud).
  • השארת ספריית ההקשר של ה-Build כ-/
  • לוחצים על שמירה.
  • בקטע 'אימות'
    • לוחצים על אפשר הפעלות לא מאומתות.
  • בקטעים 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.