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 xây dựng và triển khai các phiên bản mới của ứng dụng mỗi khi đẩy các thay đổi về mã nguồn vào 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 triển khai liên tục sao cho khi đẩy 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 có trong bản sửa đổi mới.

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

  • Viết một ứng dụng web Express bằng Cloud Shell Editor
  • Kết nối tài khoản GitHub với Google Cloud để liên tục triển khai
  • Tự động triển khai ứng dụng 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 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 nhìn thấy màn hình trung gian, hãy nhấp vào Tiếp tục.

d95252b003979716.png

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

7833d5e1c5d18f54.pngS

Máy ảo này được tải tất cả các công cụ phát triển cần thiết. Dịch vụ này cung cấp thư mục gốc có dung lượng ổn định 5 GB và chạy trên Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Nhiều (nếu không nói là) tất cả công việc của bạn trong lớp học lập trình này đều có thể thực hiện bằng trình duyệt.

Sau khi kết nối với Cloud Shell, bạn sẽ thấy mình đã được xác thực và dự án được đặt thành mã dự á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

Kết quả 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

Kết quả lệnh

[core]
project = <PROJECT_ID>

Nếu chưa, bạn có thể thiết lập chế độ này bằng lệnh sau:

gcloud config set project <PROJECT_ID>

Kết quả lệnh

Updated property [core/project].

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

Bật API

Lớp học lập trình này yêu cầu 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 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ụ

Tài khoản dịch vụ này sẽ được Cloud Run sử dụng để 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 của 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"

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

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. Sau khi tạo dự án, hãy nhấp vào Tiếp tục.
  9. Trên trình đơn Build (Tạo), hãy nhấp vào Firestore cơ sở dữ liệu.
  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í, sau đó nhấp vào Tiếp theo.
  12. Sử dụng Bắt đầu ở chế độ phát hành công khai mặc định, sau đó nhấp vào Tạo.

6. Viết ứng dụng

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

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

Sau đó, hãy tạo một tệp package.json với 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 một tệp input.css cho tailwindCSS

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

Sau đó, 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

Và 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 trên máy

Trong phần này, bạn sẽ chạy ứng dụng cục bộ để xác nhận có 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 bạn sử dụng danh tính của mình để xác thực, chẳng hạn như bạn đang chạy trong Cloud Shell), hoặc bạn có thể nhập vai 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 đã chạy trên một máy ảo Google Compute Engine. Thông tin đăng nhập liên kết với máy ảo này (như minh hoạ trong quá trình chạy gcloud auth list) sẽ được Thông tin xác thực mặc định của ứng dụng (ADC) tự động sử dụng. Do đó, bạn không cần phải sử 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 đến 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 chạy trong Cloud Shell), bạn sẽ cần phải 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 kho dữ liệu) 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.

Phương án 1) Sử dụng thông tin xác thực của bạn cho ADC

Nếu muốn sử dụng thông tin đăng nhập của mình thì 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 vai trò Người dùng Vertex AI cho danh tính của bạn. Nếu danh tính của bạn có vai trò Chủ sở hữu, thì bạn đã có vai trò của người dùng Người dùng kho dữ liệu này. Nếu không, bạn có thể chạy lệnh này để cấp vai trò của người dùng trong Vertex AI danh tính 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 đó chạy lệnh sau

gcloud auth application-default login

Lựa chọn 2) Mạo danh một tài khoản dịch vụ của 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 phải có vai trò Người tạo mã thông báo tài khoản dịch vụ. Bạn có thể có được 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 trên máy

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

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ể khởi động ứ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 vào http://localhost:8080. Nếu đang ở Cloud Shell, bạn có thể mở trang web bằng cách mở nút Web Preview (Xem trước trên web) và chọn Preview Port 8080.

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

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

Ngăn ứng dụng express chạy cục bộ (ví dụ: Ctrl^c trên MacOS).

8. Tạo Kho lưu trữ GitHub

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

git init
git branch -M main

Xác nhận cơ sở mã hiện tại đang chứa lỗi. Bạn sẽ khắc phục 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 vào 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 khuyên bạn nên đặt tên cho kho lưu trữ là cloud-run-auto-deploy-codelab Để tạo một kho lưu trữ trống, bạn sẽ không đánh dấu tất cả chế độ cài đặt mặc định hoặc đặt thành không có để không có nội dung nào nằm 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 các hướng dẫn sau trên trang kho lưu trữ trống:

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

Bạn sẽ làm theo hướng dẫn đẩy 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 main vào kho lưu trữ ngược dòng (upstream).

git push -u origin main

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

Giờ đây, khi đã có mã trong GitHub, bạn có thể thiết lập quy trình triển khai liên tục. Truy cập vào Cloud Console for Cloud Run.

  • Nhấp vào Tạo dịch vụ
  • Nhấp vào Liên tục triển khai từ kho lưu trữ
  • Nhấp vào THIẾT LẬP XÂY DỰNG ĐÁM MÂY.
  • Trong 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ý kho lưu trữ đã 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ữ rồi nhấp vào Tiếp theo
  • Trong Cấu hình bản dựng
    • Để Branch 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 gói bản dựng của Google Cloud
  • Để thư mục Ngữ cảnh bản dựng là /
  • 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(các) Vùng chứa, Số lượng, 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. Sửa lỗi

Sửa lỗi trong mã

Trong Cloud Shell Editor, hãy chỉnh sửa tệp app.js rồi xem nhận xét có nội dung //TODO: fix this bug

thay đổi dòng sau 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 lại dữ liệu cho thị trấn và làm mới. Bạn sẽ thấy dữ liệu của thị trấn mới nhập được duy 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 cam kết khắc phục.

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

rồi đẩy tệp này vào kho lưu trữ thượng 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 vào Cloud Console của dịch vụ Cloud Run để giám sát các thay đổi trong quá trình triển khai.

Xác minh bản sửa lỗi trong phiên bản phát hành công khai

Sau khi Cloud Console cho dịch vụ Cloud Run của bạn hiển thị 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à kiểm tra để đảm bảo dữ liệu của thị trấn mới nhập vẫn còn 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!

Bạn nên tham khảo tài liệu về Cloud Runhoạt động triển khai liên tục từ git.

Nội dung đã đề cập

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

12. Dọn dẹp

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

Để xoá dịch vụ Cloud Run, hãy truy cập vào Cloud Run Cloud Console 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 mà bạn đã tạo ở Bước 2 rồi chọn Xoá. Nếu xoá dự án, bạn sẽ phải thay đổi các dự án trong Cloud SDK của mình. Bạn có thể xem danh sách tất cả dự án hiện có bằng cách chạy gcloud projects list.