1. Обзор
Cloud Run — это полностью управляемая платформа, позволяющая запускать код непосредственно поверх масштабируемой инфраструктуры Google. В этом практическом занятии мы продемонстрируем, как подключить приложение Next.js на Cloud Run к базе данных Cloud SQL для PostgreSQL.
В этой лабораторной работе вы научитесь:
- Создайте экземпляр Cloud SQL для PostgreSQL.
- Разверните приложение в Cloud Run, которое подключается к вашей базе данных Cloud SQL.
2. Предварительные требования
- Если у вас еще нет учетной записи Google, вам необходимо ее создать .
- Используйте личный аккаунт вместо рабочего или учебного. Рабочие и учебные аккаунты могут иметь ограничения, которые не позволят вам включить API, необходимые для этой лабораторной работы.
3. Настройка проекта
- Войдите в консоль Google Cloud .
- Включите выставление счетов в облачной консоли.
- Выполнение этой лабораторной работы должно обойтись менее чем в 1 доллар США в виде облачных ресурсов.
- В конце этой лабораторной работы вы можете выполнить действия по удалению ресурсов, чтобы избежать дальнейших списаний средств.
- Новые пользователи могут воспользоваться бесплатной пробной версией стоимостью 300 долларов США .
- Создайте новый проект или выберите вариант повторного использования существующего проекта.
4. Откройте редактор Cloud Shell.
- Перейдите в редактор Cloud Shell.
- Если терминал не отображается в нижней части экрана, откройте его:
- Нажмите на значок гамбургера.

- Нажмите «Терминал»
- Нажмите «Новый терминал»

- Нажмите на значок гамбургера.
- В терминале настройте свой проект с помощью этой команды:
- Формат:
gcloud config set project [PROJECT_ID] - Пример:
gcloud config set project lab-project-id-example - Если вы не помните идентификатор своего проекта:
- Вы можете вывести список всех идентификаторов ваших проектов с помощью:
gcloud projects list | awk '/PROJECT_ID/{print $2}'

- Вы можете вывести список всех идентификаторов ваших проектов с помощью:
- Формат:
- Если появится запрос на авторизацию, нажмите «Авторизовать» , чтобы продолжить.

- Вы должны увидеть следующее сообщение:
Если вы видите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
Если появится запрос на авторизацию, нажмите «Авторизовать» , чтобы продолжить. 
Выполнение этой команды может занять несколько минут, но в итоге должно отобразиться сообщение об успешном завершении, похожее на это:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. Создайте учетную запись службы.
Создайте и настройте учетную запись службы Google Cloud для использования Cloud Run, чтобы она имела необходимые разрешения для подключения к Cloud SQL.
- Для создания новой учетной записи службы выполните команду
gcloud iam service-accounts createследующим образом:gcloud iam service-accounts create quickstart-service-account \ --display-name="Quickstart Service Account" - Чтобы добавить роль Log Writer к только что созданной учетной записи службы Google Cloud, выполните команду gcloud projects add-iam-policy-binding следующим образом.
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.
- Создайте политику подключения к службе, разрешающую сетевое подключение из 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 - Сгенерируйте уникальный пароль для вашей базы данных.
export DB_PASSWORD=$(openssl rand -base64 20) - Выполните команду
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
Выполнение этой команды может занять несколько минут.
- Выполните команду
gcloud sql databases create, чтобы создать базу данных Cloud SQL вquickstart-instance.gcloud sql databases create quickstart_db \ --instance=quickstart-instance
8. Подготовьте заявление.
Подготовьте приложение Next.js, которое будет обрабатывать HTTP-запросы.
- Чтобы создать новый проект Next.js с именем
task-app, используйте команду:npx --yes create-next-app@15 task-app \ --ts \ --eslint \ --tailwind \ --no-src-dir \ --turbopack \ --app \ --no-import-alias - Перейдите в каталог
task-app:cd task-app - Установите
pgдля взаимодействия с базой данных PostgreSQL.npm install pg - Установите
@types/pgв качестве зависимости для разработки, чтобы использовать приложение Next.js на TypeScript.npm install --save-dev @types/pg - Откройте файл
actions.tsв редакторе Cloud Shell: Теперь в верхней части экрана должен появиться пустой файл. Здесь вы можете редактировать файлcloudshell edit app/actions.tsactions.ts.
- Скопируйте следующий код и вставьте его в открытый файл
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; } - Откройте файл
page.tsxв редакторе Cloud Shell: В верхней части экрана должен отобразиться существующий файл. Здесь вы можете редактировать файлcloudshell edit app/page.tsxpage.tsx.
- Удалите существующее содержимое файла
page.tsx. - Скопируйте следующий код и вставьте его в открытый файл
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> ); }
Приложение готово к развертыванию.
9. Разверните приложение в Cloud Run.
- Выполните команду 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"
- Выполните команду gcloud projects add-iam-policy-binding следующим образом, чтобы добавить роль Artifact Registry Writer текущему пользователю для службы Cloud Run, которую вы собираетесь создать.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member=user:$(gcloud auth list --filter=status:ACTIVE --format="value(account)") \ --role="roles/artifactregistry.writer"
- Выполните указанную ниже команду, чтобы развернуть приложение в 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 - Если появится запрос, нажмите
YиEnter, чтобы подтвердить продолжение:Do you want to continue (Y/n)? Y
Через несколько минут приложение должно предоставить вам URL-адрес для перехода по ссылке.
Перейдите по указанному URL-адресу, чтобы увидеть приложение в действии. При каждом посещении URL-адреса или обновлении страницы вы будете видеть приложение для выполнения задач.
10. Добавьте функцию с помощью Gemini Code Assist.
Теперь вы развернули веб-приложение с базой данных. Далее мы добавим новую функцию в наше приложение Next.js, используя возможности искусственного интеллекта.
- Вернуться в редактор Cloud Shell
- Снова откройте
page.tsxcd ~/task-app cloudshell edit app/page.tsx - Перейдите в раздел Gemini Code Assist в редакторе Cloud Shell:
- Нажмите на значок Близнецов
на панели инструментов в левой части экрана - При появлении запроса войдите в систему, используя данные своей учетной записи Google.
- Если появится запрос на выбор проекта, выберите проект, который вы создали для этого практического занятия.

- Нажмите на значок Близнецов
- Введите запрос:
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(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; - Замените
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(''); 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. Переразверните приложение в Cloud Run.
- Выполните указанную ниже команду, чтобы развернуть приложение в 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 - Если появится запрос, нажмите
YиEnter, чтобы подтвердить продолжение:Do you want to continue (Y/n)? Y
12. Поздравляем!
В этой лабораторной работе вы научились выполнять следующие действия:
- Создайте экземпляр Cloud SQL для PostgreSQL.
- Разверните приложение в Cloud Run, которое подключается к вашей базе данных Cloud SQL.
Уборка
В Cloud SQL нет бесплатного тарифа, и за дальнейшее использование взимается плата. Вы можете удалить свой проект в Cloud, чтобы избежать дополнительных расходов.
Хотя Cloud Run не взимает плату, когда услуга не используется, с вас все равно может взиматься плата за хранение образа контейнера в реестре артефактов. Удаление вашего проекта Cloud прекращает выставление счетов за все ресурсы, используемые в этом проекте.
При желании вы можете удалить проект:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
Вы также можете удалить ненужные ресурсы с диска Cloudshell. Для этого выполните следующие действия:
- Удалите каталог проекта codelab:
rm -rf ~/task-app - Внимание! Следующее действие необратимо! Если вы хотите удалить все данные в Cloud Shell, чтобы освободить место, вы можете удалить всю свою домашнюю директорию . Будьте внимательны и убедитесь, что все, что вы хотите сохранить, сохранено в другом месте.
sudo rm -rf $HOME
Продолжайте учиться
- Разверните полнофункциональное приложение Next.js в Cloud Run с помощью Cloud SQL для PostgreSQL, используя коннектор Cloud SQL Node.js.
- Разверните полнофункциональное Angular-приложение в Cloud Run с помощью Cloud SQL для PostgreSQL, используя коннектор Cloud SQL Node.js.
- Разверните полнофункциональное Angular-приложение в Cloud Run с помощью Firestore, используя Node.js Admin SDK.
- Разверните полнофункциональное приложение Next.js в Cloud Run с Firestore, используя Node.js Admin SDK.