كيفية نشر التغييرات تلقائيًا من GitHub إلى Cloud Run باستخدام Cloud Build

1. مقدمة

نظرة عامة

في هذا الدرس التطبيقي حول الترميز، ستضبط Cloud Run على إنشاء إصدارات جديدة من تطبيقك ونشرها تلقائيًا كلما أرسلت تغييرات التعليمات البرمجية المصدر إلى مستودع GitHub.

يحفظ هذا التطبيق التجريبي بيانات المستخدمين في Firestore، ولكن يتم حفظ جزء فقط من البيانات بشكل صحيح. ستضبط عمليات النشر المستمر بحيث عندما تدفع إصلاحًا لخطأ إلى مستودع GitHub، ستلاحظ تلقائيًا أنّ الإصلاح أصبح متاحًا في مراجعة جديدة.

ما ستتعلمه

  • كتابة تطبيق ويب Express باستخدام "محرِّر Cloud Shell"
  • ربط حسابك على GitHub بحسابك على Google Cloud لإجراء عمليات نشر متواصلة
  • نشر تطبيقك تلقائيًا على Cloud Run
  • التعرّف على كيفية استخدام HTMX وTailwindCSS

2. الإعداد والمتطلبات

المتطلبات الأساسية

تفعيل Cloud Shell

  1. من Cloud Console، انقر على تفعيل Cloud Shell d1264ca30785e435.png.

cb81e7c8e34bc8d.png

إذا كانت هذه هي المرة الأولى التي تبدأ فيها Cloud Shell، ستظهر لك شاشة وسيطة توضّح ماهيتها. إذا ظهرت لك شاشة وسيطة، انقر على متابعة.

d95252b003979716.png

يستغرق توفير Cloud Shell والاتصال به بضع لحظات فقط.

7833d5e1c5d18f54.png

يتم تحميل هذا الجهاز الافتراضي بجميع أدوات التطوير اللازمة. توفّر هذه الخدمة دليلًا رئيسيًا دائمًا بسعة 5 غيغابايت وتعمل في Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. يمكن إنجاز معظم عملك في هذا الدرس العملي، إن لم يكن كله، باستخدام متصفح.

بعد الاتصال بـ 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- تفعيل واجهات برمجة التطبيقات وضبط متغيرات البيئة

تفعيل واجهات برمجة التطبيقات

يتطلّب هذا الدرس التطبيقي حول الترميز استخدام واجهات برمجة التطبيقات التالية. يمكنك تفعيل واجهات برمجة التطبيقات هذه من خلال تنفيذ الأمر التالي:

gcloud services enable run.googleapis.com \
    cloudbuild.googleapis.com \
    firestore.googleapis.com \
    iamcredentials.googleapis.com

إعداد متغيرات البيئة

يمكنك ضبط متغيّرات البيئة التي سيتم استخدامها في جميع مراحل هذا الدرس البرمجي.

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" لهذا الدرس العملي.
  7. انقر على إضافة Firebase.
  8. بعد إنشاء المشروع، انقر على متابعة.
  9. من قائمة إنشاء، انقر على قاعدة بيانات 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" للوصول إلى Firestore (في حال استخدام هويتك للمصادقة، مثلاً إذا كنت تستخدم Cloud Shell)، أو يمكنك انتحال هوية حساب المستخدم الذي تم إنشاؤه سابقًا.

استخدام ADC عند التشغيل محليًا

إذا كنت تستخدم Cloud Shell، يعني ذلك أنّك تستخدم جهازًا افتراضيًا على Google Compute Engine. سيتم تلقائيًا استخدام بيانات الاعتماد المرتبطة بهذا الجهاز الافتراضي (كما هو موضّح عند تنفيذ gcloud auth list) من خلال بيانات الاعتماد التلقائية للتطبيق (ADC)، لذلك ليس من الضروري استخدام الأمر gcloud auth application-default login. ومع ذلك، سيظلّ حسابك بحاجة إلى دور "مستخدِم Datastore". يمكنك الانتقال إلى القسم تشغيل التطبيق على الجهاز.

ومع ذلك، إذا كنت تستخدم الجهاز الطرفي المحلي (أي ليس في Cloud Shell)، عليك استخدام بيانات الاعتماد التلقائية للتطبيق للمصادقة على Google APIs. يمكنك إما 1) تسجيل الدخول باستخدام بيانات الاعتماد (إذا كان لديك دور "مستخدم مخزن البيانات") أو 2) تسجيل الدخول من خلال انتحال هوية حساب الخدمة المستخدَم في هذا الدرس التطبيقي حول الترميز.

الخيار 1: استخدام بيانات اعتمادك في "مركز مطوّري التطبيقات"

إذا أردت استخدام بيانات الاعتماد، يمكنك أولاً تشغيل gcloud auth list للتحقّق من طريقة مصادقتك في gcloud. بعد ذلك، قد تحتاج إلى منح هويتك دور "مستخدم Vertex AI". إذا كانت هويتك تتضمّن دور "المالك"، يكون لديك دور المستخدم "مستخدم 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

إذا كنت تريد استخدام حساب الخدمة الذي تم إنشاؤه في هذا الدرس التطبيقي حول الترميز، يجب أن يتضمّن حساب المستخدم دور "منشئ الرموز المميزة لحساب الخدمة". يمكنك الحصول على هذا الدور من خلال تنفيذ الأمر التالي:

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 الخاص ببرنامج التدريب العملي.

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 وأنشِئ مستودعًا فارغًا خاصًا بك أو عامًا. يوصي هذا الدرس العملي بتسمية المستودع 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 لخدمة Cloud Run.

  • انقر على "إنشاء خدمة".
  • انقر على النشر المتواصل من مستودع.
  • انقر على إعداد Cloud Build.
  • ضمن "مستودع المصدر"
    • اختيار GitHub كموفّر المستودع
    • انقر على إدارة المستودعات المرتبطة لضبط إذن وصول Cloud Build إلى المستودع
    • اختَر المستودع وانقر على التالي
  • ضمن "إعدادات الإنشاء"
    • اترك الفرع ^main$
    • بالنسبة إلى "نوع الإصدار"، اختَر Go أو Node.js أو Python أو Java أو ‎.NET Core أو Ruby أو PHP من خلال حِزم الإنشاء في Google Cloud.
  • اترك دليل سياق الإصدار على /
  • انقر على حفظ.
  • ضمن "المصادقة"
    • انقر على السماح بعمليات الاستدعاء غير المصادق عليها.
  • ضمن "الحاويات" و"وحدات التخزين" و"الشبكات" و"الأمان"
    • ضمن علامة التبويب "الأمان"، اختَر حساب الخدمة الذي أنشأته في خطوة سابقة، مثل Cloud Run access to Firestore.
  • انقر على إنشاء.

سيؤدي ذلك إلى نشر خدمة Cloud Run التي تحتوي على الخطأ الذي ستعمل على إصلاحه في القسم التالي.

10. إصلاح الخطأ

إصلاح الخطأ في الرمز البرمجي

في "محرّر Cloud Shell"، افتح الملف 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 Console لخدمة Cloud Run أنّ المراجعة الثانية تعرض الآن% 100 من عدد الزيارات، مثلاً https://console.cloud.google.com/run/detail/<YOUR_REGION>/<YOUR_SERVICE_NAME>/revisions، يمكنك فتح عنوان URL لخدمة Cloud Run في المتصفّح والتأكّد من استمرار بيانات المدينة التي تم إدخالها حديثًا بعد إعادة تحميل الصفحة.

11. تهانينا!

تهانينا على إكمال هذا الدرس العملي.

ننصحك بمراجعة المستندات Cloud Run والنشر المستمر من git.

المواضيع التي تناولناها

  • كتابة تطبيق ويب Express باستخدام "محرِّر Cloud Shell"
  • ربط حسابك على 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 التي أنشأتها في هذا الدرس التطبيقي حول الترميز، مثل حذف خدمة cloud-run-auto-deploy-codelab.

إذا اخترت حذف المشروع بأكمله، يمكنك الانتقال إلى https://console.cloud.google.com/cloud-resource-manager، واختيار المشروع الذي أنشأته في الخطوة 2، ثم النقر على "حذف". إذا حذفت المشروع، عليك تغيير المشاريع في Cloud SDK. يمكنك الاطّلاع على قائمة بجميع المشاريع المتاحة من خلال تنفيذ gcloud projects list.