Déployer une application Next.js full stack sur Cloud Run avec Cloud SQL pour PostgreSQL à l'aide du connecteur Node.js Cloud SQL

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 vous montrera comment connecter une application Next.js sur Cloud Run à une base de données Cloud SQL pour PostgreSQL à l'aide du connecteur Cloud SQL Node.js.

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

  1. Si vous n'avez pas encore de compte Google, vous devez en créer un.
    • Utilisez un compte personnel au lieu d'un compte professionnel ou scolaire. Il est possible que des restrictions s'appliquent aux comptes professionnels et scolaires, ce qui vous empêche d'activer les API nécessaires pour cet atelier.

3. Configuration du projet

  1. Connectez-vous à la console Google Cloud.
  2. Activez la facturation dans la console Cloud.
    • Cet atelier devrait vous coûter moins de 1 USD en ressources Cloud.
    • Vous pouvez suivre les étapes à la fin de cet atelier pour supprimer les ressources et éviter ainsi des frais supplémentaires.
    • Les nouveaux utilisateurs peuvent bénéficier d'un essai sans frais pour bénéficier d'un crédit de 300$.
  3. Créez un projet ou réutilisez-en un existant.

4. Ouvrir l'éditeur Cloud Shell

  1. Accédez à l'éditeur Cloud Shell.
  2. Si le terminal ne s'affiche pas en bas de l'écran, ouvrez-le :
    • Cliquez sur le menu hamburger Icône du menu hamburger.
    • Cliquez sur Terminal
    • Cliquez sur Nouveau terminalOuvrir un nouveau terminal dans l'éditeur Cloud Shell
  3. 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 la commande suivante :
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
        
      Définir l'ID du projet dans le terminal de l'éditeur Cloud Shell
  4. Si vous êtes invité à autoriser l'accès, cliquez sur Autoriser pour continuer. Cliquez pour autoriser Cloud Shell.
  5. Le message suivant doit s'afficher :
    Updated property [core/project].
    
    Si le message WARNING s'affiche et que vous êtes invité à Do you want to continue (Y/N)?, cela signifie probablement que vous avez saisi l'ID de projet de manière incorrecte. Appuyez sur N, puis sur Enter, et réessayez d'exécuter la commande gcloud 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 êtes invité à autoriser l'accès, cliquez sur Autoriser pour continuer. Cliquez pour autoriser Cloud Shell.

L'exécution de cette commande peut prendre quelques minutes, mais un message semblable à celui qui suit 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 pour Cloud Run afin qu'il dispose des autorisations nécessaires pour se connecter à Cloud SQL.

  1. Exécutez la commande gcloud iam service-accounts create comme suit pour créer un compte de service :
    gcloud iam service-accounts create quickstart-service-account \
      --display-name="Quickstart Service Account"
    
  2. 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"
    
  3. 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"
    
  4. 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

  1. Exécutez la commande gcloud sql instances create pour créer une instance 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
    

L'exécution de cette commande peut prendre quelques minutes.

  1. Exécutez la commande gcloud sql databases create pour créer une base de données Cloud SQL dans quickstart-instance.
    gcloud sql databases create quickstart_db \
        --instance=quickstart-instance
    
  2. Créez un utilisateur de base de données PostgreSQL pour que le compte de service que vous avez créé précédemment puisse 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.

  1. Pour créer un projet Next.js nommé task-app, utilisez la commande suivante :
    npx --yes create-next-app@15 task-app \
      --ts \
      --eslint \
      --tailwind \
      --no-src-dir \
      --turbopack \
      --app \
      --no-import-alias
    
  2. Remplacez le répertoire par task-app:
    cd task-app
    
  1. 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
    
  2. Installez @types/pg en tant que dépendance de développement pour utiliser une application TypeScript Next.js.
    npm install --save-dev @types/pg
    
  1. Ouvrez le fichier actions.ts dans l'éditeur Cloud Shell :
    cloudshell edit app/actions.ts
    
    Un fichier vide devrait maintenant s'afficher en haut de l'écran. C'est ici que vous pouvez modifier ce fichier actions.ts. Montre que le code se trouve dans la partie supérieure de l'écran
  2. Copiez le code suivant et collez-le dans le fichier actions.ts ouvert :
    'use server'
    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';
    };
    
    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)
        );`);
    }
    
    // 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`);
      return rows;
    }
    
    // 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;
    }
    
  1. Ouvrez le fichier page.tsx dans l'éditeur Cloud Shell :
    cloudshell edit app/page.tsx
    
    Un fichier existant devrait maintenant s'afficher en haut de l'écran. C'est ici que vous pouvez modifier ce fichier page.tsx. Montre que le code se trouve dans la partie supérieure de l'écran
  2. Supprimez le contenu existant du fichier page.tsx.
  3. Copiez le code suivant et collez-le dans le fichier page.tsx ouvert :
    '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';
      createdAt: number;
    };
    
    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 handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault();
        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}
                        onChange={() => 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 float-right"
                      >
                        Delete
                      </button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </main>
      );
    }
    

L'application est maintenant prête à être déployée.

9. Déployer l'application dans Cloud Run

  1. 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
    
  2. Si vous y êtes invité, appuyez sur Y et Enter pour confirmer que vous souhaitez continuer :
    Do you want to continue (Y/n)? Y
    

Après quelques minutes, l'application devrait vous fournir une URL à consulter.

Accédez à l'URL pour voir votre application en action. Chaque fois que vous accédez à l'URL ou que vous actualisez la page, l'application Tasks s'affiche.

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 et vous sera facturé si vous continuez à l'utiliser. 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 Cloud Shell. Vous pouvez :

  1. Supprimez le répertoire du projet de l'atelier de programmation :
    rm -rf ~/task-app
    
  2. Avertissement ! Cette prochaine action est irréversible. Si vous souhaitez supprimer tous les éléments de votre Cloud Shell pour libérer de l'espace, vous pouvez supprimer l'intégralité de votre répertoire d'accueil. Veillez à ce que tout ce que vous souhaitez conserver soit enregistré ailleurs.
    sudo rm -rf $HOME
    

Continuer à apprendre