1. Обзор
Cloud Run — это полностью управляемая платформа, позволяющая запускать код непосредственно поверх масштабируемой инфраструктуры Google. В этой лаборатории кода будет показано, как подключить приложение Angular в Cloud Run к базе данных Cloud SQL для PostgreSQL с помощью коннектора Cloud SQL Node.js.
В этой лабораторной работе вы научитесь:
- Создайте экземпляр Cloud SQL для PostgreSQL.
- Разверните приложение в Cloud Run, которое подключается к вашей базе данных Cloud SQL.
2. Предварительные условия
- Если у вас еще нет учетной записи Google, вам необходимо создать учетную запись Google .
- Используйте личную учетную запись вместо рабочей или учебной учетной записи. Рабочие и учебные учетные записи могут иметь ограничения, не позволяющие вам включить API, необходимые для этой лабораторной работы.
3. Настройка проекта
- Войдите в Google Cloud Console .
- Включите биллинг в Cloud Console.
- Завершение этой лабораторной работы должно стоить менее 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 \
sqladmin.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.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"
- Запустите команду gcloud project add-iam-policy-binding следующим образом, чтобы добавить роль Cloud SQL Client в только что созданную учетную запись службы Google Cloud.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --role="roles/cloudsql.client"
- Запустите команду gcloud project add-iam-policy-binding следующим образом, чтобы добавить роль пользователя экземпляра Cloud SQL к только что созданной учетной записи службы Google Cloud.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --role="roles/cloudsql.instanceUser"
- Запустите команду gcloud project 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.
- Запустите команду
gcloud sql instances create
чтобы создать экземпляр Cloud SQL.gcloud sql instances create quickstart-instance \ --database-version=POSTGRES_14 \ --cpu=4 \ --memory=16GB \ --region=us-central1 \ --database-flags=cloudsql.iam_authentication=on
Выполнение этой команды может занять несколько минут.
- Запустите команду
gcloud sql databases create
, чтобы создать базу данных Cloud SQL вquickstart-instance
.gcloud sql databases create quickstart_db \ --instance=quickstart-instance
- Создайте пользователя базы данных PostgreSQL для учетной записи службы, которую вы создали ранее, для доступа к базе данных.
gcloud sql users create quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam \ --instance=quickstart-instance \ --type=cloud_iam_service_account
8. Подготовьте заявку
Подготовьте приложение Next.js, отвечающее на HTTP-запросы.
- Чтобы создать новый проект Next.js с именем
task-app
, используйте команду:npx --yes @angular/cli@19.2.5 new task-app \ --minimal \ --inline-template \ --inline-style \ --ssr \ --server-routing \ --defaults
- Измените каталог на
task-app
:cd task-app
- Установите
pg
и библиотеку коннектора Cloud SQL Node.js для взаимодействия с базой данных PostgreSQL.npm install pg @google-cloud/cloud-sql-connector google-auth-library
- Установите
@types/pg
в качестве зависимости для разработчиков, чтобы использовать приложение TypeScript Next.js.npm install --save-dev @types/pg
- Откройте файл
server.ts
в редакторе Cloud Shell: Теперь файл должен появиться в верхней части экрана. Здесь вы можете редактировать файлcloudshell edit src/server.ts
server.ts
. - Удалите существующее содержимое файла
server.ts
. - Скопируйте следующий код и вставьте его в открытый файл
server.ts
:import { AngularNodeAppEngine, createNodeRequestHandler, isMainModule, writeResponseToNodeResponse, } from '@angular/ssr/node'; import express from 'express'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import pg from 'pg'; import { AuthTypes, Connector } from '@google-cloud/cloud-sql-connector'; import { GoogleAuth } from 'google-auth-library'; const auth = new GoogleAuth(); const { Pool } = pg; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; createdAt: number; }; const projectId = await auth.getProjectId(); const connector = new Connector(); const clientOpts = await connector.getOptions({ instanceConnectionName: `${projectId}:us-central1:quickstart-instance`, authType: AuthTypes.IAM, }); const pool = new Pool({ ...clientOpts, user: `quickstart-service-account@${projectId}.iam`, database: 'quickstart_db', }); 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) );`); } const serverDistFolder = dirname(fileURLToPath(import.meta.url)); const browserDistFolder = resolve(serverDistFolder, '../browser'); const app = express(); const angularApp = new AngularNodeAppEngine(); app.use(express.json()); app.get('/api/tasks', async (req, res) => { await tableCreationIfDoesNotExist(); const { rows } = await pool.query(`SELECT id, created_at, status, title FROM tasks ORDER BY created_at DESC LIMIT 100`); res.send(rows); }); app.post('/api/tasks', async (req, res) => { const newTaskTitle = req.body.title; if (!newTaskTitle) { res.status(400).send("Title is required"); return; } await tableCreationIfDoesNotExist(); await pool.query(`INSERT INTO tasks(created_at, status, title) VALUES(NOW(), 'IN_PROGRESS', $1)`, [newTaskTitle]); res.sendStatus(200); }); app.put('/api/tasks', async (req, res) => { const task: Task = req.body; if (!task || !task.id || !task.title || !task.status) { res.status(400).send("Invalid task data"); return; } await tableCreationIfDoesNotExist(); await pool.query( `UPDATE tasks SET status = $1, title = $2 WHERE id = $3`, [task.status, task.title, task.id] ); res.sendStatus(200); }); app.delete('/api/tasks', async (req, res) => { const task: Task = req.body; if (!task || !task.id) { res.status(400).send("Task ID is required"); return; } await tableCreationIfDoesNotExist(); await pool.query(`DELETE FROM tasks WHERE id = $1`, [task.id]); res.sendStatus(200); }); /** * Serve static files from /browser */ app.use( express.static(browserDistFolder, { maxAge: '1y', index: false, redirect: false, }), ); /** * Handle all other requests by rendering the Angular application. */ app.use('/**', (req, res, next) => { angularApp .handle(req) .then((response) => response ? writeResponseToNodeResponse(response, res) : next(), ) .catch(next); }); /** * Start the server if this module is the main entry point. * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000. */ if (isMainModule(import.meta.url)) { const port = process.env['PORT'] || 4000; app.listen(port, () => { console.log(`Node Express server listening on http://localhost:${port}`); }); } /** * Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions. */ export const reqHandler = createNodeRequestHandler(app);
- Откройте файл
app.component.ts
в редакторе Cloud Shell: Существующий файл теперь должен появиться в верхней части экрана. Здесь вы можете редактировать файлcloudshell edit src/app/app.component.ts
app.component.ts
. - Удалите существующее содержимое файла
app.component.ts
. - Скопируйте следующий код и вставьте его в открытый файл
app.component.ts
:import { afterNextRender, Component, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; createdAt: number; }; @Component({ selector: 'app-root', standalone: true, imports: [FormsModule], template: ` <section> <input type="text" placeholder="New Task Title" [(ngModel)]="newTaskTitle" class="text-black border-2 p-2 m-2 rounded" /> <button (click)="addTask()">Add new task</button> <table> <tbody> @for (task of tasks(); track task) { @let isComplete = task.status === 'COMPLETE'; <tr> <td> <input (click)="updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })" type="checkbox" [checked]="isComplete" /> </td> <td>{{ task.title }}</td> <td>{{ task.status }}</td> <td> <button (click)="deleteTask(task)">Delete</button> </td> </tr> } </tbody> </table> </section> `, styles: '', }) export class AppComponent { newTaskTitle = ''; tasks = signal<Task[]>([]); constructor() { afterNextRender({ earlyRead: () => this.getTasks() }); } async getTasks() { const response = await fetch(`/api/tasks`); const tasks = await response.json(); this.tasks.set(tasks); } async addTask() { await fetch(`/api/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: this.newTaskTitle, status: 'IN_PROGRESS', createdAt: Date.now(), }), }); this.newTaskTitle = ''; await this.getTasks(); } async updateTask(task: Task, newTaskValues: Partial<Task>) { await fetch(`/api/tasks`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...task, ...newTaskValues }), }); await this.getTasks(); } async deleteTask(task: any) { await fetch('/api/tasks', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(task), }); await this.getTasks(); } }
Теперь приложение готово к развертыванию.
9. Разверните приложение в Cloud Run.
- Выполните команду ниже, чтобы развернуть приложение в Cloud Run:
gcloud run deploy to-do-tracker \ --region=us-central1 \ --source=. \ --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --allow-unauthenticated
- При появлении запроса нажмите
Y
иEnter
, чтобы подтвердить, что вы хотите продолжить:Do you want to continue (Y/n)? Y
Через несколько минут приложение должно предоставить вам URL-адрес для посещения.
Перейдите по URL-адресу, чтобы увидеть свое приложение в действии. Каждый раз, когда вы посещаете URL-адрес или обновляете страницу, вы увидите приложение задач.
10. Поздравления
В ходе этой лабораторной работы вы научились делать следующее:
- Создайте экземпляр Cloud SQL для PostgreSQL.
- Разверните приложение в Cloud Run, которое подключается к вашей базе данных Cloud SQL.
Очистить
У Cloud SQL нет уровня бесплатного пользования, и если вы продолжите его использовать, с вас будет взиматься плата. Вы можете удалить свой облачный проект, чтобы избежать дополнительных расходов.
Хотя Cloud Run не взимает плату, когда служба не используется, с вас все равно может взиматься плата за хранение образа контейнера в реестре артефактов. При удалении облачного проекта прекращается выставление счетов за все ресурсы, используемые в этом проекте.
Если хотите, удалите проект:
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.