1. Introduction
Bienvenue dans l'atelier Rails 8 sur Google Cloud ! Dans cet atelier de programmation pratique, vous allez faire passer une application Rails 8 moderne d'une simple référence SQLite locale à une architecture de référence de niveau production et adaptée aux entreprises sur Google Cloud.
Dans cet atelier, nous ne nous contentons pas de déployer une application. Nous effectuons une modernisation cloud de niveau production, avec des choix bien définis (migration Lift and Shift bien faite).
Vous commencez par la configuration locale typique "fonctionne sur ma machine" (espace disque éphémère, SQLite intégré, jobs en cours) et la transformez progressivement en une architecture cloud d'entreprise à toute épreuve sur Google Cloud :
- 🐘 De SQLite local à Cloud SQL (PostgreSQL) : la base de données n'est plus effacée lors des redémarrages. Nous passons à PostgreSQL géré, connecté de manière sécurisée via le proxy d'authentification Cloud SQL (mTLS, sans adresses IP publiques).
- 🪣 Du stockage local éphémère à un bucket GCS privé : le disque sans serveur est temporaire. Nous connectons ActiveStorage à Google Cloud Storage privé à l'aide d'URL signées IAM (
iam: true) : aucun bucket public ni aucune clé divulguée. - 🚀 Des jobs intégrés aux jobs Cloud Run multiconteneurs : nous faisons le lien entre le monolith Rails et le cloud sans serveur. À l'aide de Docker Compose standard, nous exécutons Solid Queue en tant que conteneur de nœud de calcul dédié à côté de Puma dans un pod multiconteneur Cloud Run. Pas de prolifération de microservices ni de famine de threads. DHH serait fier de nous !
🍌 Et comme nous sommes en 2026, nous boostons l'ensemble de la pile avec Google Antigravity. Nous avons des requêtes et des compétences pour vous guider et vous aider à mieux exécuter (et apprécier) cet atelier !
🏛️ Architecture de référence cible (variante 1 : entreprise vectorielle plate et épurée)
Voici l'architecture de référence Google Cloud canonique que vous allez créer et déployer (Variante 1 : Clean Flat Vector Enterprise). Elle présente notre service Cloud Run de production multiconteneur connecté à tous les services d'IA et de persistance Google Cloud gérés :

🎞️ Évolution de l'architecture : de zéro à cloud native
Regardez notre pile se moderniser progressivement, d'une référence éphémère à une seule machine à un blueprint Google Cloud entièrement géré à chaque étape de l'atelier :

Points abordés
- Découvrez comment programmer en binôme avec Google Antigravity pour démystifier Rails 8 et Google Cloud.
- Exécuter des diagnostics de pré-vol automatisés (
just workshop-test) - Découvrez comment provisionner l'infrastructure Google Cloud de manière asynchrone à l'aide de Terraform tout en poursuivant le développement local sans blocage à l'aide du fournisseur
google. - Éliminer les antipatrons de sécurité : buckets GCS privés (
iam: true) et tunnels mTLS proxy d'authentification Cloud SQL au lieu d'ouvrir0.0.0.0/0. - Découvrez comment injecter des secrets directement depuis Google Cloud Secret Manager.
- Orchestration des tâches en arrière-plan d'IA générative asynchrones (générateur de couvertures NanoBanana, synthèse de podcasts bilingues) via Solid Queue.
C'est parti !
2. Étape 0 : Conditions préalables, configuration d'Antigravity et validation de la facturation
💡 Scénario : vous et votre équipe développez une application Rails 8 stratégique. Avant de toucher au code ou de lancer des ressources cloud, nous devons établir notre chaîne d'outils, connecter Google Antigravity et vérifier nos identifiants Google Cloud ainsi que notre base de facturation.
1. Checklist des conditions préalables
Avant de commencer, assurez-vous de disposer des outils suivants dans votre environnement :
just(1.21+) : (just --version) le gestionnaire de tâches : chaque commande de cet atelier est unejustrecette (demandez à votre harnais d'IA de l'installer ou consultezskills/rails8app-workshop).- Git 2.30 ou version ultérieure (
git --version) pour le contrôle des versions, la création de branches et le clonage du dépôt. - SDK Google Cloud (CLI
gcloud) : installé et à jour. - CLI Terraform (1.5 ou version ultérieure) : pour le provisionnement déclaratif de l'infrastructure.
dockeretdocker-compose: installé et exécuté en local.- Ruby
3.4.5: (ruby -v— épinglé parblog/.ruby-version; demandez à votre harnais d'IA d'installer3.4.5s'il manque). Rails 8 est déclaré dansGemfileet installé automatiquement parbundle install. - Google Antigravity 2.0 : votre assistant autonome d'IA pour la programmation en binôme (télécharger Google Antigravity 2.0). Vous vous connecterez avec votre compte Gmail personnel. Aucun jeton d'API n'est nécessaire.
2. Authentification Google Cloud, configuration dédiée et ADC
Pour éviter les conflits avec les configurations gcloud existantes (professionnelles, personnelles ou multicomptes), nous vous recommandons vivement de créer une configuration nommée dédiée pour cet atelier :
# 1. Create and activate dedicated workshop configuration (Fixes Issue #38)
gcloud config configurations create rails8-on-gcp-workshop --activate 2>/dev/null || \
gcloud config configurations activate rails8-on-gcp-workshop
# 2. Authenticate your account and Application Default Credentials (ADC)
gcloud auth login $GOOGLE_CLOUD_ACCOUNT
gcloud auth application-default login
# 3. Configure active project and region
gcloud config set project $GOOGLE_CLOUD_PROJECT
gcloud config set compute/region europe-west1
💡 Pourquoi une configuration dédiée ? L'utilisation de gcloud config configurations create rails8-on-gcp-workshop isole tous les paramètres de la CLI (compte, projet de quota, région par défaut) spécifiquement pour cet atelier. Une fois terminé, vous pouvez revenir à votre configuration habituelle à tout moment avec gcloud config configurations activate default.

3. 🚨 Validation de la facturation GCP
⚠️ Remarque : Les déploiements Google Cloud SQL et Cloud Run nécessitent un compte de facturation associé actif ou des crédits pédagogiques valides pour l'atelier. Assurons-nous que votre configuration est correcte. Pour ce faire, exécutez le contrôle de validation de la facturation :
gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT
Assurez-vous que billingEnabled: true est renvoyé. Vous pouvez également lister tous vos comptes de facturation :
gcloud billing accounts list
Assurez-vous qu'au moins l'un d'eux affiche OPEN: True. Si aucun n'est actif, associez un compte de facturation ou utilisez votre bon de réduction pour l'atelier sur la page de facturation de la console Google Cloud.

4. Cloner le dépôt et l'associer à Antigravity
Clonez le dépôt et accédez au répertoire. Notez que nous restons entièrement sur main :
git clone https://github.com/palladius/rails8-app-on-gcp.git
cd rails8-app-on-gcp
Ouvrez ce répertoire dans Google Antigravity. Antigravity inspecte automatiquement le dépôt, lit AGENTS.md et se tient prêt à vous aider à programmer.
5. 🪫 Si Antigravity n'a plus de crédits sans frais
⚠️ Les crédits Cloud que vous venez d'utiliser ne rechargent PAS Antigravity. Il s'agit de deux surfaces de facturation distinctes : le quota d'Antigravity se trouve dans les forfaits Google AI (Google One), tandis que les bons et les crédits Cloud sont associés à un compte de facturation Cloud. Associer un compte de facturation à votre projet GCP ne vous donnera pas une seule requête Antigravity supplémentaire.
Si l'agent s'arrête et affiche un message de quota, suivez les étapes ci-dessous. Les deux premières sont sans frais et instantanées :
- Changer de pool de modèles. Le quota est suivi par famille de modèles, et Antigravity affiche deux pools indépendants (modèles Gemini et modèles Claude/GPT). Les modèles Claude et GPT-OSS sont disponibles dans le forfait sans frais. Par conséquent, un pool Gemini épuisé laisse souvent l'autre intact. Utilisez le sélecteur de modèle sous le champ du prompt.

- Choisissez un modèle Flash au lieu d'un modèle Pro. Les limites de débit sont liées à la quantité de travail effectuée par l'agent par requête. Un modèle plus léger permet donc d'étendre ce qui reste.
- Utilisez la CLI Antigravity avec votre propre clé API Gemini. L'option "Bring your own key" (Apportez votre propre clé) n'est pas disponible dans l'IDE Antigravity. La CLI est l'alternative documentée. Créez une clé sans frais dans Google AI Studio (vous avez besoin d'un projet, mais pas de compte de facturation), puis téléchargez et configurez l'interface de ligne de commande Antigravity.
- Patientez ou passez à un forfait supérieur. Le quota du niveau sans frais est réinitialisé chaque semaine (le message d'erreur indique la date de réinitialisation). Google AI Pro/Ultra est la seule méthode documentée pour augmenter la limite de base dans l'application pour ordinateur. Ces forfaits sont actualisés toutes les cinq heures et peuvent utiliser les crédits d'IA achetés en cas de dépassement.
- Bien qu'il s'agisse d'un atelier Antigravity, la plupart des harnais de codage d'IA devraient également fonctionner. Il vous suffit de pointer le vôtre vers les répertoires
AGENTS.mdetskills/du dépôt.
6. Validation automatisée de l'étape 0
Vérifiez que votre environnement de l'étape 0 est entièrement conforme à la suite d'évaluation :
just workshop-eval 0
✨ Le moment magique : Antigravity et le moteur d'évaluation de l'atelier vérifient vos versions de CLI, votre environnement Ruby et votre authentification ADC en moins de deux secondes !
3. Étape 1 : Lancement de l'infrastructure Terraform et diagnostics avant vol
💡 Stratégie : le provisionnement des bases de données gérées telles que Google Cloud SQL PostgreSQL prend environ 10 à 12 minutes. Plutôt que d'attendre plus tard, nous lançons l'infrastructure immuable via Terraform dès maintenant en arrière-plan pendant que nous développons en local.
1. Exécuter la suite de diagnostics automatisés avant le vol
Avant de lancer l'infrastructure cloud, exécutez la suite complète de tests préliminaires :
just workshop-test
Ce script (bin/workshop_diagnostics.rb) :
- Valide la configuration de votre identité
GOOGLE_CLOUD_ACCOUNT. - Vérifie que la facturation est active et que le projet est associé.
- Valide les identifiants par défaut de l'application (ADC) pour Vertex AI.
- Confirme l'image canary seed ActiveStorage (
blog/app/assets/images/gcs_dev_image.jpg).

Si .env est manquant, copiez-le à partir du modèle documenté :
cp .env.dist .env
# Edit .env and configure GOOGLE_CLOUD_ACCOUNT with your Google/Gmail account
💡 Conseil : Si just workshop-test indique que l'API Billing est désactivée dans votre projet, activez-la rapidement en procédant comme suit :
gcloud services enable cloudbilling.googleapis.com
2. ⏱️ Lancer l'infrastructure Terraform de manière asynchrone
Exécutez la recette Terraform en une étape, qui crée le bucket d'état du backend distant et déclenche le déploiement de l'infrastructure :
just terraform-apply

Alternative manuelle (ou exécutez directement `iac/bin/terraform-apply.sh`)
# Fall back to active gcloud config if environment variables are unset
GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project)}"
GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"
# Create remote state bucket idempotently
gcloud storage buckets create "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" --location="$GOOGLE_CLOUD_REGION" 2>/dev/null || \
gcloud storage buckets describe "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" >/dev/null
cd iac
terraform init -backend-config="bucket=${GOOGLE_CLOUD_PROJECT}-tfstate"
terraform apply -auto-approve -var="project_id=${GOOGLE_CLOUD_PROJECT}" -var="region=${GOOGLE_CLOUD_REGION}"
cd ..
Éléments provisionnés par Terraform :
- Bucket Google Cloud Storage : créé avec un accès privé et la signature des identifiants IAM (
iam: true). - Image de test Canary : importe
gcs_dev_image.jpgdans le bucket pour activer la validation de bout en bout des blobs. - Instance Google Cloud SQL pour PostgreSQL : lance le provisionnement en arrière-plan (environ 10 à 12 minutes).

💡 Remarque : Vous pouvez ignorer sans risque l'avertissement concernant l'absence de sauvegardes automatiques. Nous n'en aurons pas besoin pour cette base de données d'atelier (bien que les sauvegardes quotidiennes automatiques soient une bonne pratique essentielle pour toute base de données de production réelle).

📝 Notez votre URL Cloud Run : l'application Terraform devrait réussir et afficher les résultats de votre infrastructure. Notez votre cloud_run_url (ou copiez-le dans le presse-papiers). Vous en aurez besoin lors des étapes ultérieures pour valider votre service en direct.
3. Validation automatisée de l'étape 1 (facultatif)
Vérifiez que les conditions préalables et les configurations de l'étape 1 sont respectées :
just workshop-eval 1
⏱️ Remarque : Cette évaluation vérifie les ressources cloud en direct et peut prendre quelques minutes. Cette étape est entièrement facultative. Pendant que la commande s'exécute dans votre terminal, vous pouvez passer immédiatement à l'étape suivante sans attendre.
4. Étape 2 : Référence locale, Mailpit et intégration de l'administrateur
Notre point de départ est une application de blog Rails 8 propre et moderne, exécutée sur localhost avec SQLite, l'interception d'e-mails Mailpit et ActiveStorage basé sur le disque.
1. Démarrer l'application en local
📁 Attention au répertoire ! Ce dépôt contient l'application Rails dans blog/ et Terraform dans iac/. L'étape 1 vous a laissé à la racine du dépôt. Chaque commande de l'étape 2 s'exécute donc à partir de
blog/ : Gemfile, bin/rails et compose.yaml sont tous disponibles. L'exécution à partir de la racine échoue avec no configuration file provided: not found ou un Gemfile manquant.
Accédez à l'application Rails, vérifiez que vous utilisez la version épinglée de Ruby, puis installez les gems et configurez la base de données :
cd blog
ruby -v # must print 3.4.5 — see Step 0 if it does not
bundle install
bin/rails db:setup
🧯 Erreur d'autorisation d'écriture dans
/var/lib/gems/
ou
ruby -v
affiche-t-il le Ruby du système (par exemple, 3.3.x) ? Le système Ruby Vanilla Linux ignore complètement les fichiers .ruby-version et tente d'installer les gems à l'échelle du système. Assurez-vous que rbenv est chargé dans votre terminal actif :
eval "$(rbenv init - bash)" # or: eval "$(rbenv init - zsh)"
ruby -v # confirm it now outputs 3.4.5!
Remarque : Si vous avez ignoré l'installation de la version 3.4.5 à l'étape 0, exécutez rbenv install 3.4.5. Vous pouvez également utiliser le mode A (Docker Compose) ci-dessous (just compose-up), qui exécute la pile Ruby épinglée entièrement dans des conteneurs sans toucher aux gems de l'hôte.
💡 Vous préférez ne pas y penser ? just install et just compose-up à partir de la racine du dépôt effectuent le cd blog pour vous.
2. 🌱 Découverte automatique des smart seeds et bootstrap de l'administrateur (problèmes 21 et 25)
La seed de base de données (db/seeds.rb) inclut la découverte automatique de l'environnement intelligent :
- Il inspecte votre adaptateur de base de données actif (SQLite ou Postgres) et votre configuration de stockage.
- Il détecte l'étape 0 (localhost), crée automatiquement l'utilisateur administrateur initial et insère le post pédagogique :
[LOCAL BASELINE] Welcome to Rails 8 on Localhost!- Pièce jointe d'image triste locale prête à l'emploi (
local_sad_image.png) avec un filigrane vous informant que le stockage sur disque local est éphémère.
- Il déclenche automatiquement un e-mail de réinitialisation du mot de passe via ActionMailer.
Vérifiez ou réexécutez la seed (elle utilise automatiquement votre GOOGLE_CLOUD_ACCOUNT depuis .env) :
bin/rails db:seed # or from repo root: just seed
Démarrez la pile de développement locale via Docker Compose :
docker compose up -d # from blog/ — or `just compose-up` from the repo root

L'application, le serveur SMTP Mailpit local et le lecteur de base de données Adminer s'exécutent désormais ensemble dans des conteneurs isolés :
- Application Rails : http://localhost:3000
- Interface utilisateur Web Mailpit : http://localhost:8025
- Interface utilisateur Adminer DB : http://localhost:8081
🧯 Conflit avec le port 3000 ou
server.pid
erreur ? Si vous avez déjà démarré un serveur de développement natif (bin/dev), arrêtez-le avec Ctrl+C. Si Docker indique que le port 3000 est déjà attribué ou trouve un server.pid restant, nettoyez-le avec la commande suivante :
just compose-down
rm -f blog/tmp/pids/server.pid
just compose-up
3. Expérience Mailpit et connexion administrateur
- Intercepter les e-mails sortants avec Mailpit : ouvrez
http://localhost:8025dans votre navigateur. Consultez la boîte de réception locale. Pendantdb:seed, ActionMailer a envoyé un e-mail d'intégration d'administrateur que Mailpit a capturé en toute sécurité en local, sans toucher aux serveurs de messagerie ni aux identifiants externes :
2. Connectez-vous au blog : ouvrez http://localhost:3000 dans votre navigateur.
- Vous pouvez cliquer sur le lien de réinitialisation du mot de passe directement dans l'e-mail Mailpit pour définir votre mot de passe.
- Vous pouvez également vous connecter à l'aide de l'adresse e-mail de votre compte Google Cloud (à partir de
.env) et du mot de passe par défaut :Ch4ng3m3!!1.
- Observer les badges de télémétrie visuelle :
- Notez la bannière et les badges jaunes de l'environnement dans l'UI :
Notice: Ephemeral Database Active (POSTGRESQL)et[EPHEMERAL DB / STORAGE] 💾 Local. - Remarquez le filigrane du post : le tampon 🏠 (
nanobanana_stamp_local.pngen bas à droite de l'image de couverture). Cela vous permet de vérifier immédiatement et visuellement que vos composants et votre base de données sont actuellement liés au stockage local éphémère.
- Notez la bannière et les badges jaunes de l'environnement dans l'UI :

💡 Conseil de pro facultatif : entraînement avec la console Rails Vous vous demandez comment Rails interagit avec vos données d'amorçage depuis la CLI ? Accédez à la console interactive :
bin/rails console
Inspectez votre utilisateur administrateur initialisé de manière dynamique à l'aide de votre variable d'environnement :
user = User.find_by(email_address: ENV.fetch("GOOGLE_CLOUD_ACCOUNT"))
# Or simply grab the first user:
user = User.first
puts "Admin email: #{user.email_address} (created via: #{user.created_via})"
exit
4. Validation automatique de l'étape 2
Vérifiez votre configuration de base locale et d'administrateur :
just workshop-eval 2

5. Le piège : les conteneurs sans état
Les conteneurs Cloud Run sont sans état et éphémères. Si nous déployons notre base de données SQLite et notre répertoire storage/ local directement sur Cloud Run, tous les posts et les images importées seront définitivement supprimés chaque fois qu'un conteneur est mis à l'échelle zéro ou redémarre.
À l'étape suivante, nous allons déployer intentionnellement cette configuration éphémère sur Cloud Run pour observer le choc sans état de nos propres yeux.
5. Étape 3 : Déploiement 1 – Le choc sans état (WOW précoce en 3 minutes !)
Au lieu d'attendre 15 à 20 minutes pour les bases de données cloud avant de voir quelque chose sur le Web, le développement moderne sans serveur commence par une victoire rapide : le déploiement de notre application Rails à conteneur unique directement sur Google Cloud Run en moins de 3 minutes !
1. Retour sur la journée du
Pour garantir que notre configuration de départ est 100% éphémère (base de données SQLite locale et stockage de fichiers sur disque local), utilisez le moteur Zero-Branch Time-Machine :
just workshop-rewind 1
💡 Que s'est-il passé ?workshop-rewind 1 a appliqué la superposition de configuration stage-1-stateless à blog/config/ sans quitter la branche main. Votre application est configurée avec SQLite sur le disque du conteneur et ActiveStorage sur le système de fichiers local.
2. Déployer Puma à conteneur unique sur Cloud Run
Déployez directement à partir du code source vers Cloud Run. Modern Rails 8 génère automatiquement un Dockerfile officiel et prêt pour la production (avec des compilations en plusieurs étapes, jemalloc et une sécurité non root). Lorsque vous effectuez un déploiement à partir du dossier blog/, Google Cloud Build détecte directement ce Dockerfile natif et provisionne un service géré sans serveur :
# Ensure you are inside the Rails application directory
cd blog
# Ensure default region is set
export GOOGLE_CLOUD_REGION="europe-west1"
# Deploy single-container service from source (with dummy key fallback if starting without credentials)
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--allow-unauthenticated \
--set-env-vars GOOGLE_CLOUD_ACCOUNT=$GOOGLE_CLOUD_ACCOUNT,SECRET_KEY_BASE_DUMMY=1
Vous devriez voir une sortie de progression semblable à celle-ci :

⏳ Cette opération devrait prendre quelques minutes, le temps que Cloud Build package votre conteneur et provisionne la révision Cloud Run.
🧯 Dépannage et mises en garde courantes
PERMISSION_DENIED: Build failed because the default service account is missing required IAM permissions
? Deux causes différentes entraînent la même erreur :
- gcloud vient d'activer une API pour vous. Si le déploiement a affiché
The following APIs are not enabled ... cloudbuild.googleapis.comet que vous avez réponduY, il est possible que la compilation ait commencé avant la propagation des nouvelles autorisations (l'invite de gcloud elle-même indique que cela prendra quelques minutes). Patientez une minute, puis réexécutez exactement la même commande. Elle devrait fonctionner. L'étape 1 deterraform applyactive normalement cette API pour vous. Vous ne devriez donc pas voir l'invite. - Le compte de service Compute par défaut ne dispose pas des rôles requis. Les projets GCP modernes appliquent le principe du moindre privilège, ce qui interrompt les déploiements de sources. Pour résoudre le problème :
just project-status # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builderjust workshop-testle signale également désormais. Il est donc utile de l'exécuter à nouveau si le déploiement continue d'échouer.
Pendant le déploiement :
- Cloud Run crée votre image de conteneur Rails.
- Il attribue un domaine TLS public et sécurisé :
https://blog-[hash]-[region].a.run.app. - Le trafic commence à être acheminé vers Puma sur le port 8080.
3. ✨ Le premier moment "Wow"
Ouvrez l'URL Cloud Run générée dans votre navigateur.
- Votre application Rails 8 moderne est en ligne sur Google Cloud.
- Connectez-vous avec vos identifiants d'administrateur (
GOOGLE_CLOUD_ACCOUNTetAPP_ADMIN_PASSWORD). Si vous ne trouvez pas de bouton de connexion, cliquez sur New Post (Nouvel article). Vous serez alors invité à saisir votre nom d'utilisateur et votre mot de passe :
- Cliquez sur New Post (Nouvel article), rédigez un article intitulé My First Cloud Run Post (Mon premier article Cloud Run), joignez une image, puis cliquez sur Create Post (Créer un article).
- Votre post est publié avec une mise en forme complète et votre image est affichée.
- Notez la télémétrie visuelle :
- Badge d'en-tête :
[EPHEMERAL DB / STORAGE] 💾 Local - Filigrane de l'image : le tampon 🏠 (badge en forme de disque éphémère
127.0.0.1en bas à droite).
- Badge d'en-tête :
🐝 Participez au classement des leaders de l'atelier en direct ! Si vous êtes en ligne et que votre surveillant affiche le classement, et que vous souhaitez y participer, ajoutez votre URL Cloud Run ici : 👉 Enregistrer votre Cloud Run dans le classement

4. 💥 Le piège : le choc sans état et le piège de la "solution de contournement Puma"
Cloud Run est une plate-forme sans serveur et sans état. Lorsque le trafic Web tombe à zéro, Cloud Run réduit le nombre d'instances de conteneur à zéro pour économiser de l'argent. Lorsqu'une nouvelle requête HTTP arrive ou qu'une nouvelle révision de conteneur est déployée, Cloud Run démarre une toute nouvelle image de conteneur propre.
Bannière "Tâches bloquées"
Lorsque vous créez un post ou joignez une image dans un déploiement à conteneur unique, Rails met en file d'attente les tâches ActiveJob (comme l'analyse des dimensions de l'image ou l'indexation des métadonnées). Toutefois, comme aucun processus en arrière-plan n'est en cours d'exécution, la bannière d'avertissement s'affiche :

La solution tentante : exécuter Solid Queue dans Puma
Un développeur astucieux pourrait dire : "Attendez ! Rails 8 nous permet d'exécuter Solid Queue directement dans le processus du serveur Web Puma en activant
SOLID_QUEUE_IN_PUMA=true
variable d'environnement !"
Testons cette solution de contournement sur Cloud Run :
# Force a revision update to enable Solid Queue inside Puma
gcloud run services update blog \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars SOLID_QUEUE_IN_PUMA=true
Maintenant, revenez à votre navigateur et actualisez la page :
💥 Le choc sans état :
- Bonne nouvelle : le worker Solid Queue est désormais actif dans Puma. Tous les nouveaux jobs sont immédiatement vidés.
- La douche froide (l'accroche) :
- Comme Cloud Run a déployé une nouvelle révision, l'instance de conteneur précédente a été remplacée.
- L'article que vous avez écrit et le fichier de base de données SQLite sur le disque ont été complètement effacés !
- Leçon d'architecture : l'exécution de workers en arrière-plan dans Puma consomme de précieuses ressources CPU/RAM des threads Web et ne résout toujours pas le problème de persistance.
5. Validation automatisée de l'étape 3
Vérifiez votre déploiement Cloud Run et la gestion des alertes :
just workshop-eval 3
✨ Leçon : Les solutions de contournement à conteneur unique comme SOLID_QUEUE_IN_PUMA sont pratiques pour le développement local, mais dans l'architecture cloud moderne :
- Les fichiers multimédias nécessitent Google Cloud Storage (GCS) (étape 4).
- Les données relationnelles et les files d'attente de tâches nécessitent Google Cloud SQL géré (étape 5).
- Les nœuds de calcul en arrière-plan lourds appartiennent à un conteneur side-car dédié (étape 6).
6. Étape 4 : Déploiement 3 – Stockage persistant GCS et avertissement POLA
Dans cette étape, nous dissocions le stockage des fichiers et des contenus multimédias du disque de conteneur en basculant ActiveStorage vers Google Cloud Storage (GCS), à l'aide d'URL signées de courte durée via l'API IAM Credentials (iam: true).
Avant de modifier une configuration, comprenez les trois éléments dont Rails a besoin pour communiquer avec GCS :
① ID du projet + nom du bucket : Rails doit savoir où stocker les blobs. L'application lit GOOGLE_CLOUD_PROJECT à partir de l'environnement et dérive le nom du bucket en tant que ${GOOGLE_CLOUD_PROJECT}-activestorage-prod. Votre fichier Terraform de l'étape 1 a déjà créé ce bucket. Vous n'avez rien de plus à faire.
② Un compte de service disposant des autorisations appropriées : Cloud Run s'exécute en tant que compte de service Compute Engine par défaut (PROJECT_NUMBER-compute@developer.gserviceaccount.com). Ce compte de service a besoin de deux rôles IAM sur le bucket :
roles/storage.objectAdmin: pour importer et diffuser des blobsroles/iam.serviceAccountTokenCreatorsur lui-même pour appeler l'API IAM CredentialssignBlobet générer des URL signées éphémères
⚠️ Le raccourci tentant :
public: true Le moyen le plus rapide de faire fonctionner GCS consiste à définir public: true dans storage.yml et à accorder allUsers:objectViewer sur le bucket. Les images se chargent instantanément, sans qu'aucune connexion ne soit requise. Ne faites pas ça. Un bucket public divulgue définitivement tous les contenus multimédias des utilisateurs mis en ligne sur l'Internet ouvert. Notre blueprint utilise iam: true pour que le bucket reste 100% privé. Chaque URL d'image est un jeton signé et temporaire généré à la volée par l'API IAM Credentials.
③
ACTIVE_STORAGE_SERVICE=google : variable d'environnement unique qui permet de passer du disque local à GCS pour Rails au moment de l'exécution. Aucune modification de code ni aucun redéploiement de la logique d'application ne sont nécessaires. Il vous suffit d'activer ou de désactiver une variable d'environnement.
1. Retour sur la journée du
Avancez la machine temporelle jusqu'à l'étape 2 :
just workshop-rewind 2
Inspectez blog/config/storage.yml. Vous verrez que l'application définit trois environnements GCS nommés (dev / test / prod), qui utilisent tous le même modèle sécurisé :
<% gcp_project = ENV.fetch("GOOGLE_CLOUD_PROJECT") { Rails.application.credentials.dig(:gcs, :project) } %>
<% gcs_signer_sa = ENV.fetch("GCS_SIGNER_SA_EMAIL") { "rails-cloudrun-sa@#{gcp_project}.iam.gserviceaccount.com" } %>
google_prod: &google_prod
service: GCS
project: <%= gcp_project %>
bucket: <%= gcp_project %>-activestorage-prod # ← derived from project ID, no extra env var!
iam: true # Sign URLs via IAM Credentials signBlob API (no private key JSON required!)
gsa_email: <%= gcs_signer_sa %>
# Convenience alias — ACTIVE_STORAGE_SERVICE=google points here
google:
<<: *google_prod

2. Accorder des autorisations IAM de stockage et de signature
Assurez-vous que votre compte de service d'exécution Cloud Run dispose des autorisations nécessaires pour signer des URL et importer des objets :
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"
# Grant Storage Object Admin
gcloud storage buckets add-iam-policy-binding gs://$GCS_BUCKET \
--member="serviceAccount:$RUN_SA" \
--role="roles/storage.objectAdmin"
# Grant Service Account Token Creator (required for IAM signed URLs)
gcloud iam service-accounts add-iam-policy-binding $RUN_SA \
--member="serviceAccount:$RUN_SA" \
--role="roles/iam.serviceAccountTokenCreator"
3. Troisième déploiement sur Cloud Run avec GCS associé
Redéployez avec GCS activé. Assurez-vous de vous trouver dans le dossier blog/ (sinon, Cloud Run tentera de compiler à l'aide de Buildpacks au lieu de Dockerfile Rails 8, ce qui entraînera des échecs de compilation).
Utilisez --update-env-vars (et non --set-env-vars) pour conserver les variables d'environnement existantes (clé principale, configuration IAP, etc.) :
cd blog
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars ACTIVE_STORAGE_SERVICE=google
💡 Conseil : Si le déploiement échoue, cliquez sur le lien vers les journaux Cloud Build, qui est fourni dans la sortie du terminal (comme illustré ci-dessous). Vous pouvez également demander à Antigravity de vous aider à déchiffrer et à corriger l'erreur.

4. ✨ Le tampon "Image survivante et tampon Cloud"
- Actualisez l'URL de votre blog.
- Crée un article de blog intitulé Surviving the Cloud et importe une photo.
- Regardez en bas à droite de l'image :
- Le tampon 🏠 a disparu !
- Il est remplacé par le tampon Cloud GCS coloré (
nanobanana_stamp_cloud.png).
- Forcez le redémarrage d'un autre conteneur (ce qui devrait déclencher une quatrième révision) :
gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s) - Actualisez la page : pendant que la base de données SQLite est réinitialisée, le binaire de l'image est sain et sauf dans Google Cloud Storage. Vérifier avec la CLI :
gcloud storage ls -l -r gs://$GCS_BUCKET/
Voici une liste des blobs et des objets multimédias importés que vous pouvez voir dans la console du navigateur Google Cloud Storage de votre bucket de production (gs://$GCS_BUCKET) :

5. ⚠️ Le piège : bannière d'avertissement concernant les tâches bloquées
Lorsque vous avez importé l'image, ActiveStorage a mis en file d'attente un job d'analyse (ActiveStorage::AnalyzeJob) pour extraire les dimensions et les métadonnées.
Cependant, Cloud Run n'exécute actuellement qu'un seul conteneur Web (puma). Dans Rails 8, Solid Queue stocke les jobs en arrière-plan dans la base de données, mais personne n'exécute bundle exec rails solid_queue:start !
En haut de la page de votre blog, vous verrez une bannière d'avertissement lumineuse affichée par blog/app/views/layouts/_check_stuck_jobs.html.erb.
Nous allons résoudre ce problème correctement à l'étape 6, lorsque nous passerons à une architecture sidecar multiconteneur de qualité production avec un conteneur worker dédié exécutant Solid Queue indépendamment du trafic Web.
6. Validation automatisée de l'étape 4
Vérifiez la configuration de GCS et la télémétrie des jobs bloqués :
just workshop-eval 4
7. Étape 5 : Checklist avant vol : connectivité Cloud SQL, secrets et bases de données
Alors que vous avez maîtrisé les conteneurs sans état et Google Cloud Storage dans les étapes 3 et 4, Terraform a terminé de préparer votre backend de production en arrière-plan.
Avant de déployer notre architecture side-car de production à trois conteneurs (déploiement 4 à l'étape 6), cette étape sert d'inspection avant vol rapide pour vérifier l'état de la base de données, la connectivité et l'accès secret.
0. Variables d'environnement d'amorçage
Exportez toutes les variables d'interface système requises directement à partir de vos sorties Terraform :
# Core project variables (should already be set from Step 0)
export GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"
# Compute runtime service account
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
# Cloud SQL instance name (from Terraform output, NOT hardcoded)
export SQL_INSTANCE_NAME=$(cd iac && terraform output -raw sql_instance_name 2>/dev/null || gcloud sql instances list --format='value(name)' --limit=1)
# Database password (from Terraform output)
export DB_PASSWORD=$(cd iac && terraform output -raw db_password 2>/dev/null || echo "CHANGE_ME")
# GCS bucket names
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"
echo "✅ Pre-flight environment variables ready:"
echo " RUN_SA=$RUN_SA"
echo " SQL_INSTANCE_NAME=$SQL_INSTANCE_NAME"
echo " GCS_BUCKET=$GCS_BUCKET"
1. Contrôle de l'état de Cloud SQL
Vérifiez que le provisionnement initial de votre instance PostgreSQL gérée est terminé et que l'instance est opérationnelle :
echo "Checking instance: $SQL_INSTANCE_NAME"
gcloud sql instances describe $SQL_INSTANCE_NAME --format="value(state)"
Le résultat doit être RUNNABLE.
2. (Facultatif) Tester la connectivité de la base de données depuis votre ordinateur portable
En production, les conteneurs Cloud Run se connectent de manière sécurisée à Cloud SQL via un proxy side-car chiffré (127.0.0.1:5432) sans exposer la base de données à l'Internet public (0.0.0.0/0).
ℹ️ Remarque : Cette étape est totalement facultative. Dans Cloud Run, le conteneur side-car officiel du proxy d'authentification Cloud SQL gère automatiquement la connectivité mTLS. Vous n'avez pas besoin d'installer quoi que ce soit en local pour suivre l'atelier.
Si vous avez installé cloud-sql-proxy et psql sur votre ordinateur et que vous souhaitez vérifier la connectivité directe depuis votre ordinateur portable :
# Optional: test connectivity via Google-managed mTLS proxy
gcloud sql connect $SQL_INSTANCE_NAME --user=rails_user --database=rails_production
(Si vous êtes invité à saisir un mot de passe, saisissez
$DB_PASSWORD
. Sinon, vous pouvez passer directement à l'étape 3 ci-dessous.)
3. Inspecter les secrets provisionnés par Terraform
Ne stockez jamais de mots de passe de base de données, de clés API ni de clés principales Rails en texte brut dans git ni dans des variables d'environnement de conteneur. Dans l'architecture cloud moderne, nous utilisons Google Cloud Secret Manager pour l'injection d'exécution zéro confiance.
ℹ️ Terraform s'en est chargé pour vous à l'étape 1 :
rails-master-key: pour déchiffrer les identifiants Rails lors de l'exécution.rails-db-password: mot de passe aléatoire cryptographiquement sécurisé correspondant à l'utilisateur Cloud SQL.rails-admin-password: mot de passe sécurisé pour votre premier utilisateur administrateur.
Vérifiez que vos secrets sont présents et actifs dans Secret Manager :
gcloud secrets list --filter="name:rails-"

🔑 Synchroniser master.key avec votre ordinateur portable (synchronisation locale)
Comme blog/config/master.key est ignoré par Git pour des raisons de sécurité, il est possible que votre clone local ne l'ait pas encore. Exécutez cet extrait d'autoréparation convivial pour l'extraire de Secret Manager afin que vos outils Rails locaux puissent déchiffrer les identifiants :
if [ ! -f blog/config/master.key ]; then
echo "📥 Pulling rails-master-key from Secret Manager to local blog/config/master.key..."
gcloud secrets versions access latest --secret=rails-master-key > blog/config/master.key
echo "🔑 Created local blog/config/master.key! 🎉"
else
echo "✅ Local blog/config/master.key already exists! 🚀"
fi
🧪 Bac à sable : vous voulez créer un secret personnalisé juste pour voir à quel point c'est facile ? Secret Manager n'est pas réservé à Rails. Vous pouvez créer n'importe quel secret en une seule commande CLI simple :
echo -n "MySuperSecretValue123!" | gcloud secrets create workshop-fun-secret --data-file=-
echo "✨ Created workshop-fun-secret in Secret Manager! 🪄"
4. Vérifier les autorisations de l'accesseur de secrets
Pour que les conteneurs Cloud Run puissent installer ces secrets en tant que variables d'environnement ou volumes au démarrage, le compte de service d'exécution ($RUN_SA) doit disposer du rôle roles/secretmanager.secretAccessor pour chaque secret.
Terraform l'a déjà accordé à l'étape 1. Vérifiez la liaison de la stratégie IAM sur rails-master-key :
# Verify that Cloud Run runtime SA has Secret Accessor role
gcloud secrets get-iam-policy rails-master-key \
--flatten="bindings[].members" \
--format="table(bindings.role,bindings.members)" \
--filter="bindings.members:$RUN_SA"
Vous devriez obtenir le résultat suivant :
ROLE MEMBERS
roles/secretmanager.secretAccessor serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com

5. Validation préalable automatisée
Exécutez la suite de tests automatisée de l'étape 5 pour confirmer que votre base de données est exécutable, que les secrets ne sont pas vides et que les autorisations IAM sont vertes :
just workshop-eval 5
8. Étape 6 : Déployer 4 – Sidecars multiconteneurs Enterprise (la référence)
Nous allons maintenant assembler l'architecture de référence ultime : Multiconteneur Cloud Run !
1. Restaurer la valeur de référence
Restaurez la configuration de votre dépôt dans l'état canonique main :
just workshop-restore-gold
2. Inspecter le plan de production à trois conteneurs
Ouvrez blog/compose.prod.yaml et inspectez l'architecture :

graph LR
Client[Internet / HTTPS Client] -->|Port 8080| Web[Web Container: Puma]
Web -->|Localhost:5432| Proxy[Sidecar: Cloud SQL Auth Proxy]
Worker[Worker Container: Solid Queue] -->|Localhost:5432| Proxy
Proxy -->|IAM mTLS Tunnel| CloudSQL[(Google Cloud SQL PostgreSQL)]
Le déploiement en production exécute trois conteneurs coordonnés qui partagent le même réseau local :
web: diffuse le trafic HTTP sur le port 8080 via Puma.worker: conteneur de traitement en arrière-plan dédié exécutantbundle exec rails solid_queue:start.cloudsql-proxy: side-car officiel du proxy d'authentification Google Cloud SQL (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2) lié à127.0.0.1:5432.
3. Exécuter des migrations de bases de données via un job Cloud Run
Avant de rediriger le trafic Web vers notre nouveau déploiement, nous devons exécuter des migrations de bases de données et un seeding initial sur Cloud SQL.
⚖️ Exécuter des migrations dans des conteneurs : deux stratégies
Dans les environnements cloud conteneurisés, il existe deux approches courantes pour exécuter les migrations de bases de données :
- Option A : Automatique dans le point d'entrée Docker (
entrypoint.shexécutedb:migrateau démarrage du conteneur)- Avantages : entièrement automatisé, les développeurs n'ont pas besoin de se souvenir d'exécuter une commande.
- Inconvénients : dangereux dans l'autoscaling sans serveur. Lorsque Cloud Run passe à 10 instances simultanément en cas de trafic élevé, 10 conteneurs se font concurrence pour exécuter les migrations en même temps, ce qui entraîne un verrouillage de la base de données, des conditions de concurrence de schéma ou des délais avant expiration du démarrage.
- Option B : Explicite via Cloud Run Job (notre choix d'architecture)
- Avantages : contrôle total, sécurité transactionnelle et absence de conditions de concurrence. S'exécute en tant que tâche de conteneur ponctuelle dédiée, complètement détachée du trafic Web. Si une migration échoue, la diffusion Web n'est pas affectée.
- Inconvénients : nécessite de déclencher un job CLI ou une étape CI/CD avant le déploiement.
Nous choisissons l'option B pour une fiabilité de production maximale.
⚠️ Critique : DATABASE_URL Format et rigueur RFC 3986 de Ruby 3.4.5
Les jobs Cloud Run se connectent à Cloud SQL à l'aide du proxy de socket Unix natif (/cloudsql/PROJECT:REGION:INSTANCE). Le DATABASE_URL doit utiliser le format à trois barres obliques :
postgresql:///rails_production?user=rails_user&password=PASS&host=/cloudsql/PROJECT:REGION:INSTANCE
# Core environment variables for database migration & initial admin seed
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
echo "Admin user will be seeded for: $GOOGLE_CLOUD_ACCOUNT"
# Get the latest blog image from Cloud Run (jobs don't support --source)
export BLOG_IMAGE=$(gcloud run services describe blog --region=$GOOGLE_CLOUD_REGION --format='value(spec.template.spec.containers[0].image)')
# Build the DATABASE_URL in triple-slash format (required for Unix socket proxy)
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_CONNECTION="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
export DB_URL="postgresql:///rails_production?user=rails_user&password=${DB_PASSWORD}&host=/cloudsql/${CLOUDSQL_CONNECTION}"
# Create migration job — note ALL 4 database URL env vars and GOOGLE_CLOUD_ACCOUNT for seed user creation
gcloud run jobs create rails-migrate \
--image=$BLOG_IMAGE \
--command "bin/rails" \
--args "db:prepare" \
--set-env-vars="DATABASE_URL=${DB_URL},DATABASE_QUEUE_URL=${DB_URL},DATABASE_CACHE_URL=${DB_URL},DATABASE_CABLE_URL=${DB_URL},GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT},GOOGLE_CLOUD_ACCOUNT=${GOOGLE_CLOUD_ACCOUNT}" \
--set-secrets="RAILS_MASTER_KEY=rails-master-key:latest" \
--set-cloudsql-instances="${CLOUDSQL_CONNECTION}" \
--service-account=$RUN_SA \
--region $GOOGLE_CLOUD_REGION 2>/dev/null || true
# Execute migration job
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait
💡 Rails 8 Multi-Database : l'application utilise quatre bases de données (principale, file d'attente, cache et câble) par database.yml. Les quatre variables d'environnement DATABASE_*_URL doivent toutes pointer vers la même instance Cloud SQL. Sinon, les tables Solid Queue/Cache/Cable ne seront pas créées.
Après db:prepare, chargez les schémas Solid Queue/Cache/Cable :
# Schema load for queue, cache, and cable databases (creates solid_queue_jobs, etc.)
gcloud run jobs update rails-migrate \
--command "bash" \
--args "-c,DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bin/rails db:schema:load:queue db:schema:load:cache db:schema:load:cable" \
--region $GOOGLE_CLOUD_REGION
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait
La figure ci-dessus montre un résultat possible de
gcloud run jobs executions describe $JOBNAME
Console Google Cloud Run Jobs montrant
rails-migrate
Exécution réussie
4. Quatrième déploiement : déployer un multiconteneur Cloud Run via Docker Compose
Déployez maintenant la véritable pile de production à trois conteneurs (web, worker, cloudsql-proxy) directement depuis compose.prod.yaml :
# 1. Self-healing environment exports (guarantees no missing master key or DB password)
export RAILS_MASTER_KEY="${RAILS_MASTER_KEY:-$(cat config/master.key 2>/dev/null || gcloud secrets versions access latest --secret=rails-master-key)}"
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_INSTANCE_CONNECTION_NAME="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
# 2. Database credentials & GCS attachments
export DB_USER="rails_user"
export DB_NAME="rails_production"
export GCS_BUCKET="${GCS_BUCKET}"
export ACTIVE_STORAGE_SERVICE="google"
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
export GEMINI_API_KEY="${GEMINI_API_KEY:-dummy-key}"
# 3. Deploy the full multi-container sidecar architecture to Cloud Run
gcloud run compose up compose.prod.yaml \
--region=$GOOGLE_CLOUD_REGION \
--allow-unauthenticated
Console Google Cloud Run : onglet "Conteneurs" affichant les trois conteneurs side-car (
cloudsql-proxy
,
web
,
worker
)
💡 Curiosité de l'UI / Problème de la console : dans le menu déroulant de la console, vous remarquerez peut-être que Cloud Run attribue le libellé Ingress container: cloudsql-proxy à la première entrée. Pas de panique ! Il s'agit d'un simple bug visuel de l'UI dans la console Google Cloud (suivi dans #149) causé par l'ordre des dépendances des conteneurs. En réalité, web est le seul conteneur à écouter sur le port HTTP 8080 et à gérer 100% du trafic Web entrant, tandis que cloudsql-proxy sert strictement le trafic PostgreSQL privé sur le port 5432.
5. ✨ Le moment magique : aucune alerte et persistance complète dans le cloud !
Respirez profondément et admirez votre écran : toutes les bannières d'avertissement ont disparu ! 🪄
Blog sur la production Rails 8 : une UI impeccable, aucune bannière d'avertissement et un état Cloud SQL persistant !
L'application est désormais officiellement adaptée à la production et cloud native :
- Soutenu par Google Cloud SQL (PostgreSQL) géré via un tunneling mTLS sécurisé.
- Les contenus multimédias sont stockés de manière sécurisée dans Google Cloud Storage privé avec signature de blobs IAM.
- Aucun avertissement de stockage éphémère et aucune alerte de job bloqué.
Ouvrez votre URL Cloud Run :
- Examinez les badges de télémétrie :
[CLOUD PERSISTENT 🐘 ☁️]a été validé dans l'amorçage de votre base de données.- Les badges Google Cloud Run et GCS dans le pied de page sont verts.
- La bannière d'avertissement concernant les jobs bloqués a complètement disparu.
- créer des articles de blog et des commentaires ;
- Redémarrez ou redéployez autant de fois que vous le souhaitez : vos données, vos posts, vos commentaires et vos composants sont conservés indéfiniment dans Cloud SQL et GCS.
6. Validation automatisée de l'étape 6
Vérifiez la configuration multiconteneur :
just workshop-eval 6
9. Étape 7 : Pipelines d'IA générative, Podcastifier et la chasse au trésor GCS 🏴☠️
Avec Solid Queue exécuté dans un conteneur dédié et Google Cloud Storage actif, nous pouvons libérer l'IA générative asynchrone !
1. Générateur de couvertures vintage NanoBanana (pipeline d'IA générative prêt à l'emploi)

Bonne nouvelle : cette fonctionnalité est déjà entièrement implémentée dans la base de code. Vous n'avez pas besoin d'écrire de code. Votre tâche consiste à déclencher et à observer un pipeline d'IA générative cloud asynchrone en action.
🧠 Comment ça marche en coulisses
- Authentification : aucune clé API n'est requise. Il utilise automatiquement les identifiants par défaut de l'application (
roles/aiplatform.usersur Vertex AI) via le compte de service d'exécution Cloud Run. (Sinon, siGEMINI_API_KEYest fourni, il appelle directement l'API Gemini.) - Pipeline : lorsqu'un article est créé sans image de couverture, un rappel
after_commitmetGenerateCoverImageJoben file d'attente dans Solid Queue. - Modèle : il invite le modèle multimodal de Google (Gemini 2.5 Flash Image / Imagen sur Vertex AI) à générer une affiche de film italien authentique des années 1960 ("Locandina di un film 1960") avec une banane en cameo et un rubis brillant en forme de 8.
- Filigrane de provenance dynamique : utilise
libvipspour ajouter dynamiquement un badge visuel dans l'angle (nuage coloré sur GCS). - Échange à chaud Turbo Stream : lorsque l'image est jointe,
Turbo::StreamsChannel.broadcast_refresh_to(post)met à jour le navigateur du lecteur en direct, sans recharger manuellement la page.

📐 Afficher la source du diagramme de séquence Mermaid
sequenceDiagram
autonumber
actor User as 👤 Reader / Author
participant Web as 🌐 Web (Puma)
participant DB as 🐘 Cloud SQL (Solid Queue)
participant Worker as ⚙️ Worker (Solid Queue)
participant Vertex as 🧠 Vertex AI (Gemini Flash / Imagen)
participant GCS as ☁️ Google Cloud Storage
User->>Web: POST /posts (Title & Body, No Cover)
Web->>DB: 1. Save Post & Enqueue GenerateCoverImageJob
Web-->>User: Redirect to Post (turbo_stream subscribed)
Worker->>DB: 2. Dequeue GenerateCoverImageJob
Worker->>Vertex: 3. Prompt for 1960s Italian Poster (ADC auth)
Vertex-->>Worker: 4. Raw Image Buffer
Worker->>Worker: 5. Stamp Provenance Watermark (libvips)
Worker->>GCS: 6. Attach Blob via ActiveStorage (IAM signBlob)
Worker->>Web: 7. Turbo::StreamsChannel.broadcast_refresh_to(post)
Web-->>User: ⚡ Live UI Hot-Swap: Poster magically appears!
🧪 Essayez-le en direct !
Vous pouvez le déclencher de deux manières :
- Option A (interface utilisateur Web) : cliquez sur Nouveau post, saisissez un titre tel que "Architecture sans serveur avec Ruby on Rails 8", rédigez du contenu et laissez vide le champ de saisie du fichier d'image de couverture. Cliquez sur Créer un post.
- Option B (CLI Runner) : exécutez la commande depuis votre terminal :
bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
Patientez environ 15 à 30 secondes, puis consultez les journaux du nœud de calcul Solid Queue dans Cloud Run. L'affiche vintage s'affichera directement dans votre post.
📸 TODO(riccardo) : ajouter une capture d'écran d'un article de blog avec une affiche de film italien vintage des années 1960 générée par IA, avec une banane et un rubis 8 en cameo
2. Le défi Bilingual Podcastifier (exercice pratique de codage antigravité)
⚠️ Important : Contrairement au générateur de couverture ci-dessus, cette fonctionnalité n'est PAS encore implémentée.
Si vous ouvrez blog/app/jobs/podcastifier_job.rb, vous trouverez un stub de scaffold vide :
class PodcastifierJob < ApplicationJob
queue_as :default
def perform(post_id)
# TODO(student): COMPLETE_ME — Pair program with Google Antigravity to implement
# Italian TTS podcast generation using voice 'it-IT-Wavenet-A' via Cloud TTS / ADC!
end
end
Dans cet exercice pratique, votre mission consiste à programmer en binôme avec Google Antigravity pour implémenter cette fonctionnalité :
- Prompt Antigravity : "Aide-moi à implémenter PodcastifierJob dans
app/jobs/podcastifier_job.rb. Il doit récupérer le post, traduire ou synthétiser un aperçu audio en italien à l'aide de Google Cloud Text-to-Speech (voix "it-IT-Wavenet-A" avec le code de langue "it-IT" via les identifiants ADC), et joindre le fichier audio àpost.podcast_audio_ità l'aide d'ActiveStorage !" - Ajouter un lecteur à la vue : dans
app/views/posts/show.html.erb, ajoutez un lecteurHTML5 lorsque@post.podcast_audio_it.attached?. - Déclencheur de synthèse : une fois enregistré, Solid Queue exécute le job de synthèse vocale de manière asynchrone en arrière-plan.

🎧 Exemples de résultats Podcastifier :
💡 Besoin d'aide ou bloqué ? Une solution de référence complète et éprouvée est disponible dans la branche solutions/podcastifier. Vous pouvez l'inspecter à tout moment avec git diff solutions/podcastifier.
3. [FACULTATIF] 🏴☠️ La chasse au trésor GCS (récupération de blobs dans la console)
ℹ️ Vérification des prérequis : local
cloud-sql-proxy
CLI requise : cet exercice facultatif connecte directement votre ordinateur portable local à Google Cloud SQL via un tunnel mTLS chiffré. Vérifiez si le proxy CLI est installé :
which cloud-sql-proxy
- Si l'application est installée : passez à la section ci-dessous.
- Si vous ne l'avez pas installé et que vous souhaitez effectuer cette quête, installez-le avec la commande suivante :
# Linux (x86_64) curl -o /tmp/cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.0/cloud-sql-proxy.linux.amd64 chmod +x /tmp/cloud-sql-proxy && sudo mv /tmp/cloud-sql-proxy /usr/local/bin/cloud-sql-proxy # macOS (Homebrew) brew install cloud-sql-proxy - Sinon : vous pouvez passer directement à la validation automatique de l'étape 7.
Vous vous souvenez de la photo que vous avez importée à l'étape 4, avant que le redémarrage du conteneur n'efface la base de données SQLite éphémère ? Ce fichier image est toujours stocké en toute sécurité dans votre bucket GCS privé en tant que "blob orphelin".
Utilisons la console Rails pour le récupérer et l'associer à un post Cloud SQL :
# Connect to your production database via Rails console and Cloud SQL Proxy
cloud-sql-proxy --port 5432 ${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME} &
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production bin/rails c
Dans la console Rails :
# 1. Search for orphaned GCS blobs in ActiveStorage
orphan_blobs = ActiveStorage::Blob.where.missing(:attachments)
puts "🏴☠️ Found #{orphan_blobs.count} orphaned GCS blobs!"
# 2. Attach the surviving blob to your latest post
if orphan_blobs.any?
post = Post.last
post.cover_image.attach(orphan_blobs.first)
post.save!
puts "🎉 Rescued blob #{orphan_blobs.first.filename} attached to '#{post.title}'!"
end
exit
📸 TODO(riccardo) : ajouter une capture d'écran de la console Rails interactive exécutant l'extrait de code GCS Treasure Hunt et récupérant les blobs ActiveStorage orphelins
Actualisez votre blog : la photo importée à l'étape 4 (État de choc sans état) est de nouveau disponible et associée de manière permanente à votre post Cloud SQL.
4. Validation automatisée de l'étape 7
Vérifiez les jobs et les composants d'IA générative :
just workshop-eval 7
10. Étape 8 : Choisissez votre aventure / Quêtes avancées 🏆
Maintenant que vous maîtrisez l'architecture de référence canonique, choisissez votre quête de fin de formation.
🛡️ Quête 1 : Zero Trust Google IAP (Identity-Aware Proxy)
- Difficulté : moyenne (sécurité Enterprise)
- Objectif : sécuriser votre application Cloud Run afin que seuls vos comptes Google / Gmail validés puissent y accéder, en éliminant complètement la connexion par mot de passe.
- Fonctionnement
- Un équilibreur de charge d'application HTTPS externe met fin à l'authentification Google OAuth et valide l'identité avant que le trafic n'atteigne Cloud Run.
- Google transmet les en-têtes d'identité validée (
X-Goog-Authenticated-User-Email). - Rails se connecte automatiquement à l'identité Google validée via
blog/app/controllers/concerns/iap_authenticatable.rb:module IapAuthenticatable extend ActiveSupport::Concern included { before_action :authenticate_via_iap } private def authenticate_via_iap return unless (raw = request.headers["X-Goog-Authenticated-User-Email"]).present? email = raw.sub(/^accounts\.google\.com:/, "") user = User.find_or_create_by!(email_address: email) { |u| u.password = SecureRandom.hex(16) } start_new_session_for(user) unless authenticated? end end - Activez le module Terraform dans
iac/iap.tfavecenable_iap = trueet spécifiez les comptes Google autorisés :iap_allowed_users = [var.google_cloud_account]

📊 Quête 2 : Télémétrie SRE de production et Cloud Logging
- Difficulté : moyenne (observabilité)
- Objectif : diffuser des journaux d'application JSON structurés avec des ID de corrélation de trace directement dans Google Cloud Logging et détecter les exceptions de production en temps réel avec Cloud Error Reporting.
- Fonctionnement
- Configurez
blog/config/environments/production.rbpour qu'il émette des journaux JSON structurés avec des libellés de trace GCP. - Déclenchez une exception de test intentionnelle et regardez Google Cloud Error Reporting la regrouper et vous en informer instantanément.
- Configurez
📸 TODO(riccardo) : ajouter une capture d'écran de Google Cloud Error Reporting montrant les exceptions de production groupées et la charge utile JSON structurée de Cloud Logging avec des libellés de trace
🧠 Quête 3 : pgvector Recherche sémantique et Gemini RAG
- Difficulté : difficile (projet Capstone sur l'IA générative)
- Objectif : effectuer une recherche conceptuelle dans des articles à l'aide d'embeddings vectoriels stockés dans PostgreSQL sur Cloud SQL.
- Fonctionnement
- Activez l'extension
vectorsur votre instance Cloud SQL pour PostgreSQL :CREATE EXTENSION IF NOT EXISTS vector; - Ajoutez le gem
neighboràblog/Gemfileet générez des embeddings de texte avec Gemini (text-embedding-004) surPost#after_save. - Effectuez des requêtes de distance de cosinus (
<=>) pour alimenter une barre de recherche sémantique avec Turbo Streams.
- Activez l'extension
📸 TODO(riccardo) : ajouter une capture d'écran de la barre de recherche sémantique du blog en direct renvoyant des correspondances conceptuelles avec des scores de similarité cosinus
⚡ Quête 4 : Assistant d'audit SEO et des performances
- Difficulté : facile / amusant (expérience du développeur)
- Objectif : auditer les métriques Core Web Vitals, Largest Contentful Paint (LCP) et les indices de lisibilité Flesch-Kincaid / Fog à l'aide des outils Antigravity et Speedgrapher.
📸 TODO(riccardo) : ajouter une capture d'écran du récapitulatif de l'audit SEO et des Core Web Vitals de Speedgrapher / Lighthouse
🤔 Boîte de curiosités : obtenir une console Rails en direct sur Cloud Run
💡 La question : sur les VM traditionnelles ou les plates-formes PaaS de type Heroku, les développeurs se connectent généralement en SSH à une instance en cours d'exécution ou utilisent heroku run rails console pour inspecter ou corriger les données de production en direct. Quel est l'équivalent natif de Cloud Run et comment le renforcer ?
Pourquoi ?
gcloud run exec
n'existe pas pour les services Cloud Run
Cloud Run est une plate-forme sans serveur : votre conteneur peut s'exécuter sous la forme de zéro, une ou des centaines d'instances identiques, sans identité stable ni shell adressable. Cette approche est fondamentalement différente de celle de Kubernetes (kubectl exec), où un pod spécifique dispose d'un socket nommé auquel vous pouvez vous connecter.
# ❌ This does NOT exist for Cloud Run Services:
gcloud run services exec blog --region europe-west1 -- rails console
# ✅ This exists for Cloud Run JOBS (non-interactive, one-shot tasks):
gcloud run jobs execute my-job --region europe-west1 --wait
Les quatre modèles natifs de GCP
Modèle | Commande | Interactif ? | Sécurité |
1. Proxy local + base de données distante |
| ✅ Complète | 🔒 mTLS du proxy d'authentification Cloud SQL |
2. Job Cloud Run (ponctuel) |
| ❌ Script uniquement | 🔒 Rôle IAM requis |
3. Gem Web Console + IAP | Navigateur → | ✅ Complète | 🛡️ IAP zéro confiance |
4. Cloud Shell + Proxy | Cloud Shell → | ✅ Plein | 🔒 Identité Google |
Schéma 1 (recommandé pour l'inspection des données) : exécutez une console Rails locale qui communique directement avec votre instance Cloud SQL PostgreSQL via le proxy d'authentification Cloud SQL :
# In one terminal: start the auth proxy
cloud-sql-proxy rails8-fl-20260907b-xm8ko4:europe-west1:your-instance
# In another terminal: open a read-write or sandbox console
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production \
bin/rails console --sandbox
Vous bénéficiez d'une interactivité IRB complète sans exposition au réseau. Le proxy Cloud SQL utilise mTLS sur un socket de domaine Unix ou une boucle locale.
Modèle 3 (console Web + approche zéro confiance IAP, chemin d'accès au navigateur renforcé) :
Le gem web-console fournit une session IRB basée sur le navigateur. Sans protection, il s'agit d'une faille de sécurité critique. Ne l'exposez jamais publiquement. Toutefois, combinée à Google IAP (Quest 1), la surface d'attaque s'effondre : seules les identités Google validées figurant sur votre liste d'autorisation peuvent atteindre le point de terminaison /rails/console.
# Gemfile — only load in development/staging, never production without IAP!
gem "web-console", group: :development
# config/environments/production.rb — if IAP is enabled:
config.web_console.allowed_ips = ["0.0.0.0/0"] # IAP terminates auth before us
⚠️ Le modèle 3 nécessite d'abord l'activation de la quête 1 (IAP). Sans cela, /rails/console sur une URL publique équivaut à laisser votre salle de serveurs déverrouillée. Avec IAP, il devient un outil opérateur puissant renforcé par la sécurité zéro confiance de Google.
Demandez à Antigravity : "Montre-moi comment exécuter
rails console --sandbox
contre ma base de données de production Cloud SQL de manière sécurisée à l'aide du proxy d'authentification Cloud SQL."
🏆 Passer à l'étape supérieure sur The Hive : preuve de travail validée par un surveillant
Une fois la quête de votre choix terminée, récupérez votre trophée de fin de l'étape 8 🏆 sur le classement The Hive :
- Envoyer votre quête sur GitHub :
- Ouvrez un problème dans le dépôt officiel : github.com/palladius/rails8-app-on-gcp/issues/new.
- Format du titre :
🎓 [Step 8 Completed]: - Dans le corps de l'e-mail, incluez une brève description de ce que vous avez accompli et un lien vers votre déploiement Cloud Run en direct.
- Associez votre application Cloud Run à votre problème :
- Notez le numéro du problème (par exemple,
88ou l'URL complètehttps://github.com/palladius/rails8-app-on-gcp/issues/88). - Injectez la variable d'environnement
STEP_8_GHIdans votre service Cloud Run :gcloud run services update blog \ --region europe-west1 \ --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER> - Vérifiez que votre point de terminaison
/status.jsonsignale désormais votre quête : Vous devriez voir :curl -s https://<YOUR_APP>.run.app/status.json | jq .quest{ "step_8_completed": true, "ghi_issue": 88, "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88" }
- Notez le numéro du problème (par exemple,
- Faites examiner votre épreuve par un surveillant et débloquez le trophée d'or :
- Consultez le classement The Hive : votre ligne affiche désormais un badge ambre :
⏳ GHI #XX review pending. - Demandez aux responsables de l'atelier (@palladius, @emilianodellacasa ou @ricc) d'inspecter votre déploiement.
- Dès qu'un surveillant ajoute un commentaire
LGTMà votre problème, The Hive fait automatiquement passer votre progression à 8/8 🏆 avec un badge doré/violet et un trophée permanent renvoyant directement à votre problème de synthèse.
- Consultez le classement The Hive : votre ligne affiche désormais un badge ambre :
11. Conclusion et nettoyage 🎓
Félicitations ! 🎉
Vous avez créé et déployé une application Rails 8 de niveau production et adaptée aux entreprises sur Google Cloud Platform :
- Progression sans branche : maîtrisez les workflows modernes avec les calques Time-Machine sur
main. - Cloud Storage : stockage d'objets privé et évolutif avec signature de blobs IAM (
iam: true). - Cloud SQL : PostgreSQL géré et sécurisé avec des tunnels mTLS du proxy d'authentification Cloud SQL.
- Secret Manager : aucune fuite d'identifiants en texte brut ni de fichiers
.env. - Cloud Run multiconteneurs : side-cars Web Puma, worker Solid Queue et proxy isolés.
- IA générative : génération asynchrone d'affiches vintage et synthèse vocale de podcasts avec Vertex AI.
- Quêtes : IAP zéro confiance, observabilité SRE et embeddings pgvector !
🧹 Nettoyage des ressources
Pour éviter que des frais ne vous soient facturés en continu une fois l'atelier terminé, assurez-vous de nettoyer vos ressources Google Cloud. Choisissez l'approche qui correspond le mieux à votre environnement :
Option 1 : Terraform Teardown (recommandée / par défaut 🟢)
Utilisez cette option si vous utilisez un projet Google Cloud existant ou partagé dans lequel d'autres ressources ou données sont stockées. Il détruit uniquement les ressources provisionnées lors de cet atelier (Cloud Run, Cloud SQL, Secret Manager, buckets GCS), en laissant le reste de votre projet intact.
cd iac
terraform destroy -auto-approve
Option 2 : Suppression totale du projet (ne laisser aucune trace 🌪️)
Utilisez cette option si vous avez créé un projet d'atelier dédié (par exemple, à l'aide de crédits d'atelier ou d'un bac à sable) et que vous souhaitez garantir qu'il ne reste aucune trace, y compris les journaux, les métadonnées, les comptes de service et les liens de facturation.
gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet