Cách tự động triển khai các thay đổi từ GitHub sang Cloud Run bằng Cloud Build

1. Giới thiệu

Tổng quan

Trong lớp học lập trình này, bạn sẽ định cấu hình Cloud Run để tự động tạo và triển khai các phiên bản mới của ứng dụng bất cứ khi nào bạn đẩy các thay đổi về mã nguồn lên một kho lưu trữ GitHub.

Ứng dụng minh hoạ này lưu dữ liệu người dùng vào Firestore, tuy nhiên, chỉ một phần dữ liệu được lưu đúng cách. Bạn sẽ định cấu hình quy trình triển khai liên tục để khi bạn đẩy một bản sửa lỗi vào kho lưu trữ GitHub, bạn sẽ tự động thấy bản sửa lỗi xuất hiện trong một bản sửa đổi mới.

Kiến thức bạn sẽ học được

  • Viết ứng dụng web Express bằng Trình chỉnh sửa Cloud Shell
  • Kết nối tài khoản GitHub với Google Cloud để triển khai liên tục
  • Tự động triển khai ứng dụng của bạn lên Cloud Run
  • Tìm hiểu cách sử dụng HTMX và TailwindCSS

2. Thiết lập và yêu cầu

Điều kiện tiên quyết

Kích hoạt Cloud Shell

  1. Trong Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell d1264ca30785e435.png.

cb81e7c8e34bc8d.png

Nếu đây là lần đầu tiên bạn khởi động Cloud Shell, bạn sẽ thấy một màn hình trung gian mô tả về Cloud Shell. Nếu bạn thấy màn hình trung gian, hãy nhấp vào Tiếp tục.

d95252b003979716.png

Quá trình cung cấp và kết nối với Cloud Shell chỉ mất vài phút.

7833d5e1c5d18f54.png

Máy ảo này được trang bị tất cả các công cụ phát triển cần thiết. Nền tảng này cung cấp một thư mục chính có dung lượng 5 GB và chạy trong Google Cloud, giúp tăng cường đáng kể hiệu suất mạng và hoạt động xác thực. Bạn có thể thực hiện hầu hết, nếu không muốn nói là tất cả, công việc của mình trong lớp học lập trình này bằng trình duyệt.

Sau khi kết nối với Cloud Shell, bạn sẽ thấy rằng mình đã được xác thực và dự án được đặt thành mã dự án của bạn.

  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng bạn đã được xác thực:
gcloud auth list

Đầu ra của lệnh

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng lệnh gcloud biết về dự án của bạn:
gcloud config list project

Đầu ra của lệnh

[core]
project = <PROJECT_ID>

Nếu không, bạn có thể đặt bằng lệnh sau:

gcloud config set project <PROJECT_ID>

Đầu ra của lệnh

Updated property [core/project].

3. Bật API và đặt các biến môi trường

Bật API

Lớp học lập trình này yêu cầu bạn sử dụng các API sau. Bạn có thể bật các API đó bằng cách chạy lệnh sau:

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

Thiết lập các biến môi trường

Bạn có thể thiết lập các biến môi trường sẽ được dùng trong suốt lớp học lập trình này.

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. Tạo một tài khoản dịch vụ

Cloud Run sẽ dùng tài khoản dịch vụ này để gọi Vertex AI Gemini API. Tài khoản dịch vụ này cũng sẽ có quyền đọc và ghi vào Firestore, cũng như đọc các khoá bí mật từ Secret Manager.

Trước tiên, hãy tạo tài khoản dịch vụ bằng cách chạy lệnh sau:

gcloud iam service-accounts create $SERVICE_ACCOUNT \
  --display-name="Cloud Run access to Firestore"

Giờ đây, hãy cấp cho tài khoản dịch vụ quyền đọc và ghi đối với Firestore.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
  --role=roles/datastore.user

5. Tạo và định cấu hình dự án Firebase

  1. Trong bảng điều khiển của Firebase, hãy nhấp vào Thêm dự án.
  2. Nhập <YOUR_PROJECT_ID> để thêm Firebase vào một trong các dự án hiện có trên Google Cloud
  3. Nếu được nhắc, hãy xem xét và chấp nhận các điều khoản của Firebase.
  4. Nhấp vào Tiếp tục.
  5. Nhấp vào Xác nhận gói để xác nhận gói thanh toán Firebase.
  6. Bạn không bắt buộc phải Bật Google Analytics cho lớp học lập trình này.
  7. Nhấp vào Thêm Firebase.
  8. Khi dự án đã được tạo, hãy nhấp vào Tiếp tục.
  9. Trong trình đơn Build (Xây dựng), hãy nhấp vào Firestore database (Cơ sở dữ liệu Firestore).
  10. Nhấp vào Tạo cơ sở dữ liệu.
  11. Chọn khu vực của bạn trong trình đơn thả xuống Vị trí, rồi nhấp vào Tiếp theo.
  12. Sử dụng chế độ mặc định Bắt đầu ở chế độ phát hành công khai, rồi nhấp vào Tạo.

6. Viết ứng dụng

Trước tiên, hãy tạo một thư mục cho mã nguồn và chuyển đến thư mục đó.

mkdir cloud-run-github-cd-demo && cd $_

Sau đó, hãy tạo một tệp package.json có nội dung sau:

{
  "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"
  }
}

Trước tiên, hãy tạo một tệp nguồn app.js có nội dung bên dưới. Tệp này chứa điểm truy cập cho dịch vụ và chứa logic chính cho ứng dụng.

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);
});

Tạo một tệp có tên là 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>`;

Tạo tệp input.css cho tailwindCSS

@tailwind base;
@tailwind components;
@tailwind utilities;

Và tạo tệp tailwind.config.js cho tailwindCSS

/** @type {import('tailwindcss').Config} */
module.exports = {
    content: ["./**/*.{html,js}"],
    theme: {
        extend: {}
    },
    plugins: []
};

Và tạo một tệp .gitignore.

node_modules/

npm-debug.log
coverage/

package-lock.json

.DS_Store

Bây giờ, hãy tạo một thư mục public mới.

mkdir public
cd public

Trong thư mục công khai đó, hãy tạo tệp index.html cho giao diện người dùng. Tệp này sẽ sử dụng 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. Chạy ứng dụng cục bộ

Trong phần này, bạn sẽ chạy ứng dụng cục bộ để xác nhận rằng có một lỗi trong ứng dụng khi người dùng cố gắng lưu dữ liệu.

Trước tiên, bạn cần có vai trò Người dùng Datastore để truy cập vào Firestore (nếu sử dụng danh tính của mình để xác thực, ví dụ: bạn đang chạy trong Cloud Shell) hoặc bạn có thể mạo danh tài khoản người dùng đã tạo trước đó.

Sử dụng ADC khi chạy cục bộ

Nếu đang chạy trong Cloud Shell, tức là bạn đang chạy trên một máy ảo Google Compute Engine. Thông tin đăng nhập của bạn được liên kết với máy ảo này (như khi chạy gcloud auth list) sẽ tự động được Thông tin đăng nhập mặc định của ứng dụng (ADC) sử dụng, nên bạn không cần dùng lệnh gcloud auth application-default login. Tuy nhiên, danh tính của bạn vẫn cần có vai trò Người dùng Datastore. Bạn có thể chuyển xuống phần Chạy ứng dụng cục bộ.

Tuy nhiên, nếu đang chạy trên thiết bị đầu cuối cục bộ (tức là không phải trong Cloud Shell), bạn sẽ cần sử dụng Thông tin xác thực mặc định của ứng dụng để xác thực với các API của Google. Bạn có thể 1) đăng nhập bằng thông tin đăng nhập của mình (miễn là bạn có vai trò Người dùng Datastore) hoặc 2) bạn có thể đăng nhập bằng cách mạo danh tài khoản dịch vụ được dùng trong lớp học lập trình này.

Cách 1) Sử dụng thông tin đăng nhập của bạn cho ADC

Nếu muốn sử dụng thông tin đăng nhập của mình, trước tiên, bạn có thể chạy gcloud auth list để xác minh cách bạn được xác thực trong gcloud. Tiếp theo, bạn có thể cần cấp cho danh tính của mình vai trò Người dùng Vertex AI. Nếu danh tính của bạn có vai trò Chủ sở hữu, thì bạn đã có vai trò người dùng Datastore User này. Nếu không, bạn có thể chạy lệnh này để cấp cho danh tính của mình vai trò người dùng Vertex AI và vai trò Người dùng Datastore.

USER=<YOUR_PRINCIPAL_EMAIL>

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member user:$USER \
  --role=roles/datastore.user

Sau đó, hãy chạy lệnh sau

gcloud auth application-default login

Cách 2) Mạo danh Tài khoản dịch vụ cho ADC

Nếu muốn sử dụng tài khoản dịch vụ được tạo trong lớp học lập trình này, tài khoản người dùng của bạn cần có vai trò Trình tạo mã thông báo tài khoản dịch vụ. Bạn có thể lấy vai trò này bằng cách chạy lệnh sau:

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member user:$USER \
  --role=roles/iam.serviceAccountTokenCreator

Tiếp theo, bạn sẽ chạy lệnh sau để sử dụng ADC với tài khoản dịch vụ

gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS

Chạy ứng dụng cục bộ

Tiếp theo, hãy đảm bảo bạn đang ở trong thư mục gốc cloud-run-github-cd-demo cho lớp học lập trình này.

cd .. && pwd

Bây giờ, bạn sẽ cài đặt các phần phụ thuộc.

npm install

Cuối cùng, bạn có thể chạy ứng dụng bằng cách chạy tập lệnh sau. Tập lệnh này cũng sẽ tạo tệp output.css từ tailwindCSS.

npm run dev

Bây giờ, hãy mở trình duyệt web của bạn đến http://localhost:8080. Nếu đang ở trong Cloud Shell, bạn có thể mở trang web bằng cách mở nút Web Preview (Xem trước trên web) rồi chọn Preview Port 8080 (Xem trước cổng 8080).

xem trước trên web – nút xem trước trên cổng 8080

Nhập văn bản cho các trường tên và thị trấn rồi nhấn vào lưu. Sau đó, hãy làm mới trang. Bạn sẽ nhận thấy rằng trường thành phố không được duy trì. Bạn sẽ sửa lỗi này trong phần tiếp theo.

Dừng ứng dụng Express chạy cục bộ (ví dụ: Ctrl^c trên macOS).

8. Tạo kho lưu trữ trên GitHub

Trong thư mục cục bộ, hãy tạo một kho lưu trữ mới có main làm tên nhánh mặc định.

git init
git branch -M main

Xác nhận cơ sở mã hiện tại có chứa lỗi. Bạn sẽ sửa lỗi sau khi định cấu hình quy trình triển khai liên tục.

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

Truy cập GitHub rồi tạo một kho lưu trữ trống ở chế độ riêng tư hoặc công khai. Lớp học lập trình này đề xuất đặt tên cho kho lưu trữ của bạn là cloud-run-auto-deploy-codelab. Để tạo một kho lưu trữ trống, bạn sẽ bỏ đánh dấu hoặc đặt tất cả chế độ cài đặt mặc định thành không có để không có nội dung nào trong kho lưu trữ theo mặc định khi được tạo, ví dụ:

Chế độ cài đặt mặc định của GitHub

Nếu hoàn tất bước này đúng cách, bạn sẽ thấy hướng dẫn sau trên trang kho lưu trữ trống:

Hướng dẫn về kho lưu trữ trống trên GitHub

Bạn sẽ làm theo hướng dẫn đẩy một kho lưu trữ hiện có từ dòng lệnh bằng cách chạy các lệnh sau:

Trước tiên, hãy thêm kho lưu trữ từ xa bằng cách chạy

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

sau đó đẩy nhánh chính lên kho lưu trữ nguồn.

git push -u origin main

9. Thiết lập quy trình triển khai liên tục

Bây giờ, khi đã có mã trong GitHub, bạn có thể thiết lập quy trình triển khai liên tục. Chuyển đến Cloud Console cho Cloud Run.

  • Nhấp vào Tạo dịch vụ
  • Nhấp vào Triển khai liên tục từ một kho lưu trữ
  • Nhấp vào THIẾT LẬP CLOUD BUILD.
  • Trong phần Source repository (Kho lưu trữ nguồn)
    • Chọn GitHub làm Nhà cung cấp kho lưu trữ
    • Nhấp vào Quản lý các kho lưu trữ được kết nối để định cấu hình quyền truy cập của Cloud Build vào kho lưu trữ
    • Chọn kho lưu trữ của bạn rồi nhấp vào Tiếp theo
  • Trong phần Build Configuration
    • Để Branch (Nhánh) là ^main$
    • Đối với Loại bản dựng, hãy chọn Go, Node.js, Python, Java, .NET Core, Ruby hoặc PHP thông qua các gói bản dựng của Google Cloud
  • Để thư mục bối cảnh Build ở dạng /
  • Nhấp vào Lưu
  • Trong phần Xác thực
    • Nhấp vào Cho phép lệnh gọi chưa được xác thực
  • Trong phần Vùng chứa, Tập, Mạng, Bảo mật
    • Trong thẻ Bảo mật, hãy chọn tài khoản dịch vụ mà bạn đã tạo ở bước trước đó, ví dụ: Cloud Run access to Firestore
  • Nhấp vào TẠO

Thao tác này sẽ triển khai dịch vụ Cloud Run có chứa lỗi mà bạn sẽ khắc phục trong phần tiếp theo.

10. Khắc phục lỗi

Khắc phục lỗi trong mã

Trong Cloud Shell Editor, hãy mở tệp app.js rồi chuyển đến nhận xét có nội dung //TODO: fix this bug

thay đổi dòng sau đây từ

 //TODO: fix this bug
    await doc.set({
        name: name
    });

tới

//fixed town bug
    await doc.set({
        name: name,
        town: town
    });

Xác minh bản sửa lỗi bằng cách chạy

npm run start

và mở trình duyệt web. Lưu dữ liệu cho thị trấn một lần nữa rồi làm mới. Bạn sẽ thấy dữ liệu thị trấn mới nhập đã được lưu trữ chính xác khi làm mới.

Sau khi xác minh bản sửa lỗi, bạn có thể triển khai bản sửa lỗi đó. Trước tiên, hãy xác nhận việc sửa lỗi.

git add .
git commit -m "fixed town bug"

rồi đẩy nó lên kho lưu trữ nguồn trên GitHub.

git push origin main

Cloud Build sẽ tự động triển khai các thay đổi của bạn. Bạn có thể truy cập Cloud Console cho dịch vụ Cloud Run để theo dõi các thay đổi về việc triển khai.

Xác minh bản sửa lỗi trong môi trường thực tế

Sau khi Cloud Console cho dịch vụ Cloud Run của bạn cho thấy bản sửa đổi thứ 2 hiện đang phân phát 100% lưu lượng truy cập, ví dụ: https://console.cloud.google.com/run/detail/<YOUR_REGION>/<YOUR_SERVICE_NAME>/revisions, bạn có thể mở URL dịch vụ Cloud Run trong trình duyệt và xác minh rằng dữ liệu thị trấn mới nhập vẫn được lưu giữ sau khi làm mới trang.

11. Xin chúc mừng!

Chúc mừng bạn đã hoàn thành lớp học lập trình này!

Bạn nên xem tài liệu về Cloud Run và triển khai liên tục từ git.

Nội dung đã đề cập

  • Viết ứng dụng web Express bằng Trình chỉnh sửa Cloud Shell
  • Kết nối tài khoản GitHub với Google Cloud để triển khai liên tục
  • Tự động triển khai ứng dụng của bạn lên Cloud Run
  • Tìm hiểu cách sử dụng HTMX và TailwindCSS

12. Dọn dẹp

Để tránh bị tính phí ngoài ý muốn (ví dụ: nếu các dịch vụ Cloud Run được gọi nhiều lần hơn hạn mức gọi Cloud Run hằng tháng của bạn trong cấp miễn phí), bạn có thể xoá Cloud Run hoặc xoá dự án mà bạn đã tạo ở Bước 2.

Để xoá dịch vụ Cloud Run, hãy truy cập vào Cloud Console của Cloud Run tại https://console.cloud.google.com/run rồi xoá dịch vụ Cloud Run mà bạn đã tạo trong lớp học lập trình này, ví dụ: xoá dịch vụ cloud-run-auto-deploy-codelab.

Nếu chọn xoá toàn bộ dự án, bạn có thể truy cập vào https://console.cloud.google.com/cloud-resource-manager, chọn dự án bạn đã tạo ở Bước 2 rồi chọn Xoá. Nếu xoá dự án, bạn sẽ cần thay đổi dự án trong Cloud SDK. Bạn có thể xem danh sách tất cả các dự án có sẵn bằng cách chạy gcloud projects list.