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. پیش نیازها
- اگر قبلاً یک حساب Google ندارید، باید یک حساب Google ایجاد کنید .
- از حساب شخصی به جای حساب کاری یا مدرسه استفاده کنید. حسابهای محل کار و مدرسه ممکن است محدودیتهایی داشته باشند که شما را از فعال کردن APIهای مورد نیاز برای این آزمایشگاه باز میدارد.
3. راه اندازی پروژه
- به Google Cloud Console وارد شوید.
- صورتحساب را در کنسول Cloud فعال کنید .
- تکمیل این آزمایشگاه باید کمتر از 1 دلار آمریکا در منابع Cloud هزینه داشته باشد.
- برای جلوگیری از هزینه های بیشتر، می توانید مراحل انتهای این آزمایشگاه را برای حذف منابع دنبال کنید.
- کاربران جدید واجد شرایط استفاده از نسخه آزمایشی رایگان 300 دلاری هستند.
- یک پروژه جدید ایجاد کنید یا استفاده مجدد از یک پروژه موجود را انتخاب کنید.
4. Cloud Shell Editor را باز کنید
- به Cloud Shell Editor بروید
- اگر ترمینال در پایین صفحه ظاهر نشد، آن را باز کنید:
- روی منوی همبرگر کلیک کنید
- روی ترمینال کلیک کنید
- روی New Terminal کلیک کنید
- روی منوی همبرگر کلیک کنید
- در ترمینال، پروژه خود را با این دستور تنظیم کنید:
- قالب:
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"
- دستور add-iam-policy-binding پروژه های gcloud را به صورت زیر اجرا کنید تا نقش Log Writer را به حساب سرویس 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 Instance ایجاد کنید
- یک خط مشی اتصال سرویس ایجاد کنید تا امکان اتصال شبکه از 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 create-next-app@15.0.3 task-app \ --ts \ --eslint \ --tailwind \ --no-src-dir \ --turbopack \ --app \ --no-import-alias
- اگر از شما خواسته شد که
create-next-app
نصب کنید، برای ادامهEnter
فشار دهید:Need to install the following packages: create-next-app@15.0.3 Ok to proceed? (y)
- دایرکتوری را به
task-app
تغییر دهید:cd task-app
-
pg
برای تعامل با پایگاه داده PostgreSQL نصب کنید.npm install pg
- برای استفاده از برنامه TypeScript Next.js
@types/pg
به عنوان یک وابستگی توسعه دهنده نصب کنید.npm install --save-dev @types/pg
- فایل
actions.ts
را ایجاد کنید.touch app/actions.ts
- فایل
actions.ts
را در Cloud Shell Editor باز کنید: اکنون باید یک فایل خالی در قسمت بالای صفحه ظاهر شود. اینجاست که می توانید این فایلcloudshell edit app/actions.ts
actions.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 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; }
- فایل
page.tsx
را در Cloud Shell Editor باز کنید: اکنون یک فایل موجود باید در قسمت بالای صفحه ظاهر شود. اینجاست که می توانید این فایلcloudshell edit app/page.tsx
page.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 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 مستقر کنید
- دستور add-iam-policy-binding پروژه های gcloud را به صورت زیر اجرا کنید تا نقش Network User را به حساب سرویس 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"
- دستور زیر را برای استقرار برنامه خود در 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 Editor برگردید
-
page.tsx
دوباره باز کنیدcd ~/task-app cloudshell edit app/page.tsx
- به Gemini Code Assist در ویرایشگر Cloud Shell بروید:
- روی نماد Gemini کلیک کنید
در نوار ابزار سمت چپ صفحه نمایش
- اگر از شما خواسته شد، با اطلاعات کاربری حساب Google خود وارد شوید
- اگر از شما خواسته شد پروژه ای را انتخاب کنید، پروژه ای را که برای این Codelab ایجاد کرده اید انتخاب کنید
- روی نماد Gemini کلیک کنید
- اعلان را وارد کنید:
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>
- خروجی Gemini Code Assist را جایگزین
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[]>([]); 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 قرار دهید
- دستور زیر را برای استقرار برنامه خود در 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 (پیکربندی شده برای استفاده از Private Service Connect )
- برنامه ای را در Cloud Run مستقر کنید که به پایگاه داده Cloud SQL شما متصل می شود
- از Gemini Code Assist برای افزودن قابلیت به برنامه خود استفاده کنید
پاک کن
Cloud SQL لایه رایگان ندارد و در صورت ادامه استفاده از آن هزینه ای از شما دریافت می کند. برای جلوگیری از تحمیل هزینه های اضافی، می توانید پروژه Cloud خود را حذف کنید.
در حالی که Cloud Run زمانی که از سرویس استفاده نمیشود شارژ نمیشود، ممکن است همچنان برای ذخیره تصویر ظرف در Artifact Registry هزینه دریافت کنید. با حذف پروژه Cloud، صورتحساب تمام منابع مورد استفاده در آن پروژه متوقف میشود.
در صورت تمایل پروژه را حذف کنید:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
همچنین ممکن است بخواهید منابع غیر ضروری را از دیسک پوسته ابری خود حذف کنید. شما می توانید:
- دایرکتوری پروژه codelab را حذف کنید:
rm -rf ~/task-app
- هشدار! این اقدام بعدی قابل واگرد نیست! اگر میخواهید همه چیز را در Cloud Shell خود حذف کنید تا فضا آزاد شود، میتوانید کل فهرست اصلی خود را حذف کنید . مراقب باشید هر چیزی که می خواهید نگه دارید در جای دیگری ذخیره شود.
sudo rm -rf $HOME