ติดตั้งใช้งานแอปพลิเคชัน JavaScript แบบ Full Stack ไปยัง Cloud Run ด้วย Cloud SQL สำหรับ PostgreSQL

ติดตั้งใช้งานแอปพลิเคชัน JavaScript แบบ Full Stack ไปยัง Cloud Run ด้วย Cloud SQL สำหรับ PostgreSQL

เกี่ยวกับ Codelab นี้

subjectอัปเดตล่าสุดเมื่อ ธ.ค. 12, 2024
account_circleเขียนโดย Luke Schlangen & Jack Wotherspoon

1 ภาพรวม

Cloud Run เป็นแพลตฟอร์มที่มีการจัดการโดยสมบูรณ์ซึ่งช่วยให้คุณเรียกใช้โค้ดบนโครงสร้างพื้นฐานที่ปรับขนาดได้ของ Google ได้โดยตรง Codelab นี้จะสาธิตวิธีเชื่อมต่อแอปพลิเคชัน Next.js ใน Cloud Run กับฐานข้อมูล Cloud SQL สำหรับ PostgreSQL

ในบทแนะนำนี้ คุณจะได้เรียนรู้วิธีทำสิ่งต่อไปนี้

  • สร้างอินสแตนซ์ Cloud SQL สำหรับ PostgreSQL (กำหนดค่าให้ใช้ Private Service Connect)
  • ติดตั้งใช้งานแอปพลิเคชันใน Cloud Run ที่เชื่อมต่อกับฐานข้อมูล Cloud SQL
  • ใช้ Gemini Code Assist เพื่อเพิ่มฟังก์ชันการทำงานในแอปพลิเคชัน

2 ข้อกำหนดเบื้องต้น

  1. หากยังไม่มีบัญชี Google คุณต้องสร้างบัญชี Google
    • ใช้บัญชีส่วนตัวแทนบัญชีงานหรือบัญชีโรงเรียน บัญชีงานและบัญชีโรงเรียนอาจมีข้อจํากัดที่ทำให้คุณเปิดใช้ API ที่จําเป็นสําหรับห้องทดลองนี้ไม่ได้

3 การตั้งค่าโปรเจ็กต์

  1. ลงชื่อเข้าใช้คอนโซล Google Cloud
  2. เปิดใช้การเรียกเก็บเงินในคอนโซล Cloud
    • การทำแล็บนี้ให้เสร็จสมบูรณ์ควรใช้ทรัพยากรในระบบคลาวด์ไม่ถึง $1 USD
    • คุณสามารถทำตามขั้นตอนที่ส่วนท้ายของห้องทดลองนี้เพื่อลบทรัพยากรเพื่อหลีกเลี่ยงการเรียกเก็บเงินเพิ่มเติม
    • ผู้ใช้ใหม่มีสิทธิ์รับช่วงทดลองใช้ฟรีมูลค่า$300 USD
  3. สร้างโปรเจ็กต์ใหม่หรือเลือกนําโปรเจ็กต์ที่มีอยู่มาใช้ซ้ำ

4 เปิดเครื่องมือแก้ไข Cloud Shell

  1. ไปที่ Cloud Shell Editor
  2. หากเทอร์มินัลไม่ปรากฏที่ด้านล่างของหน้าจอ ให้เปิดเทอร์มินัลโดยทำดังนี้
    • คลิกเมนู 3 ขีด ไอคอนเมนู 3 ขีด
    • คลิก Terminal
    • คลิก New Terminalเปิดเทอร์มินัลใหม่ในเครื่องมือแก้ไข Cloud Shell
  3. ในเทอร์มินัล ให้ตั้งค่าโปรเจ็กต์ด้วยคำสั่งนี้
    • รูปแบบ:
      gcloud config set project [PROJECT_ID]
    • ตัวอย่าง
      gcloud config set project lab-project-id-example
    • หากจำรหัสโปรเจ็กต์ไม่ได้ ให้ทำดังนี้
      • คุณแสดงรายการรหัสโปรเจ็กต์ทั้งหมดได้โดยทำดังนี้
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
      ตั้งค่ารหัสโปรเจ็กต์ในเทอร์มินัลของเครื่องมือแก้ไข Cloud Shell
  4. หากได้รับข้อความแจ้งให้ให้สิทธิ์ ให้คลิกให้สิทธิ์เพื่อดำเนินการต่อ คลิกเพื่อให้สิทธิ์ Cloud Shell
  5. คุณควรเห็นข้อความนี้
    Updated property [core/project].
    
    หากเห็น WARNING และระบบถาม Do you want to continue (Y/N)? แสดงว่าคุณอาจป้อนรหัสโปรเจ็กต์ไม่ถูกต้อง กด N แล้วกด Enter แล้วลองเรียกใช้คำสั่ง gcloud config set project อีกครั้ง

5 เปิดใช้ API

เปิดใช้ API ต่อไปนี้ในเทอร์มินัล

gcloud services enable \
  compute.googleapis.com \
  sqladmin.googleapis.com \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  networkconnectivity.googleapis.com \
  servicenetworking.googleapis.com \
  cloudaicompanion.googleapis.com

หากได้รับข้อความแจ้งให้ให้สิทธิ์ ให้คลิกให้สิทธิ์เพื่อดำเนินการต่อ คลิกเพื่อให้สิทธิ์ Cloud Shell

คำสั่งนี้อาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์ แต่สุดท้ายแล้วควรแสดงข้อความสำเร็จรูปคล้ายกับข้อความนี้

Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.

6 สร้างบัญชีบริการ

สร้างและกําหนดค่าบัญชีบริการ Google Cloud เพื่อใช้กับ Cloud Run เพื่อให้มีสิทธิ์ที่เหมาะสมในการเชื่อมต่อกับ Cloud SQL

  1. เรียกใช้คำสั่ง gcloud iam service-accounts create ดังนี้เพื่อสร้างบัญชีบริการใหม่
    gcloud iam service-accounts create quickstart-service-account \
      --display-name="Quickstart Service Account"
  2. เรียกใช้คำสั่ง gcloud projects add-iam-policy-binding ดังนี้เพื่อเพิ่มบทบาทผู้เขียนบันทึกลงในบัญชีบริการ Google Cloud ที่คุณเพิ่งสร้างขึ้น
    gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
      --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
      --role="roles/logging.logWriter"

7 สร้างอินสแตนซ์ Cloud SQL

  1. สร้างนโยบายการเชื่อมต่อบริการเพื่ออนุญาตให้เชื่อมต่อเครือข่ายจาก Cloud Run กับ Cloud SQL ด้วย Private Service Connect
    gcloud network-connectivity service-connection-policies create quickstart-policy \
        --network=default \
        --project=${GOOGLE_CLOUD_PROJECT} \
        --region=us-central1 \
        --service-class=google-cloud-sql \
        --subnets=https://www.googleapis.com/compute/v1/projects/${GOOGLE_CLOUD_PROJECT}/regions/us-central1/subnetworks/default
  2. สร้างรหัสผ่านที่ไม่ซ้ำกันสำหรับฐานข้อมูล
    export DB_PASSWORD=$(openssl rand -base64 20)
  3. เรียกใช้คําสั่ง gcloud sql instances create เพื่อสร้างอินสแตนซ์ Cloud SQL
    gcloud sql instances create quickstart-instance \
        --project=${GOOGLE_CLOUD_PROJECT} \
        --root-password=${DB_PASSWORD} \
        --database-version=POSTGRES_17 \
        --tier=db-perf-optimized-N-2 \
        --region=us-central1 \
        --ssl-mode=ENCRYPTED_ONLY \
        --no-assign-ip \
        --enable-private-service-connect \
        --psc-auto-connections=network=projects/${GOOGLE_CLOUD_PROJECT}/global/networks/default

คำสั่งนี้อาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์

  1. เรียกใช้คําสั่ง gcloud sql databases create เพื่อสร้างฐานข้อมูล Cloud SQL ภายใน quickstart-instance
    gcloud sql databases create quickstart_db \
      --instance=quickstart-instance

8 เตรียมใบสมัคร

เตรียมแอปพลิเคชัน Next.js ที่ตอบสนองต่อคําขอ HTTP

  1. หากต้องการสร้างโปรเจ็กต์ Next.js ใหม่ชื่อ task-app ให้ใช้คำสั่งต่อไปนี้
    npx create-next-app@15.0.3 task-app \
      --ts \
      --eslint \
      --tailwind \
      --no-src-dir \
      --turbopack \
      --app \
      --no-import-alias
  2. หากระบบขอให้ติดตั้ง create-next-app ให้กด Enter เพื่อดำเนินการต่อ
    Need to install the following packages:
    create-next-app@15.0.3
    Ok to proceed? (y)
  3. เปลี่ยนไดเรกทอรีเป็น task-app
    cd task-app
  4. ติดตั้ง pg เพื่อโต้ตอบกับฐานข้อมูล PostgreSQL
    npm install pg
  5. ติดตั้ง @types/pg เป็นการอ้างอิงของนักพัฒนาซอฟต์แวร์เพื่อใช้แอปพลิเคชัน TypeScript Next.js
    npm install --save-dev @types/pg
  6. สร้างไฟล์ actions.ts
    touch app/actions.ts
  7. เปิดไฟล์ actions.ts ใน Cloud Shell Editor โดยทำดังนี้
    cloudshell edit app/actions.ts
    ตอนนี้ไฟล์ว่างควรปรากฏที่ด้านบนของหน้าจอ คุณสามารถแก้ไขไฟล์ actions.ts นี้ได้ แสดงรหัสนั้นในส่วนด้านบนของหน้าจอ
  8. คัดลอกโค้ดต่อไปนี้และวางลงในไฟล์ actions.ts ที่เปิดอยู่
    'use server'
    import pg from 'pg';

    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
    };

    const { Pool } = pg;

    const pool = new Pool({
      host: process.env.DB_HOST,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      ssl: {
        // @ts-expect-error require true is not recognized by @types/pg, but does exist on pg
        require: true,
        rejectUnauthorized: false, // required for self-signed certs
        // https://node-postgres.com/features/ssl#self-signed-cert
      }
    });

    const tableCreationIfDoesNotExist = async () => {
      await pool.query(`CREATE TABLE IF NOT EXISTS visits (
        id SERIAL NOT NULL,
        created_at timestamp NOT NULL,
        PRIMARY KEY (id)
      );`);
      await pool.query(`CREATE TABLE IF NOT EXISTS tasks (
        id SERIAL NOT NULL,
        created_at timestamp NOT NULL,
        status VARCHAR(255) NOT NULL default 'IN_PROGRESS',
        title VARCHAR(1024) NOT NULL,
        PRIMARY KEY (id)
      );`);
    }

    // CREATE
    export async function addNewTaskToDatabase(newTask: string) {
      await tableCreationIfDoesNotExist();
      await pool.query(`INSERT INTO tasks(created_at, status, title) VALUES(NOW(), 'IN_PROGRESS', $1)`, [newTask]);
      return;
    }

    // READ
    export async function getTasksFromDatabase() {
      await tableCreationIfDoesNotExist();
      const { rows } = await pool.query(`SELECT id, created_at, status, title FROM tasks ORDER BY created_at DESC LIMIT 100`);
      console.table(rows); // logs the last 5 visits on the server
      return rows; // sends the last 5 visits to the client
    }

    // UPDATE
    export async function updateTaskInDatabase(task: Task) {
      await tableCreationIfDoesNotExist();
      await pool.query(
        `UPDATE tasks SET status = $1, title = $2 WHERE id = $3`,
        [task.status, task.title, task.id]
      );
      return;
    }

    // DELETE
    export async function deleteTaskFromDatabase(taskId: string) {
      await tableCreationIfDoesNotExist();
      await pool.query(`DELETE FROM tasks WHERE id = $1`, [taskId]);
      return;
    }
  9. เปิดไฟล์ page.tsx ใน Cloud Shell Editor โดยทำดังนี้
    cloudshell edit app/page.tsx
    ตอนนี้ไฟล์ที่มีอยู่ควรปรากฏที่ด้านบนของหน้าจอ คุณสามารถแก้ไขไฟล์ page.tsx นี้ได้ แสดงรหัสนั้นในส่วนบนของหน้าจอ
  10. ลบเนื้อหาที่มีอยู่ของไฟล์ page.tsx
  11. คัดลอกโค้ดต่อไปนี้และวางลงในไฟล์ page.tsx ที่เปิดอยู่
    'use client'
    import React, { useEffect, useState } from "react";
    import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions";

    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
    };

    export default function Home() {
      const [newTaskTitle, setNewTaskTitle] = useState('');
      const [tasks, setTasks] = useState<Task[]>([]);

      async function getTasks() {
        const updatedListOfTasks = await getTasksFromDatabase();
        setTasks(updatedListOfTasks);
      }

      useEffect(() => {
        getTasks();
      }, []);

      async function addTask() {
        await addNewTaskToDatabase(newTaskTitle);
        await getTasks();
        setNewTaskTitle('');
      }

      async function updateTask(task: Task, newTaskValues: Partial<Task>) {
        await updateTaskInDatabase({ ...task, ...newTaskValues });
        await getTasks();
      }

      async function deleteTask(taskId: string) {
        await deleteTaskFromDatabase(taskId);
        await getTasks();
      }

      return (
        <main className="p-4">
          <h2 className="text-2xl font-bold mb-4">To Do List</h2>
          <div className="flex mb-4">
            <form onSubmit={handleSubmit} className="flex mb-8">
              <input
                type="text"
                placeholder="New Task Title"
                value={newTaskTitle}
                onChange={(e) => setNewTaskTitle(e.target.value)}
                className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit"
              />
              <button
                type="submit"
                className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap"
              >
                Add New Task
              </button>
            </form>
          </div>
          <table className="w-full">
            <tbody>
              {tasks.map(function (task) {
                const isComplete = task.status === 'COMPLETE';
                return (
                  <tr key={task.id} className="border-b border-gray-200">
                    <td className="py-2 px-4">
                      <input
                        type="checkbox"
                        checked={isComplete}
                        onClick={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })}
                        className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500"
                      />
                    </td>
                    <td className="py-2 px-4">
                      <span className={`transition-all duration-300 ease-in-out ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`}>
                        {task.title}
                      </span>
                    </td>
                    <td className="py-2 px-4">
                      <button
                        onClick={() => deleteTask(task.id)}
                        className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
                      >
                        Delete
                      </button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </main>
      );
    }

ตอนนี้แอปพลิเคชันพร้อมใช้งานแล้ว

9 ทำให้แอปพลิเคชันใช้งานได้ใน Cloud Run

  1. เรียกใช้คำสั่ง gcloud projects add-iam-policy-binding ดังนี้เพื่อเพิ่มบทบาทผู้ใช้เครือข่ายลงในบัญชีบริการ Cloud Run สำหรับบริการ Cloud Run ที่คุณกำลังจะสร้าง
    gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
    --member "serviceAccount:service-$(gcloud projects describe ${GOOGLE_CLOUD_PROJECT} --format="value(projectNumber)")@serverless-robot-prod.iam.gserviceaccount.com" \
    --role "roles/compute.networkUser"
  1. เรียกใช้คําสั่งด้านล่างเพื่อทําให้แอปพลิเคชันใช้งานได้ใน Cloud Run
    gcloud run deploy helloworld \
      --region=us-central1 \
      --source=. \
      --set-env-vars DB_NAME="quickstart_db" \
      --set-env-vars DB_USER="postgres" \
      --set-env-vars DB_PASSWORD=${DB_PASSWORD} \
      --set-env-vars DB_HOST="$(gcloud sql instances describe quickstart-instance --project=${GOOGLE_CLOUD_PROJECT} --format='value(settings.ipConfiguration.pscConfig.pscAutoConnections.ipAddress)')" \
      --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
      --network=default \
      --subnet=default \
      --allow-unauthenticated
  2. หากได้รับข้อความแจ้ง ให้กด Y และ Enter เพื่อยืนยันว่าคุณต้องการดำเนินการต่อ
    Do you want to continue (Y/n)? Y
    

หลังจากผ่านไป 2-3 นาที แอปพลิเคชันควรแสดง URL ให้คุณเข้าชม

ไปที่ URL เพื่อดูการทำงานของแอปพลิเคชัน คุณจะเห็นแอปงานทุกครั้งที่ไปที่ URL หรือรีเฟรชหน้าเว็บ

10 เพิ่มฟีเจอร์ด้วยฟีเจอร์ Gemini Code Assist

ตอนนี้คุณได้ติดตั้งใช้งานเว็บแอปที่มีฐานข้อมูลแล้ว ถัดไป เราจะเพิ่มฟีเจอร์ใหม่ลงในแอป next.js โดยใช้ความช่วยเหลือจาก AI

  1. กลับไปที่เครื่องมือแก้ไข Cloud Shell
  2. เปิด page.tsx อีกครั้ง
    cd ~/task-app
    cloudshell edit app/page.tsx
  3. ไปที่ Gemini Code Assist ภายในเครื่องมือแก้ไข Cloud Shell
    • คลิกไอคอน Gemini ไอคอน Gemini Code Assist ในแถบเครื่องมือทางด้านซ้ายของหน้าจอ
    • ลงชื่อเข้าใช้ด้วยข้อมูลเข้าสู่ระบบของบัญชี Google หากได้รับข้อความแจ้ง
    • หากได้รับข้อความแจ้งให้เลือกโปรเจ็กต์ ให้เลือกโปรเจ็กต์ที่คุณสร้างสำหรับ Codelab นี้ เลือกโปรเจ็กต์ Gemini
  4. ป้อนพรอมต์ Add the ability to update the title of the task. The code in your output should be complete and working code. การตอบกลับควรมีข้อมูลโค้ดต่อไปนี้เพื่อเพิ่มฟังก์ชัน handleEditStart และ handleEditCancel
    const [editingTaskId, setEditingTaskId] = useState<string>('');
    const [editedTaskTitle, setEditedTaskTitle] = useState('');

    const handleEditStart = (task: Task) => {
      setEditingTaskId(task.id);
      setEditedTaskTitle(task.title);
    };

    const handleEditCancel = () => {
      setEditingTaskId('');
      setEditedTaskTitle('');
    };
    <td className="py-2 px-4">
      {editingTaskId === task.id ? (
        <form onSubmit={(e) => {
          e.preventDefault();
          updateTask(task, { title: editedTaskTitle });
        }}>
          <input
            type="text"
            value={editedTaskTitle}
            onChange={(e) => setEditedTaskTitle(e.target.value)}
            onBlur={() => handleEditCancel} // Handle clicking outside input
            className="border border-gray-400 rounded px-3 py-1 mr-2"
          />
            <button type="submit" className="text-green-600 hover:text-green-900 mr-1">Save</button>
        </form>

      ) : (
        <span onClick={() => handleEditStart(task)} className="cursor-pointer">
          {task.title}
        </span>
      )}
    </td>
  5. แทนที่ page.tsx ด้วยเอาต์พุตของ Gemini Code Assist ตัวอย่างที่ใช้งานได้มีดังนี้
    'use client'
    import React, { useEffect, useState } from "react";
    import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions";

    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
    };

    export default function Home() {
      const [newTaskTitle, setNewTaskTitle] = useState('');
      const [tasks, setTasks] = useState<Task[]>([]);
      const [editingTaskId, setEditingTaskId] = useState<string>('');
      const [editedTaskTitle, setEditedTaskTitle] = useState('');

      async function getTasks() {
        const updatedListOfTasks = await getTasksFromDatabase();
        setTasks(updatedListOfTasks);
      }

      useEffect(() => {
        getTasks();
      }, []);

      async function addTask() {
        await addNewTaskToDatabase(newTaskTitle);
        await getTasks();
        setNewTaskTitle('');
      }

      async function updateTask(task: Task, newTaskValues: Partial<Task>) {
        await updateTaskInDatabase({ ...task, ...newTaskValues });
        await getTasks();
        setEditingTaskId(''); // Clear editing state after update
        setEditedTaskTitle('');
      }

      async function deleteTask(taskId: string) {
        await deleteTaskFromDatabase(taskId);
        await getTasks();
      }

      const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        addTask();
      };


      const handleEditStart = (task: Task) => {
        setEditingTaskId(task.id);
        setEditedTaskTitle(task.title);
      };

      const handleEditCancel = () => {
        setEditingTaskId('');
        setEditedTaskTitle('');
      };

      return (
        <main className="p-4">
          <h2 className="text-2xl font-bold mb-4">To Do List</h2>
          <form onSubmit={handleSubmit} className="flex mb-8">
            <input
              type="text"
              placeholder="New Task Title"
              value={newTaskTitle}
              onChange={(e) => setNewTaskTitle(e.target.value)}
              className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit"
            />
            <button
              type="submit"
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap"
            >
              Add New Task
            </button>
          </form>
          <table className="w-full">
            <tbody>
              {tasks.map(task => {
                const isComplete = task.status === 'COMPLETE';
                return (
                  <tr key={task.id} className="border-b border-gray-200">
                    <td className="py-2 px-4">
                      <input
                        type="checkbox"
                        checked={isComplete}
                        onClick={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })}
                        className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500"
                      />
                    </td>
                    <td className="py-2 px-4">
                      {editingTaskId === task.id ? (
                        <form
                          onSubmit={(e) => {
                            e.preventDefault();
                            updateTask(task, { title: editedTaskTitle });
                          }}
                          className="flex"
                        >
                          <input
                            type="text"
                            value={editedTaskTitle}
                            onChange={(e) => setEditedTaskTitle(e.target.value)}
                            onBlur={() => handleEditCancel()} // Handle clicking outside input
                            className="flex-grow border border-gray-400 rounded px-3 py-1 mr-2 bg-inherit"
                          />
                          <button
                            type="submit"
                            className="bg-green-600 hover:bg-green-900 m-1 text-white font-bold py-2 px-4 rounded"
                          >
                            Save
                          </button>
                        </form>
                      ) : (
                        <span
                          onClick={() => handleEditStart(task)}
                          className={`transition-all duration-300 ease-in-out cursor-pointer ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`}
                        >
                          {task.title}
                        </span>
                      )}
                    </td>
                    <td className="py-2 px-4">
                      <button
                        onClick={() => deleteTask(task.id)}
                        className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded float-right"
                      >
                        Delete
                      </button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </main>
      );
    }

11 ปรับใช้แอปพลิเคชันกับ Cloud Run อีกครั้ง

  1. เรียกใช้คําสั่งด้านล่างเพื่อทําให้แอปพลิเคชันใช้งานได้ใน Cloud Run
    gcloud run deploy helloworld \
      --region=us-central1 \
      --source=. \
      --set-env-vars DB_NAME="quickstart_db" \
      --set-env-vars DB_USER="postgres" \
      --set-env-vars DB_PASSWORD=${DB_PASSWORD} \
      --set-env-vars DB_HOST="$(gcloud sql instances describe quickstart-instance --project=${GOOGLE_CLOUD_PROJECT} --format='value(settings.ipConfiguration.pscConfig.pscAutoConnections.ipAddress)')" \
      --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
      --network=default \
      --subnet=default \
      --allow-unauthenticated
  2. หากได้รับข้อความแจ้ง ให้กด Y และ Enter เพื่อยืนยันว่าคุณต้องการดำเนินการต่อ
    Do you want to continue (Y/n)? Y
    

12 ขอแสดงความยินดี

ในบทแนะนำนี้ คุณได้เรียนรู้วิธีดำเนินการต่อไปนี้

  • สร้างอินสแตนซ์ Cloud SQL สำหรับ PostgreSQL (กำหนดค่าให้ใช้ Private Service Connect)
  • ติดตั้งใช้งานแอปพลิเคชันใน Cloud Run ที่เชื่อมต่อกับฐานข้อมูล Cloud SQL
  • ใช้ Gemini Code Assist เพื่อเพิ่มฟังก์ชันการทำงานในแอปพลิเคชัน

ล้างข้อมูล

Cloud SQL ไม่มีแพ็กเกจที่ไม่มีค่าใช้จ่ายและจะเรียกเก็บเงินจากคุณหากยังใช้งานต่อไป คุณลบโปรเจ็กต์ที่อยู่ในระบบคลาวด์เพื่อหลีกเลี่ยงการเรียกเก็บเงินเพิ่มเติมได้

แม้ว่า Cloud Run จะไม่เรียกเก็บเงินเมื่อไม่ได้ใช้งานบริการ แต่คุณอาจยังคงถูกเรียกเก็บเงินสำหรับการจัดเก็บอิมเมจคอนเทนเนอร์ใน Artifact Registry การลบโปรเจ็กต์ในระบบคลาวด์จะหยุดการเรียกเก็บเงินสำหรับทรัพยากรทั้งหมดที่ใช้ภายในโปรเจ็กต์นั้น

หากต้องการลบโปรเจ็กต์ ให้ทำดังนี้

gcloud projects delete $GOOGLE_CLOUD_PROJECT

คุณอาจต้องลบทรัพยากรที่ไม่จำเป็นออกจากดิสก์ CloudShell ด้วย ดังนี้

  1. ลบไดเรกทอรีโปรเจ็กต์ Codelab โดยทำดังนี้
    rm -rf ~/task-app
  2. คำเตือน! การดำเนินการถัดไปนี้จะยกเลิกไม่ได้ หากต้องการลบทุกอย่างใน Cloud Shell เพื่อเพิ่มพื้นที่ว่าง คุณสามารถลบไดเรกทอรีหน้าแรกทั้งหมด โปรดตรวจสอบว่าคุณได้บันทึกข้อมูลทั้งหมดที่ต้องการเก็บไว้ไว้ที่อื่นแล้ว
    sudo rm -rf $HOME