Informationen zu diesem Codelab
1. Übersicht
Cloud Run ist eine vollständig verwaltete Plattform, mit der Sie Ihren Code direkt auf der skalierbaren Infrastruktur von Google ausführen können. In diesem Codelab wird gezeigt, wie Sie eine Next.js-Anwendung in Cloud Run mit einer Cloud SQL for PostgreSQL-Datenbank verbinden.
Aufgaben in diesem Lab:
- Cloud SQL for PostgreSQL-Instanz erstellen (konfiguriert für die Verwendung von Private Service Connect)
- Anwendung in Cloud Run bereitstellen, die eine Verbindung zu Ihrer Cloud SQL-Datenbank herstellt
- Mit Gemini Code Assist Funktionen zu Ihrer Anwendung hinzufügen
2. Vorbereitung
- Wenn Sie noch kein Google-Konto haben, müssen Sie ein Google-Konto erstellen.
- Sie verwenden ein privates Konto anstelle eines Arbeits- oder Schulkontos. Für geschäftliche und schulische Konten gelten möglicherweise Einschränkungen, die Sie daran hindern, die für dieses Lab erforderlichen APIs zu aktivieren.
3. Projekt einrichten
- Melden Sie sich in der Google Cloud Console an.
- Aktivieren Sie die Abrechnung in der Cloud Console.
- Die Kosten für die Durchführung dieses Labs sollten unter 1 $liegen.
- Sie können die Schritte am Ende dieses Labs ausführen, um Ressourcen zu löschen und weitere Kosten zu vermeiden.
- Neue Nutzer können das kostenlose Testabo mit einem Guthaben von 300$ nutzen.
- Erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes.
4. Cloud Shell-Editor öffnen
- Rufen Sie den Cloud Shell-Editor auf.
- Wenn das Terminal nicht unten auf dem Bildschirm angezeigt wird, öffnen Sie es:
- Klicke auf das Dreistrich-Menü
.
- Klicken Sie auf Terminal.
- Klicken Sie auf Neues Terminal
.
- Klicke auf das Dreistrich-Menü
- Legen Sie das Projekt im Terminal mit diesem Befehl fest:
- Format:
gcloud config set project [PROJECT_ID]
- Beispiel:
gcloud config set project lab-project-id-example
- Wenn Sie sich Ihre Projekt-ID nicht merken können:
- So listen Sie alle Ihre Projekt-IDs auf:
gcloud projects list | awk '/PROJECT_ID/{print $2}'
- So listen Sie alle Ihre Projekt-IDs auf:
- Format:
- Wenn Sie dazu aufgefordert werden, klicken Sie auf Autorisieren, um fortzufahren.
- Es sollte folgende Meldung angezeigt werden:
Wenn SieUpdated property [core/project].
WARNING
sehen undDo you want to continue (Y/N)?
gefragt werden, haben Sie wahrscheinlich die Projekt-ID falsch eingegeben. Drücken SieN
,Enter
und versuchen Sie noch einmal, den Befehlgcloud config set project
auszuführen.
5. APIs aktivieren
Aktivieren Sie im Terminal die APIs:
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
Wenn Sie dazu aufgefordert werden, klicken Sie auf Autorisieren, um fortzufahren.
Die Verarbeitung dieses Befehls kann einige Minuten dauern. Sie sollten aber schließlich eine Meldung ähnlich der folgenden erhalten:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. Dienstkonto einrichten
Erstellen und konfigurieren Sie ein Google Cloud-Dienstkonto, das von Cloud Run verwendet werden soll, damit es die richtigen Berechtigungen zum Herstellen einer Verbindung zu Cloud SQL hat.
- Führen Sie den Befehl
gcloud iam service-accounts create
so aus, um ein neues Dienstkonto zu erstellen:gcloud iam service-accounts create quickstart-service-account \
--display-name="Quickstart Service Account" - Führen Sie den Befehl „gcloud projects add-iam-policy-binding“ wie unten beschrieben aus, um dem gerade erstellten Google Cloud-Dienstkonto die Rolle Logautor hinzuzufügen.
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-Instanz erstellen
- Richtlinie für Dienstverbindungen erstellen, um die Netzwerkverbindung von Cloud Run zu Cloud SQL mit Private Service Connect zuzulassen
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 - Ein eindeutiges Passwort für Ihre Datenbank generieren
export DB_PASSWORD=$(openssl rand -base64 20)
- Befehl
gcloud sql instances create
ausführen, um eine Cloud SQL-Instanz zu erstellengcloud 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
Die Verarbeitung dieses Befehls kann einige Minuten dauern.
- Führen Sie den Befehl
gcloud sql databases create
aus, um eine Cloud SQL-Datenbank in derquickstart-instance
zu erstellen.gcloud sql databases create quickstart_db \
--instance=quickstart-instance
8. Anwendung vorbereiten
Bereiten Sie eine Next.js-Anwendung vor, die auf HTTP-Anfragen reagiert.
- Verwenden Sie den folgenden Befehl, um ein neues Next.js-Projekt mit dem Namen
task-app
zu erstellen:npx create-next-app@15.0.3 task-app \
--ts \
--eslint \
--tailwind \
--no-src-dir \
--turbopack \
--app \
--no-import-alias - Wenn Sie aufgefordert werden,
create-next-app
zu installieren, drücken SieEnter
, um fortzufahren:Need to install the following packages:
create-next-app@15.0.3
Ok to proceed? (y) - Ändern Sie das Verzeichnis in
task-app
:cd task-app
- Installieren Sie
pg
, um mit der PostgreSQL-Datenbank zu interagieren.npm install pg
- Installieren Sie
@types/pg
als Entwicklungsabhängigkeit, um TypeScript Next.js-Anwendungen zu verwenden.npm install --save-dev @types/pg
- Erstellen Sie die Datei
actions.ts
.touch app/actions.ts
- Öffnen Sie die Datei
actions.ts
im Cloud Shell-Editor: Oben auf dem Bildschirm sollte jetzt eine leere Datei angezeigt werden. Hier können Sie die Dateicloudshell edit app/actions.ts
actions.ts
bearbeiten. - Kopieren Sie den folgenden Code und fügen Sie ihn in die geöffnete
actions.ts
-Datei ein:'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;
} - Öffnen Sie die Datei
page.tsx
im Cloud Shell-Editor: Oben auf dem Bildschirm sollte jetzt eine vorhandene Datei angezeigt werden. Hier können Sie die Dateicloudshell edit app/page.tsx
page.tsx
bearbeiten. - Löschen Sie den vorhandenen Inhalt der Datei
page.tsx
. - Kopieren Sie den folgenden Code und fügen Sie ihn in die geöffnete
page.tsx
-Datei ein:'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>
);
}
Die Anwendung kann jetzt bereitgestellt werden.
9. Anwendung in Cloud Run bereitstellen
- Führen Sie den Befehl „gcloud projects add-iam-policy-binding“ so aus, um dem Cloud Run-Dienstkonto für den Cloud Run-Dienst, den Sie gleich erstellen, die Rolle Netzwerknutzer hinzuzufügen.
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"
- Führen Sie den folgenden Befehl aus, um die Anwendung in Cloud Run bereitzustellen:
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 - Wenn Sie dazu aufgefordert werden, drücken Sie
Y
undEnter
, um zu bestätigen, dass Sie fortfahren möchten:Do you want to continue (Y/n)? Y
Nach einigen Minuten sollte die Anwendung eine URL anzeigen, die Sie aufrufen können.
Rufen Sie die URL auf, um Ihre Anwendung in Aktion zu sehen. Jedes Mal, wenn Sie die URL aufrufen oder die Seite aktualisieren, wird die Aufgaben-App angezeigt.
10. Mit Gemini Code Assist eine Funktion hinzufügen
Sie haben jetzt eine Webanwendung mit einer Datenbank bereitgestellt. Als Nächstes fügen wir unserer Next.js-App mithilfe von KI-Unterstützung eine neue Funktion hinzu.
- Zum Cloud Shell-Editor zurückkehren
page.tsx
wieder öffnencd ~/task-app
cloudshell edit app/page.tsx- Rufen Sie Gemini Code Assist im Cloud Shell-Editor auf:
- Klicken Sie in der Symbolleiste auf der linken Seite des Bildschirms auf das Gemini-Symbol
.
- Melden Sie sich mit den Anmeldedaten Ihres Google-Kontos an, wenn Sie dazu aufgefordert werden.
- Wenn Sie aufgefordert werden, ein Projekt auszuwählen, wählen Sie das Projekt aus, das Sie für dieses Codelab erstellt haben.
- Klicken Sie in der Symbolleiste auf der linken Seite des Bildschirms auf das Gemini-Symbol
- Geben Sie den Prompt ein:
Add the ability to update the title of the task. The code in your output should be complete and working code.
. Die Antwort sollte in etwa so aussehen, umhandleEditStart
- undhandleEditCancel
-Funktionen hinzuzufügen: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> - Ersetzen Sie
page.tsx
durch die Ausgabe von Gemini Code Assist. Hier ein Beispiel:'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. Anwendung noch einmal in Cloud Run bereitstellen
- Führen Sie den folgenden Befehl aus, um die Anwendung in Cloud Run bereitzustellen:
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 - Wenn Sie dazu aufgefordert werden, drücken Sie
Y
undEnter
, um zu bestätigen, dass Sie fortfahren möchten:Do you want to continue (Y/n)? Y
12. Glückwunsch
In diesem Lab haben Sie Folgendes gelernt:
- Cloud SQL for PostgreSQL-Instanz erstellen (konfiguriert für die Verwendung von Private Service Connect)
- Anwendung in Cloud Run bereitstellen, die eine Verbindung zu Ihrer Cloud SQL-Datenbank herstellt
- Mit Gemini Code Assist Funktionen zu Ihrer Anwendung hinzufügen
Bereinigen
Cloud SQL ist nicht kostenlos. Wenn Sie die Plattform weiter nutzen, werden Ihnen Kosten in Rechnung gestellt. Sie können Ihr Cloud-Projekt löschen, um zusätzliche Kosten zu vermeiden.
Für Cloud Run fallen keine Kosten an, wenn der Dienst nicht verwendet wird. Allerdings können für das Speichern des Container-Images in Artifact Registry Gebühren berechnet werden. Wenn Sie Ihr Cloud-Projekt löschen, wird die Abrechnung für alle in diesem Projekt verwendeten Ressourcen beendet.
Sie können das Projekt löschen:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
Sie können auch unnötige Ressourcen von Ihrem CloudShell-Laufwerk löschen. Sie haben folgende Möglichkeiten:
- Löschen Sie das Codelab-Projektverzeichnis:
rm -rf ~/task-app
- Warnung! Diese nächste Aktion kann nicht rückgängig gemacht werden. Wenn Sie alles in Cloud Shell löschen möchten, um Speicherplatz freizugeben, können Sie Ihr ganzes Basisverzeichnis löschen. Achten Sie darauf, dass alles, was Sie behalten möchten, an einem anderen Ort gespeichert ist.
sudo rm -rf $HOME