À propos de cet atelier de programmation
1. Présentation
Cloud Run est une plate-forme entièrement gérée qui vous permet d'exécuter votre code directement sur l'infrastructure évolutive de Google. Cet atelier de programmation montre comment connecter une application Angular sur Cloud Run à une base de données Cloud SQL pour PostgreSQL à l'aide du connecteur Node.js Cloud SQL.
Dans cet atelier, vous allez apprendre à effectuer les tâches suivantes :
- Créer une instance Cloud SQL pour PostgreSQL
- Déployer une application sur Cloud Run qui se connecte à votre base de données Cloud SQL
2. Prérequis
- Si vous ne possédez pas encore de compte Google, vous devez en créer un.
- Vous utilisez un compte personnel au lieu d'un compte professionnel ou scolaire. Les comptes professionnels et scolaires peuvent être soumis à des restrictions qui vous empêchent d'activer les API nécessaires à cet atelier.
3. Configuration du projet
- Connectez-vous à la console Google Cloud.
- Activez la facturation dans la console Cloud.
- La réalisation de cet atelier devrait coûter moins de 1 USD en ressources Cloud.
- Vous pouvez suivre les étapes à la fin de cet atelier pour supprimer les ressources afin d'éviter que des frais supplémentaires ne vous soient facturés.
- Les nouveaux utilisateurs peuvent bénéficier d'un essai sans frais pour bénéficier d'un crédit de 300$.
- Créez un projet ou choisissez de réutiliser un projet existant.
4. Ouvrir l'éditeur Cloud Shell
- Accédez à l'éditeur Cloud Shell.
- Si le terminal ne s'affiche pas en bas de l'écran, ouvrez-le:
- Cliquez sur le menu hamburger
.
- Cliquez sur Terminal
- Cliquez sur Nouveau terminal
- Cliquez sur le menu hamburger
- Dans le terminal, définissez votre projet à l'aide de la commande suivante:
- Format :
gcloud config set project [PROJECT_ID]
- Exemple :
gcloud config set project lab-project-id-example
- Si vous ne vous souvenez pas de l'ID de votre projet:
- Vous pouvez lister tous vos ID de projet avec:
gcloud projects list | awk '/PROJECT_ID/{print $2}'
- Vous pouvez lister tous vos ID de projet avec:
- Format :
- Si vous y êtes invité, cliquez sur Autoriser pour continuer.
- Le message suivant doit s'afficher :
Si unUpdated property [core/project].
WARNING
s'affiche et que vous êtes invité à saisirDo you want to continue (Y/N)?
, vous avez probablement saisi l'ID de projet de manière incorrecte. Appuyez surN
, puis surEnter
, puis essayez d'exécuter à nouveau la commandegcloud config set project
.
5. Activer les API
Dans le terminal, activez les API:
gcloud services enable \
sqladmin.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
Si vous y êtes invité, cliquez sur Autoriser pour continuer.
L'exécution de cette commande peut prendre quelques minutes, mais un message semblable à celui-ci devrait s'afficher pour vous indiquer que l'opération s'est correctement déroulée:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. Configurer un compte de service
Créez et configurez un compte de service Google Cloud à utiliser par Cloud Run afin qu'il dispose des autorisations appropriées pour se connecter à Cloud SQL.
- Pour créer un compte de service, exécutez la commande
gcloud iam service-accounts create
comme suit:gcloud iam service-accounts create quickstart-service-account \
--display-name="Quickstart Service Account" - Exécutez la commande gcloud projects add-iam-policy-binding comme suit pour ajouter le rôle Client Cloud SQL au compte de service Google Cloud que vous venez de créer.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
--member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
--role="roles/cloudsql.client" - Exécutez la commande gcloud projects add-iam-policy-binding comme suit pour ajouter le rôle Utilisateur de l'instance Cloud SQL au compte de service Google Cloud que vous venez de créer.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
--member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
--role="roles/cloudsql.instanceUser" - Exécutez la commande gcloud projects add-iam-policy-binding comme suit pour ajouter le rôle Rédacteur de journal au compte de service Google Cloud que vous venez de créer.
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. Créer une base de données Cloud SQL
- Exécuter la commande
gcloud sql instances create
pour créer une instance Cloud SQLgcloud sql instances create quickstart-instance \
--database-version=POSTGRES_14 \
--cpu=4 \
--memory=16GB \
--region=us-central1 \
--database-flags=cloudsql.iam_authentication=on
L'exécution de cette commande peut prendre quelques minutes.
- Exécutez la commande
gcloud sql databases create
pour créer une base de données Cloud SQL dansquickstart-instance
.gcloud sql databases create quickstart_db \
--instance=quickstart-instance - Créez un utilisateur de base de données PostgreSQL pour le compte de service que vous avez créé précédemment afin d'accéder à la base de données.
gcloud sql users create quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam \
--instance=quickstart-instance \
--type=cloud_iam_service_account
8. Préparer l'application
Préparez une application Next.js qui répond aux requêtes HTTP.
- Pour créer un projet Next.js nommé
task-app
, utilisez la commande suivante :npx --yes @angular/cli@19.2.5 new task-app \
--minimal \
--inline-template \
--inline-style \
--ssr \
--server-routing \
--defaults - Remplacez le répertoire par
task-app
:cd task-app
- Installez
pg
et la bibliothèque de connecteurs Cloud SQL Node.js pour interagir avec la base de données PostgreSQL.npm install pg @google-cloud/cloud-sql-connector google-auth-library
- Installez
@types/pg
en tant que dépendance de développement pour utiliser une application TypeScript Next.js.npm install --save-dev @types/pg
- Ouvrez le fichier
server.ts
dans l'éditeur Cloud Shell: Un fichier devrait maintenant s'afficher en haut de l'écran. C'est là que vous pouvez modifier ce fichiercloudshell edit src/server.ts
server.ts
. - Supprimez le contenu existant du fichier
server.ts
. - Copiez le code suivant et collez-le dans le fichier
server.ts
ouvert: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);
- Ouvrez le fichier
app.component.ts
dans l'éditeur Cloud Shell: Un fichier existant devrait maintenant s'afficher en haut de l'écran. C'est là que vous pouvez modifier ce fichiercloudshell edit src/app/app.component.ts
app.component.ts
. - Supprimez le contenu existant du fichier
app.component.ts
. - Copiez le code suivant et collez-le dans le fichier
app.component.ts
ouvert: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();
}
}
L'application est maintenant prête à être déployée.
9. Déployer l'application dans Cloud Run
- Exécutez la commande ci-dessous pour déployer votre application sur 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 - Si vous y êtes invité, appuyez sur
Y
etEnter
pour confirmer que vous souhaitez continuer:Do you want to continue (Y/n)? Y
Après quelques minutes, l'application doit vous fournir une URL à laquelle vous pouvez accéder.
Accédez à l'URL pour voir votre application en action. Chaque fois que vous accéderez à l'URL ou actualiserez la page, l'application de tâches s'affichera.
10. Félicitations
Dans cet atelier, vous avez appris à effectuer les tâches suivantes :
- Créer une instance Cloud SQL pour PostgreSQL
- Déployer une application sur Cloud Run qui se connecte à votre base de données Cloud SQL
Effectuer un nettoyage
Cloud SQL ne propose pas de niveau sans frais. Si vous continuez à l'utiliser, des frais vous seront facturés. Vous pouvez supprimer votre projet Cloud pour éviter des frais supplémentaires.
Bien que Cloud Run ne facture pas lorsque le service n'est pas utilisé, il se peut que des frais vous soient facturés pour le stockage de l'image de conteneur dans Artifact Registry. La suppression de votre projet Cloud arrête la facturation de toutes les ressources utilisées dans ce projet.
Si vous le souhaitez, supprimez le projet:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
Vous pouvez également supprimer les ressources inutiles de votre disque cloudshell. Vous pouvez :
- Supprimez le répertoire du projet de l'atelier de programmation:
rm -rf ~/task-app
- Avertissement ! Cette action est irréversible. Si vous souhaitez supprimer tout ce qui se trouve sur votre Cloud Shell pour libérer de l'espace, vous pouvez supprimer l'intégralité de votre répertoire personnel. Assurez-vous que tout ce que vous souhaitez conserver est enregistré ailleurs.
sudo rm -rf $HOME