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 Angular-Anwendung in Cloud Run mit einer Firestore-Datenbank verbinden. Dazu wird das Node.js Admin SDK verwendet.
Aufgaben in diesem Lab:
- Firestore-Datenbank erstellen
- Anwendung in Cloud Run bereitstellen, die eine Verbindung zu Ihrer Firestore-Datenbank herstellt
2. Vorbereitung
- Wenn Sie noch kein Google-Konto haben, müssen Sie ein Google-Konto erstellen.
- Verwenden Sie stattdessen ein privates Konto. Bei Arbeitskonten und Konten von Bildungseinrichtungen kann es Einschränkungen geben, die verhindern, dass Sie die für dieses Lab erforderlichen APIs aktivieren.
3. Projekt einrichten
- Melden Sie sich in der Google Cloud Console an.
- Aktivieren Sie die Abrechnung in der Cloud Console.
- Die Cloud-Ressourcen, die für dieses Lab benötigt werden, sollten weniger als 1 $kosten.
- Sie können die Schritte am Ende dieses Labs ausführen, um Ressourcen zu löschen und so weitere Kosten zu vermeiden.
- Neue Nutzer haben Anspruch auf die kostenlose Testversion mit einem Guthaben von 300$.
- Erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt wieder.
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:
- Klicken Sie auf das Dreistrich-Menü
. - Klicken Sie auf Terminal.
- Klicken Sie auf Neues Terminal
.
- Klicken Sie auf das Dreistrich-Menü
- Legen Sie im Terminal Ihr Projekt mit diesem Befehl fest:
- Format:
gcloud config set project [PROJECT_ID] - Beispiel:
gcloud config set project lab-project-id-example - Wenn Sie sich nicht an Ihre Projekt-ID erinnern können:
- Sie können alle Ihre Projekt-IDs mit folgendem Befehl auflisten:
gcloud projects list | awk '/PROJECT_ID/{print $2}'

- Sie können alle Ihre Projekt-IDs mit folgendem Befehl auflisten:
- Format:
- Wenn Sie zur Autorisierung aufgefordert werden, klicken Sie auf Autorisieren, um fortzufahren.

- Es sollte folgende Meldung angezeigt werden:
Wenn SieUpdated property [core/project].
WARNINGsehen undDo you want to continue (Y/N)?gefragt werden, haben Sie die Projekt-ID wahrscheinlich falsch eingegeben. Drücken SieN, dannEnterund versuchen Sie, den Befehlgcloud config set projectnoch einmal auszuführen.
5. APIs aktivieren
Aktivieren Sie die APIs im Terminal:
gcloud services enable \
firestore.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
Wenn Sie zur Autorisierung aufgefordert werden, klicken Sie auf Autorisieren, um fortzufahren. 
Es kann einige Minuten dauern, bis dieser Befehl ausgeführt wird. Wenn die Ausführung erfolgreich war, erhalten Sie eine Meldung, die ungefähr so aussieht:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. Firestore-Datenbank erstellen
- Mit dem Befehl
gcloud firestore databases createeine Firestore-Datenbank erstellengcloud firestore databases create --location=nam5
7. 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-appzu erstellen:npx --yes @angular/cli@19.2.5 new task-app \ --minimal \ --inline-template \ --inline-style \ --ssr \ --server-routing \ --defaults - Ändern Sie das Verzeichnis in
task-app:cd task-app
- Installieren Sie
firebase-admin, um mit der Firestore-Datenbank zu interagieren.npm install firebase-admin
- Öffnen Sie die Datei
server.tsim Cloud Shell-Editor: Oben auf dem Bildschirm sollte jetzt eine Datei angezeigt werden. Hier können Sie die Dateicloudshell edit src/server.tsserver.tsbearbeiten.
- Löschen Sie den vorhandenen Inhalt der Datei
server.ts. - Kopieren Sie den folgenden Code und fügen Sie ihn in die geöffnete Datei
server.tsein: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 { initializeApp, applicationDefault, getApps } from 'firebase-admin/app'; import { getFirestore } from 'firebase-admin/firestore'; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; createdAt: number; }; const credential = applicationDefault(); // Only initialize app if it does not already exist if (getApps().length === 0) { initializeApp({ credential }); } const db = getFirestore(); const tasksRef = db.collection('tasks'); 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) => { const snapshot = await tasksRef.orderBy('createdAt', 'desc').limit(100).get(); const tasks: Task[] = snapshot.docs.map(doc => ({ id: doc.id, title: doc.data()['title'], status: doc.data()['status'], createdAt: doc.data()['createdAt'], })); res.send(tasks); }); app.post('/api/tasks', async (req, res) => { const newTaskTitle = req.body.title; if(!newTaskTitle){ res.status(400).send("Title is required"); return; } await tasksRef.doc().create({ title: newTaskTitle, status: 'IN_PROGRESS', createdAt: Date.now(), }); 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 tasksRef.doc(task.id).set(task); 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 tasksRef.doc(task.id).delete(); 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); - Öffnen Sie die Datei
angular.jsonim Cloud Shell-Editor: Wir fügen jetzt die Zeilecloudshell edit angular.json"externalDependencies": ["firebase-admin"]in die Dateiangular.jsonein. - Löschen Sie den vorhandenen Inhalt der Datei
angular.json. - Kopieren Sie den folgenden Code und fügen Sie ihn in die geöffnete Datei
angular.jsonein:{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "task-app": { "projectType": "application", "schematics": { "@schematics/angular:component": { "inlineTemplate": true, "inlineStyle": true, "skipTests": true }, "@schematics/angular:class": { "skipTests": true }, "@schematics/angular:directive": { "skipTests": true }, "@schematics/angular:guard": { "skipTests": true }, "@schematics/angular:interceptor": { "skipTests": true }, "@schematics/angular:pipe": { "skipTests": true }, "@schematics/angular:resolver": { "skipTests": true }, "@schematics/angular:service": { "skipTests": true } }, "root": "", "sourceRoot": "src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/task-app", "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ "zone.js" ], "tsConfig": "tsconfig.app.json", "assets": [ { "glob": "**/*", "input": "public" } ], "styles": [ "src/styles.css" ], "scripts": [], "server": "src/main.server.ts", "outputMode": "server", "ssr": { "entry": "src/server.ts" }, "externalDependencies": ["firebase-admin"] }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" }, { "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" } ], "outputHashing": "all" }, "development": { "optimization": false, "extractLicenses": false, "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "task-app:build:production" }, "development": { "buildTarget": "task-app:build:development" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n" } } } } }
"externalDependencies": ["firebase-admin"]
- Öffnen Sie die Datei
app.component.tsim Cloud Shell-Editor: Oben auf dem Bildschirm sollte jetzt eine vorhandene Datei angezeigt werden. Hier können Sie die Dateicloudshell edit src/app/app.component.tsapp.component.tsbearbeiten.
- Löschen Sie den vorhandenen Inhalt der Datei
app.component.ts. - Kopieren Sie den folgenden Code und fügen Sie ihn in die geöffnete Datei
app.component.tsein: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(); } }
Die Anwendung kann jetzt bereitgestellt werden.
8. Anwendung 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=. - Wenn Sie dazu aufgefordert werden, drücken Sie
YundEnter, 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 bereitstellen, 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.
9. Glückwunsch
In diesem Lab haben Sie Folgendes gelernt:
- Cloud SQL for PostgreSQL-Instanz erstellen
- Anwendung in Cloud Run bereitstellen, die eine Verbindung zu Ihrer Cloud SQL-Datenbank herstellt
Bereinigen
Cloud SQL bietet kein kostenloses Kontingent. Wenn Sie den Dienst weiterhin nutzen, werden Ihnen die Kosten in Rechnung gestellt. Sie können Ihr Cloud-Projekt löschen, um zusätzliche Gebühren zu vermeiden.
Während für Cloud Run keine Kosten anfallen, wenn der Dienst nicht verwendet wird, wird Ihnen dennoch das Speichern des Container-Images in Artifact Registry möglicherweise in Rechnung gestellt. Durch das Löschen des Cloud-Projekts wird die Abrechnung für alle in diesem Projekt verwendeten Ressourcen beendet.
Wenn Sie möchten, können Sie das Projekt löschen:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
Sie können auch unnötige Ressourcen von Ihrer Cloud Shell-Festplatte löschen. Sie haben folgende Möglichkeiten:
- Löschen Sie das Codelab-Projektverzeichnis:
rm -rf ~/task-app - Warnung! Diese Aktion kann nicht rückgängig gemacht werden. Wenn Sie alles in Ihrer Cloud Shell löschen möchten, um Speicherplatz freizugeben, können Sie Ihr gesamtes Basisverzeichnis löschen. Achten Sie darauf, dass alles, was Sie behalten möchten, an einem anderen Ort gespeichert ist.
sudo rm -rf $HOME
Weiterlernen
- Vollständige Next.js-Anwendung mit Cloud SQL for PostgreSQL in Cloud Run bereitstellen
- Vollständige Angular-Anwendung in Cloud Run mit Cloud SQL for PostgreSQL mithilfe des Cloud SQL Node.js-Connectors bereitstellen
- Vollständige Angular-Anwendung mit Firestore in Cloud Run bereitstellen (mit dem Node.js Admin SDK)
- Full-Stack-Next.js-Anwendung mit Firestore und dem Node.js Admin SDK in Cloud Run bereitstellen