1. Przegląd
Cloud Run to w pełni zarządzana platforma, która umożliwia uruchamianie kodu bezpośrednio w skalowalnej infrastrukturze Google. W tym laboratorium dowiesz się, jak połączyć aplikację Next.js w Cloud Run z bazą danych Cloud SQL dla PostgreSQL.
W tym module nauczysz się, jak:
- Utwórz instancję Cloud SQL for PostgreSQL
- Wdrażanie aplikacji w Cloud Run, która łączy się z bazą danych Cloud SQL
2. Wymagania wstępne
- Jeśli nie masz jeszcze konta Google, musisz je utworzyć.
- Używaj konta osobistego zamiast konta służbowego lub szkolnego. Konta służbowe i szkolne mogą mieć ograniczenia, które uniemożliwiają włączenie interfejsów API potrzebnych w tym module.
3. Konfigurowanie projektu
- Zaloguj się w konsoli Google Cloud.
- Włącz płatności w konsoli Google Cloud.
- Pod względem opłat za zasoby chmury ukończenie tego modułu powinno kosztować mniej niż 1 USD.
- Jeśli chcesz uniknąć dalszych opłat, wykonaj czynności opisane na końcu tego modułu, aby usunąć zasoby.
- Nowi użytkownicy mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
- Utwórz nowy projekt lub użyj już istniejącego.
4. Otwórz edytor Cloud Shell
- Otwórz edytor Cloud Shell.
- Jeśli terminal nie pojawi się u dołu ekranu, otwórz go:
- Kliknij menu
. - Kliknij Terminal.
- Kliknij Nowy terminal
.
- Kliknij menu
- W terminalu ustaw projekt za pomocą tego polecenia:
- Format:
gcloud config set project [PROJECT_ID] - Przykład:
gcloud config set project lab-project-id-example - Jeśli nie pamiętasz identyfikatora projektu:
- Aby wyświetlić listę wszystkich identyfikatorów projektów, użyj tego polecenia:
gcloud projects list | awk '/PROJECT_ID/{print $2}'

- Aby wyświetlić listę wszystkich identyfikatorów projektów, użyj tego polecenia:
- Format:
- Jeśli pojawi się pytanie o autoryzację, kliknij Autoryzuj, aby przejść dalej.

- Powinien wyświetlić się ten komunikat:
Jeśli widzisz symbolUpdated property [core/project].
WARNINGi pojawia się pytanieDo you want to continue (Y/N)?, prawdopodobnie identyfikator projektu został wpisany nieprawidłowo. NaciśnijN, a potemEnteri spróbuj ponownie uruchomić poleceniegcloud config set project.
5. Włącz interfejsy API
W terminalu włącz interfejsy 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
Jeśli pojawi się pytanie o autoryzację, kliknij Autoryzuj, aby przejść dalej. 
Wykonanie tego polecenia może potrwać kilka minut, ale powinno ostatecznie wyświetlić komunikat o sukcesie podobny do tego:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. Konfigurowanie konta usługi
Utwórz i skonfiguruj konto usługi Google Cloud, które będzie używane przez Cloud Run, aby miało odpowiednie uprawnienia do łączenia się z Cloud SQL.
- Aby utworzyć nowe konto usługi, uruchom polecenie
gcloud iam service-accounts createw ten sposób:gcloud iam service-accounts create quickstart-service-account \ --display-name="Quickstart Service Account" - Aby dodać rolę Zapisujący dzienniki do utworzonego przed chwilą konta usługi Google Cloud, uruchom polecenie gcloud projects add-iam-policy-binding w ten sposób:
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. Tworzenie bazy danych Cloud SQL
- Utwórz zasadę połączenia z usługą, aby zezwolić na połączenie sieciowe z Cloud Run do Cloud SQL za pomocą 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 - Generowanie unikalnego hasła do bazy danych
export DB_PASSWORD=$(openssl rand -base64 20) - Uruchom polecenie
gcloud sql instances create, aby utworzyć instancję 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
Wykonanie tego polecenia może potrwać kilka minut.
- Aby utworzyć bazę danych Cloud SQL w usłudze
quickstart-instance, uruchom poleceniegcloud sql databases create.gcloud sql databases create quickstart_db \ --instance=quickstart-instance
8. Przygotowywanie aplikacji
Przygotuj aplikację Next.js, która odpowiada na żądania HTTP.
- Aby utworzyć nowy projekt Next.js o nazwie
task-app, użyj tego polecenia:npx --yes create-next-app@15 task-app \ --ts \ --eslint \ --tailwind \ --no-src-dir \ --turbopack \ --app \ --no-import-alias - Przejdź do katalogu
task-app:cd task-app - Zainstaluj
pg, aby korzystać z bazy danych PostgreSQL.npm install pg - Zainstaluj
@types/pgjako zależność deweloperską, aby używać aplikacji Next.js w TypeScript.npm install --save-dev @types/pg - Otwórz plik
actions.tsw edytorze Cloud Shell: U góry ekranu powinien pojawić się pusty plik. W tym miejscu możesz edytować plikcloudshell edit app/actions.tsactions.ts.
- Skopiuj ten kod i wklej go do otwartego pliku
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 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`); return rows; } // 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; } - Otwórz plik
page.tsxw edytorze Cloud Shell: U góry ekranu powinien pojawić się istniejący plik. W tym miejscu możesz edytować plikcloudshell edit app/page.tsxpage.tsx.
- Usuń dotychczasową zawartość pliku
page.tsx. - Skopiuj ten kod i wklej go do otwartego pliku
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 handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); 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 float-right" > Delete </button> </td> </tr> ); })} </tbody> </table> </main> ); }
Aplikacja jest teraz gotowa do wdrożenia.
9. Wdrażanie aplikacji w Cloud Run
- Aby dodać do konta usługi Cloud Run rolę Użytkownik sieci w przypadku usługi Cloud Run, którą zamierzasz utworzyć, wykonaj to polecenie gcloud projects add-iam-policy-binding:
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"
- Aby dodać rolę Zapisujący w Artifact Registry do bieżącego użytkownika w usłudze Cloud Run, którą zamierzasz utworzyć, wykonaj to polecenie:
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member=user:$(gcloud auth list --filter=status:ACTIVE --format="value(account)") \ --role="roles/artifactregistry.writer"
- Aby wdrożyć aplikację w Cloud Run, uruchom to polecenie:
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 - Jeśli pojawi się prośba, naciśnij
YiEnter, aby potwierdzić, że chcesz kontynuować:Do you want to continue (Y/n)? Y
Po kilku minutach aplikacja powinna podać adres URL, który możesz otworzyć.
Otwórz adres URL, aby zobaczyć działanie aplikacji. Za każdym razem, gdy otworzysz adres URL lub odświeżysz stronę, zobaczysz aplikację do zarządzania zadaniami.
10. Dodawanie funkcji za pomocą Gemini Code Assist
Aplikacja internetowa z bazą danych została wdrożona. Następnie dodamy nową funkcję do naszej aplikacji next.js, korzystając z pomocy AI.
- Powrót do edytora Cloud Shell
- Otwórz ponownie
page.tsxcd ~/task-app cloudshell edit app/page.tsx - Otwórz Gemini Code Assist w edytorze Cloud Shell:
- Na pasku narzędzi po lewej stronie ekranu kliknij ikonę Gemini
. - Jeśli pojawi się taka prośba, zaloguj się przy użyciu danych logowania na konto Google.
- Jeśli pojawi się prośba o wybranie projektu, wybierz projekt utworzony na potrzeby tego laboratorium
.
- Na pasku narzędzi po lewej stronie ekranu kliknij ikonę Gemini
- Wpisz prompt:
Add the ability to update the title of the task. The code in your output should be complete and working code.. Odpowiedź powinna zawierać fragmenty kodu podobne do tych, które dodają funkcjehandleEditStartihandleEditCancel:const [editingTaskId, setEditingTaskId] = useState(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; - Zastąp symbol
page.tsxwynikiem działania Gemini Code Assist. Oto działający przykład:'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(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); async function getTasks() { const updatedListOfTasks = await getTasksFromDatabase(); setTasks(updatedListOfTasks); } useEffect(() => { getTasks(); }, []); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); await addNewTaskToDatabase(newTaskTitle); await getTasks(); setNewTaskTitle(''); }; async function updateTask(task: Task, newTaskValues: Partial<Task>) { await updateTaskInDatabase({ ...task, ...newTaskValues }); await getTasks(); setEditingTaskId(''); setEditedTaskTitle(''); } async function deleteTask(taskId: string) { await deleteTaskFromDatabase(taskId); await getTasks(); } function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; 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"> {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={() => updateTask(task, { title: editedTaskTitle })} // Handle clicking outside input className="flex-grow border border-gray-400 rounded px-3 py-1 mr-2 bg-inherit" /> </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. Ponowne wdrażanie aplikacji w Cloud Run
- Aby wdrożyć aplikację w Cloud Run, uruchom to polecenie:
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 - Jeśli pojawi się prośba, naciśnij
YiEnter, aby potwierdzić, że chcesz kontynuować:Do you want to continue (Y/n)? Y
12. Gratulacje
Z tego modułu dowiesz się, jak:
- Utwórz instancję Cloud SQL for PostgreSQL
- Wdrażanie aplikacji w Cloud Run, która łączy się z bazą danych Cloud SQL
Czyszczenie danych
Cloud SQL nie ma bezpłatnego poziomu i jeśli będziesz z niego korzystać, zostaniesz obciążony opłatami. Aby uniknąć dodatkowych opłat, możesz usunąć projekt w Cloud.
Cloud Run nie nalicza opłat, gdy usługa nie jest używana, ale może zostać pobrana należność za przechowywanie obrazu kontenera w Artifact Registry. Usunięcie projektu Cloud powoduje zaprzestanie naliczania opłat za wszelkie zasoby wykorzystywane w ramach tego projektu.
Jeśli chcesz, usuń projekt:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
Możesz też usunąć niepotrzebne zasoby z dysku Cloud Shell. Możesz:
- Usuń katalog projektu ćwiczeń z programowania:
rm -rf ~/task-app - Ostrzeżenie! Tej czynności nie będzie można cofnąć. Jeśli chcesz usunąć wszystko z Cloud Shell, aby zwolnić miejsce, możesz usunąć cały katalog domowy. Upewnij się, że wszystko, co chcesz zachować, jest zapisane w innym miejscu.
sudo rm -rf $HOME
Ucz się dalej
- Wdrażanie pełnej aplikacji Next.js w Cloud Run z Cloud SQL dla PostgreSQL przy użyciu łącznika Cloud SQL Node.js
- Wdrażanie pełnej aplikacji Angular w Cloud Run z Cloud SQL dla PostgreSQL przy użyciu łącznika Cloud SQL Node.js
- Wdrażanie pełnej aplikacji Angular w Cloud Run z Firestore za pomocą pakietu Node.js Admin SDK
- Wdrażanie aplikacji full stack Next.js w Cloud Run z Firestore za pomocą pakietu Node.js Admin SDK