Rails 8 su Google Cloud: da zero all'AI

1. Introduzione

Ti diamo il benvenuto al workshop Rails 8 su Google Cloud. In questa codelab pratica, prenderai un'applicazione Rails 8 moderna da una semplice baseline SQLite locale a un'architettura di riferimento di livello di produzione e pronta per l'uso aziendale su Google Cloud.

In questo workshop non eseguiamo solo il deployment di un'app, ma eseguiamo una modernizzazione del cloud basata su opinioni e di livello di produzione (lift-and-shift eseguito correttamente).

Si parte dalla tipica configurazione locale "funziona sulla mia macchina" (disco temporaneo, SQLite incorporato, job in-process) e la si evolve progressivamente in un'architettura cloud aziendale e a prova di errore su Google Cloud:

  1. 🐘 Da SQLite locale → Cloud SQL (PostgreSQL): il database non viene più cancellato al riavvio. Passiamo a PostgreSQL gestito connesso in modo sicuro tramite proxy di autenticazione Cloud SQL (mTLS, nessun IP pubblico).
  2. 🪣 Da archiviazione locale temporanea a GCS privato: il disco serverless è temporaneo. Colleghiamo ActiveStorage a Google Cloud Storage privato utilizzando URL firmati (iam: true): nessun bucket pubblico, nessuna chiave compromessa.
  3. 🚀 Da job in-process → Cloud Run multi-container: colleghiamo il monolite Rails e il cloud serverless. Utilizzando Docker Compose standard, eseguiamo Solid Queue come container di lavoro dedicato insieme a Puma in un pod multiconteiner di Cloud Run: zero proliferazione di microservizi, zero carenza di thread. DHH sarebbe orgoglioso.

🍌 E poiché viviamo nel 2026, potenziamo l'intero stack con Google Antigravity. Non è un'idea improvvisata, abbiamo prompt e competenze per guidare il tuo harness a eseguire (e godersi) questo workshop.

🏛️ Architettura di riferimento target (variante 1: Clean Flat Vector Enterprise)

Di seguito è riportata l'architettura di riferimento canonica di Google Cloud che creerai ed eseguirai il deployment (Variante 1: Clean Flat Vector Enterprise), che mostra il nostro servizio Cloud Run di produzione multiconteiner collegato a tutti i servizi di persistenza e AI gestiti di Google Cloud:

Variante 1: Clean Flat Vector Enterprise - Architettura Rails 8 su Google Cloud

🎞️ L'evoluzione dell'architettura: da zero al cloud-native

Guarda il nostro stack modernizzarsi progressivamente da una baseline effimera a una singola macchina a un progetto Google Cloud completamente gestito in ogni traguardo del workshop:

Workshop sull'evoluzione dell'architettura

Obiettivi didattici

Iniziamo.

2. Passaggio 0: prerequisiti, configurazione di Antigravity e verifica della fatturazione

💡 Lo scenario: tu e il tuo team state creando un'applicazione Rails 8 mission-critical. Prima di toccare il codice o avviare le risorse cloud, dobbiamo stabilire la nostra toolchain, connettere Google Antigravity e verificare le nostre credenziali Google Cloud e le basi della fatturazione.

1. Elenco di controllo dei prerequisiti

Prima di iniziare, assicurati di avere a disposizione i seguenti strumenti nel tuo ambiente:

  • just (1.21+): (just --version) il task runner: ogni singolo comando di questo workshop è una just ricetta (chiedi all'AI di installarlo o consulta skills/rails8app-workshop).
  • Git (2.30+): (git --version) per il controllo della versione, la creazione di rami e la clonazione del repository.
  • Google Cloud SDK (gcloud CLI): installato e aggiornato.
  • Interfaccia a riga di comando Terraform (1.5+): per il provisioning dichiarativo dell'infrastruttura.
  • docker e docker-compose: installati ed eseguiti localmente.
  • Ruby 3.4.5: (ruby -v, aggiunto da blog/.ruby-version; chiedi al tuo sistema AI di installare 3.4.5 se non è presente). Rails 8 è dichiarato in Gemfile e installato automaticamente da bundle install.
  • Google Antigravity 2.0:il tuo assistente di programmazione in coppia con l'AI autonoma (scarica Google Antigravity 2.0). Accederai con il tuo account Gmail personale, senza bisogno di token API.

2. Autenticazione Google Cloud, configurazione dedicata e Credenziali predefinite dell'applicazione

Per evitare conflitti con configurazioni gcloud aziendali, personali o multi-account esistenti, ti consigliamo vivamente di creare una configurazione denominata dedicata per questo workshop:

# 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

💡 Perché una configurazione dedicata? L'utilizzo di gcloud config configurations create rails8-on-gcp-workshop isola tutte le impostazioni della CLI (account, progetto quota, regione predefinita) specificamente per questo workshop. Al termine, puoi tornare alla configurazione normale in qualsiasi momento con gcloud config configurations activate default.

Configurazione gcloud attiva

3. 🚨 Verifica della fatturazione Google Cloud

⚠️ Nota: i deployment di Google Cloud SQL e Cloud Run richiedono un account di fatturazione collegato attivo o crediti didattici validi per il workshop. Assicuriamoci che la configurazione sia corretta. Per farlo, esegui il controllo di verifica della fatturazione:

gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT

Assicurati che billingEnabled: true venga restituito. Puoi anche elencare tutti i tuoi account di fatturazione:

gcloud billing accounts list

Assicurati che almeno uno di questi mostri OPEN: True. Se nessuno è attivo, collega un account di fatturazione o riscatta il coupon per il credito del workshop nella pagina Fatturazione della console Google Cloud.

Account di fatturazione Google Cloud attivo

4. Clona il repository e accoppialo ad Antigravity

Clona il repository e accedi alla directory. Tieni presente che rimaniamo interamente su main:

git clone https://github.com/palladius/rails8-app-on-gcp.git
cd rails8-app-on-gcp

Apri questa directory in Google Antigravity. Antigravity ispezionerà automaticamente il repository, leggerà AGENTS.md e rimarrà in attesa come programmatore in coppia.

5. 🪫 Se Antigravity esaurisce i crediti senza costi

⚠️ I crediti Cloud che hai appena riscattato NON ricaricano Antigravity. Si tratta di due superfici di fatturazione separate: la quota di Antigravity si trova nei piani Google AI (Google One), mentre i voucher e i crediti Cloud vengono inseriti in un account di fatturazione Cloud. Il collegamento di un account di fatturazione al tuo progetto Google Cloud non ti darà un singolo prompt Antigravity aggiuntivo.

Se l'agente si interrompe con un messaggio relativo alla quota, segui questa scala: i primi due sono senza costi e immediati:

  1. Cambia pool di modelli. La quota viene monitorata per famiglia di modelli e Antigravity mostra due pool indipendenti (modelli Gemini e modelli Claude/GPT). I modelli Claude e GPT-OSS sono disponibili nel Livello senza costi, quindi un pool Gemini esaurito spesso lascia intatto l'altro. Utilizza il selettore del modello sotto la casella del prompt.Selettore di modelli antigravità: passa da un pool all'altro di Gemini, Claude e GPT-OSS
  2. Scegli un modello Flash anziché Pro. I limiti di frequenza sono correlati alla quantità di lavoro svolto dall'agente per richiesta, quindi un modello più leggero estende ciò che rimane.
  3. Utilizza la CLI Antigravity con la tua chiave API Gemini. Bring Your Own Key non è supportato nell'IDE Antigravity. La CLI è l'alternativa documentata. Crea una chiave senza costi in Google AI Studio (è necessario un progetto, ma nessun account di fatturazione), quindi scarica e configura la CLI Antigravity.
  4. Attendi o esegui l'upgrade. La quota del livello senza costi viene aggiornata settimanalmente (il messaggio di errore indica la data di reimpostazione). Google AI Pro/Ultra è l'unico modo documentato per aumentare la base di riferimento all'interno dell'app per computer; questi piani vengono aggiornati ogni cinque ore e possono utilizzare i crediti AI acquistati per il superamento.
  5. Sebbene questo sia un workshop su Antigravity, la maggior parte degli strumenti di codifica AI dovrebbe funzionare. Basta puntare il tuo strumento alle directory AGENTS.md e skills/ nel repository.

6. Convalida automatizzata del passaggio 0

Verifica che l'ambiente del passaggio 0 sia conforme al 100% alla suite di valutazione:

just workshop-eval 0

✨ Il momento wow: Antigravity e il motore di valutazione del workshop verificano le versioni della CLI, l'ambiente Ruby e l'autenticazione ADC in meno di 2 secondi.

3. Passaggio 1: avvio dell'infrastruttura Terraform e diagnostica pre-volo

💡 La strategia: il provisioning dei database gestiti come Google Cloud SQL PostgreSQL richiede circa 10-12 minuti. Anziché aspettare inattivi in un secondo momento, lanciamo l'infrastruttura immutabile tramite Terraform in background mentre sviluppiamo in locale.

1. Esegui la suite di diagnostica pre-volo automatica

Prima di lanciare l'infrastruttura cloud, esegui la suite di test pre-volo completa:

just workshop-test

Questo script (bin/workshop_diagnostics.rb):

  • Verifica la configurazione dell'identità GOOGLE_CLOUD_ACCOUNT.
  • Verifica il collegamento attivo di fatturazione e progetto.
  • Convalida le Credenziali predefinite dell'applicazione (ADC) per Vertex AI.
  • Conferma l'immagine canary seed di ActiveStorage (blog/app/assets/images/gcs_dev_image.jpg).

just workshop-test diagnostics output

Se .env non è presente, copialo dal modello documentato:

cp .env.dist .env
# Edit .env and configure GOOGLE_CLOUD_ACCOUNT with your Google/Gmail account

💡 Suggerimento:se just workshop-test segnala che l'API Billing è disabilitata nel tuo progetto, abilitala rapidamente tramite:

gcloud services enable cloudbilling.googleapis.com

2. ⏱️ Avvia Terraform Infrastructure in modo asincrono

Esegui la ricetta Terraform in un solo passaggio, che crea il bucket di stato del backend remoto e attiva il deployment dell'infrastruttura:

just terraform-apply

Terraform inizializzato correttamente

Alternativa manuale (o esegui direttamente `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 ..

Elementi di cui Terraform esegue il provisioning:

  • Bucket Google Cloud Storage:creato con accesso privato e firma delle credenziali IAM (iam: true).
  • Immagine di test canary:carica gcs_dev_image.jpg nel bucket per abilitare la verifica end-to-end dei blob.
  • Istanza Google Cloud SQL PostgreSQL:avvia il provisioning in background (~10-12 minuti).

Istanza Cloud SQL PostgreSQL nella console Google Cloud

💡 Nota:puoi ignorare tranquillamente l'avviso relativo ai backup automatici mancanti. Non ci serviranno per questo database del workshop (anche se i backup automatici giornalieri sono una best practice essenziale per qualsiasi database di produzione reale).

terraform apply completato correttamente con gli output

📝 Prendi nota dell'URL Cloud Run:l'applicazione Terraform dovrebbe riuscire e stampare gli output dell'infrastruttura. Annota il tuo cloud_run_url (o copialo negli appunti). Ti servirà nei passaggi successivi per verificare il servizio live.

3. (Facoltativo) Convalida automatizzata del passaggio 1

Verifica che i prerequisiti e le configurazioni del passaggio 1 siano soddisfatti:

just workshop-eval 1

⏱️ Nota:questa valutazione controlla le risorse cloud attive e potrebbe richiedere un paio di minuti. È completamente facoltativo: mentre viene eseguito nel terminale, puoi procedere immediatamente al passaggio successivo senza attendere.

4. Passaggio 2: la baseline locale, Mailpit e l'onboarding dell'amministratore

Il nostro punto di partenza è un'applicazione blog Rails 8 pulita e moderna in esecuzione su localhost con SQLite, intercettazione delle email Mailpit e ActiveStorage basato su disco.

1. Avvia l'app in locale

📁 Attenzione alla directory! Questo repository contiene l'app Rails in blog/ e Terraform in iac/. Il passaggio 1 ti ha lasciato nella radice del repository, quindi ogni comando del passaggio 2 viene eseguito da

blog/: Gemfile, bin/rails e compose.yaml vivono lì. L'esecuzione dalla radice non va a buon fine e viene visualizzato l'errore no configuration file provided: not found o manca Gemfile.

Sposta l'app Rails, verifica di utilizzare Ruby bloccato, quindi installa i gem e configura il database:

cd blog
ruby -v          # must print 3.4.5 — see Step 0 if it does not
bundle install
bin/rails db:setup

🧯 Errore di autorizzazione durante la scrittura in

/var/lib/gems/

o

ruby -v

mostra Ruby di sistema (ad es. 3.3.x)? Vanilla Linux system Ruby ignora completamente i file .ruby-version e tenta di installare gem a livello di sistema. Assicurati che rbenv sia caricato nella shell del terminale attivo:

eval "$(rbenv init - bash)"   # or: eval "$(rbenv init - zsh)"
ruby -v                       # confirm it now outputs 3.4.5!

Nota:se hai saltato l'installazione della versione 3.4.5 nel passaggio 0, esegui rbenv install 3.4.5. In alternativa, utilizza la modalità A (Docker Compose) di seguito (just compose-up), che esegue lo stack Ruby bloccato completamente all'interno dei container senza toccare i gem dell'host.

💡 Preferisci non pensarci? just install e just compose-up dalla radice del repository eseguono cd blog per te.

2. 🌱 Rilevamento automatico di Smart Seed e bootstrap dell'amministratore (problemi n. 21 e 25)

Il seed del database (db/seeds.rb) include la rilevamento automatico dell'ambiente intelligente:

  • Ispeziona l'adattatore di database attivo (SQLite o Postgres) e la configurazione di archiviazione.
  • Rileva la fase 0 (localhost) e crea automaticamente l'utente amministratore iniziale e inserisce il post pedagogico:
    • [LOCAL BASELINE] Welcome to Rails 8 on Localhost!
    • Allegato predefinito di immagine triste locale (local_sad_image.png) con filigrana che ti informa che lo spazio di archiviazione sul disco locale è temporaneo.
  • Attiva automaticamente un'email di reimpostazione della password tramite ActionMailer.

Verifica o esegui di nuovo il seed (utilizza automaticamente il tuo GOOGLE_CLOUD_ACCOUNT da .env):

bin/rails db:seed    # or from repo root: just seed

Avvia lo stack di sviluppo locale tramite Docker Compose:

docker compose up -d     # from blog/ — or `just compose-up` from the repo root

Docker Compose che avvia Rails, Mailpit e lo stack locale

L'applicazione, il server SMTP locale Mailpit e il visualizzatore di database Adminer vengono ora eseguiti insieme in container isolati:

  • App Rails: http://localhost:3000
  • Interfaccia utente web di Mailpit: http://localhost:8025
  • Interfaccia utente del database Adminer: http://localhost:8081

🧯 Conflitto con la porta 3000 o

server.pid

errore? Se in precedenza hai avviato un server di sviluppo nativo (bin/dev), arrestalo con Ctrl+C. Se Docker segnala che la porta 3000 è già allocata o trova un server.pid residuo, esegui la pulizia con:

just compose-down
rm -f blog/tmp/pids/server.pid
just compose-up

3. L'esperienza Mailpit e l'accesso amministratore

  1. Acquisire le email in uscita con Mailpit: apri http://localhost:8025 nel browser. Dai un'occhiata alla posta in arrivo locale. Durante db:seed, ActionMailer ha inviato un'email di onboarding dell'amministratore che Mailpit ha acquisito in modo sicuro a livello locale senza toccare server o credenziali email esterni:

Interfaccia utente web di Mailpit che mostra l'email di reimpostazione password intercettata 2. Accedi al blog: apri http://localhost:3000 nel browser.

  • Puoi fare clic sul link di reimpostazione password direttamente nell'email di Mailpit per impostare la password.
  • In alternativa, accedi utilizzando l'email del tuo account Google Cloud (da .env) e la password predefinita: Ch4ng3m3!!1.
  1. Osserva i badge di telemetria visiva:
    • Nota il banner e i badge gialli dell'ambiente nell'interfaccia utente: Notice: Ephemeral Database Active (POSTGRESQL) e [EPHEMERAL DB / STORAGE] 💾 Local.
    • Nota la filigrana del post: il timbro 🏠 (nanobanana_stamp_local.png nell'angolo in basso a destra dell'immagine di copertina). In questo modo, puoi verificare immediatamente che gli asset e il database siano attualmente associati all'archiviazione locale temporanea.

Home page del blog locale che mostra badge e avvisi di telemetria effimeri

💡 Suggerimento professionale facoltativo: esercizio con la console Rails Ti incuriosisce il modo in cui Rails interagisce con i dati iniziali dalla CLI? Accedi alla console interattiva:

bin/rails console

Ispeziona l'utente amministratore iniziale in modo dinamico utilizzando la variabile di ambiente:

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. Convalida automatizzata del passaggio 2

Verifica la configurazione di base locale e dell'amministratore:

just workshop-eval 2

just workshop-eval 2 verification output

5. Il problema: container stateless

I container Cloud Run sono stateless e temporanei. Se eseguiamo il deployment del nostro database SQLite e della directory locale storage/ direttamente su Cloud Run, tutti i post e le immagini caricate verranno eliminati definitivamente ogni volta che un container viene scalato a zero o riavviato.

Nel passaggio successivo, eseguiremo intenzionalmente il deployment di questa configurazione effimera in Cloud Run per assistere in prima persona allo Stateless Shock.

5. Passaggio 3: esegui il deployment 1 - The Stateless Shock (WOW iniziale in 3 minuti)

Invece di aspettare 15-20 minuti per i database cloud prima di vedere qualcosa sul web, lo sviluppo serverless moderno inizia con una vittoria immediata: il deployment della nostra applicazione Rails a singolo container direttamente su Google Cloud Run in meno di 3 minuti.

1. Il Rewind della macchina del tempo

Per garantire che la nostra configurazione iniziale sia effimera al 100% (database SQLite locale e archiviazione di file su disco locale), utilizza il motore Zero-Branch Time-Machine:

just workshop-rewind 1

💡 Che cosa è successo?workshop-rewind 1 ha applicato l'overlay di configurazione stage-1-stateless a blog/config/ senza uscire dal ramo main. La tua app è configurata con SQLite sul disco del container e ActiveStorage sul file system locale.

2. Deployment di Puma a contenitore singolo in Cloud Run

Esegui il deployment direttamente dal codice sorgente a Cloud Run. Modern Rails 8 genera automaticamente un Dockerfile ufficiale pronto per la produzione (con build multifase, jemalloc e sicurezza non root). Quando esegui il deployment dalla cartella blog/, Google Cloud Build rileva direttamente questo Dockerfile nativo e esegue il provisioning di un servizio serverless gestito:

# 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

Dovresti visualizzare un output di avanzamento simile a questo:

gcloud run deploy building and deploying container

⏳ L'operazione dovrebbe richiedere un paio di minuti mentre Cloud Build pacchettizza il container e esegue il provisioning della revisione Cloud Run.

🧯 Risoluzione dei problemi e avvertenze comuni

PERMISSION_DENIED: Build failed because the default service account is missing required IAM permissions

? Due cause diverse generano lo stesso errore:

  1. gcloud ha appena abilitato un'API per te. Se il deployment ha stampato The following APIs are not enabled ... cloudbuild.googleapis.com e hai risposto Y, la build potrebbe essere iniziata prima che la propagazione delle nuove autorizzazioni sia stata completata. Il prompt di gcloud avvisa che "questa operazione richiederà alcuni minuti". Attendi un minuto ed esegui di nuovo lo stesso comando. In genere l'operazione va a buon fine. Il passaggio 1 terraform apply in genere abilita questa API, quindi non dovresti visualizzare la richiesta.
  2. Nel service account Compute predefinito mancano davvero dei ruoli. I progetti Google Cloud moderni applicano il principio del privilegio minimo, il che interrompe i deployment delle origini. Correggi con:
    just project-status   # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builder
    
    Ora anche just workshop-test segnala questo problema, quindi vale la pena eseguire di nuovo il comando se il deployment continua a non riuscire.

Durante il deployment:

  1. Cloud Run crea l'immagine container Rails.
  2. Assegna un dominio TLS pubblico e sicuro: https://blog-[hash]-[region].a.run.app.
  3. Il traffico inizia a essere indirizzato a Puma sulla porta 8080.

3. ✨ Il primo momento WOW

Apri l'URL di Cloud Run generato nel browser.

  1. La tua moderna applicazione Rails 8 è online su Google Cloud.
  2. Accedi con le tue credenziali di amministratore (GOOGLE_CLOUD_ACCOUNT e APP_ADMIN_PASSWORD). Se non trovi il pulsante di accesso, fai clic su "Nuovo post" e ti verrà chiesto prima l'utente e la password:Pagina di accesso di Rails 8 con banner di telemetria effimeri
  3. Fai clic su "Nuovo post", scrivi un articolo intitolato "Il mio primo post di Cloud Run", allega un'immagine e fai clic su Crea post.
  4. Il post viene pubblicato con la formattazione completa e l'immagine viene visualizzata.
  5. Nota la telemetria visiva:
    • Badge dell'intestazione: [EPHEMERAL DB / STORAGE] 💾 Local
    • Filigrana immagine: il bollino 🏠 (badge del disco temporaneo 127.0.0.1 nell'angolo in basso a destra).

🐝 Partecipa alla classifica dell'alveare del workshop live. Se sei online e il tuo supervisore sta mostrando la classifica e vuoi partecipare, aggiungi qui il tuo URL Cloud Run: 👉 Registra Cloud Run nella classifica

Dettagli della revisione del servizio del blog della console Google Cloud Run

4. 💥 Il problema: lo shock stateless e la trappola della "soluzione alternativa Puma"

Cloud Run è una piattaforma serverless stateless. Quando il traffico web scende a zero, Cloud Run esegue lo scale down fino a zero istanze container per risparmiare denaro. Quando arriva una nuova richiesta HTTP o viene eseguito il deployment di una nuova revisione del container, Cloud Run avvia un'immagine container nuova e pulita.

Banner Lavori bloccati

Quando crei un post o alleghi un'immagine in un deployment a singolo container, Rails mette in coda le attività ActiveJob (come l'analisi delle dimensioni delle immagini o l'indicizzazione dei metadati). Tuttavia, poiché nessuno esegue un worker in background, vedrai il banner di avviso:

Avviso: 8 job in background in attesa di esecuzione

La soluzione allettante: esecuzione di Solid Queue all'interno di Puma

Uno sviluppatore intelligente potrebbe dire: "Aspetta! Rails 8 consente di eseguire Solid Queue direttamente all'interno del processo del server web Puma attivando

SOLID_QUEUE_IN_PUMA=true

variabile di ambiente."

Testiamo questa soluzione alternativa su 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

Ora, torna al browser e aggiorna la pagina:

💥 Lo shock senza stato:

  1. Buone notizie: il worker Solid Queue è ora attivo in Puma. Tutti i nuovi job vengono eseguiti immediatamente.
  2. Doccia fredda (il trucco):
    • Poiché Cloud Run ha eseguito il deployment di una nuova revisione, l'istanza container precedente è stata sostituita.
    • L'articolo che hai scritto e il file del database SQLite sul disco sono stati completamente cancellati.
  3. La lezione di architettura: l'esecuzione di worker in background all'interno di Puma consuma preziosa CPU/RAM dei thread web e non risolve comunque la persistenza.

5. Convalida automatizzata del passaggio 3

Verifica il deployment di Cloud Run e la gestione degli avvisi:

just workshop-eval 3

✨ La lezione: le soluzioni alternative per i singoli container come SOLID_QUEUE_IN_PUMA sono utili per lo sviluppo locale, ma nell'architettura cloud moderna:

  • I file multimediali richiedono Google Cloud Storage (GCS) (passaggio 4).
  • I dati relazionali e le code di lavoro richiedono Google Cloud SQL gestito (passaggio 5).
  • I worker in background pesanti devono trovarsi in un container sidecar dedicato (passaggio 6).

6. Passaggio 4: esegui il deployment 3 - GCS Persistent Storage e avviso POLA

In questo passaggio, separiamo l'archiviazione di contenuti multimediali e file dal disco del container passando ad ActiveStorage su Google Cloud Storage (GCS), utilizzando URL firmati di breve durata tramite l'API IAM Credentials (iam: true).

Prima di modificare qualsiasi configurazione, scopri le tre cose di cui Rails ha bisogno per comunicare con GCS:

① ID progetto + Nome bucket: Rails deve sapere dove archiviare i blob. L'app legge GOOGLE_CLOUD_PROJECT dall'ambiente e deriva il nome del bucket come ${GOOGLE_CLOUD_PROJECT}-activestorage-prod. Terraform del passaggio 1 ha già creato questo bucket, quindi non è necessario fare altro.

② Un service account con le autorizzazioni giuste: Cloud Run viene eseguito come service account Compute Engine predefinito (PROJECT_NUMBER-compute@developer.gserviceaccount.com). Questo service account richiede due ruoli IAM sul bucket:

  • roles/storage.objectAdmin: per caricare e pubblicare i blob
  • roles/iam.serviceAccountTokenCreator su se stesso per chiamare l'API IAM Credentials signBlob e generare URL firmati di breve durata

⚠️ La scorciatoia allettante :

public: true Il modo più rapido per far funzionare GCS è impostare public: true in storage.yml e concedere allUsers:objectViewer sul bucket. Le immagini vengono caricate immediatamente, senza bisogno di accedere. Non farlo. Un bucket pubblico espone per sempre tutti i contenuti multimediali degli utenti caricati su internet. Il nostro progetto utilizza iam: true per mantenere il bucket privato al 100%. Ogni URL immagine è un token firmato e in scadenza generato al volo dall'API IAM Credentials.

③

ACTIVE_STORAGE_SERVICE=google: la singola variabile di ambiente che sposta Rails dal disco locale a GCS in fase di runtime. Nessuna modifica al codice, nessun nuovo deployment della logica dell'applicazione: basta attivare/disattivare una variabile di ambiente.

1. Il Rewind della macchina del tempo

Avanza nella macchina del tempo fino alla fase 2:

just workshop-rewind 2

Esamina blog/config/storage.yml: vedrai che l'app definisce tre ambienti GCS denominati (dev / test / prod), tutti che utilizzano lo stesso pattern sicuro:

<% 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

Architettura di firma IAM di GCS

2. Concessione delle autorizzazioni IAM Storage & Signing

Assicurati che il service account di runtime di Cloud Run disponga delle autorizzazioni per firmare gli URL e caricare gli oggetti:

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. Terzo deployment su Cloud Run con GCS collegato

Esegui nuovamente il deployment con GCS abilitato. Assicurati di trovarti all'interno della cartella blog/ (altrimenti Cloud Run tenta di eseguire la build utilizzando Buildpacks anziché Dockerfile di Rails 8, causando errori di build).

Utilizza --update-env-vars (non --set-env-vars) per conservare le variabili di ambiente esistenti (chiave master, configurazione IAP e così via):

cd blog
gcloud run deploy blog \
  --source . \
  --region $GOOGLE_CLOUD_REGION \
  --update-env-vars ACTIVE_STORAGE_SERVICE=google

💡 Suggerimento: se il deployment non va a buon fine, fai clic sul link ai log di Cloud Build fornito nell'output del terminale (come mostrato nella figura seguente). Puoi anche chiedere ad Antigravity di aiutarti a decifrare e correggere l'errore.

Link al log di errore di Cloud Build nell'output del terminale

4. ✨ L'immagine sopravvissuta e il timbro del cloud

  1. Aggiorna l'URL del blog.
  2. Crea un nuovo post del blog intitolato "Surviving the Cloud" (Sopravvivere al cloud) e carica una foto.
  3. Guarda l'angolo in basso a destra dell'immagine:
    • Il bollino 🏠 è sparito.
    • Viene sostituito dal colorato timbro GCS Cloud (nanobanana_stamp_cloud.png).
  4. Forza un altro riavvio del container (che dovrebbe attivare una quarta revisione):
    gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s)
    
  5. Aggiorna la pagina: durante la reimpostazione del database SQLite, il binario dell'immagine è al sicuro in Google Cloud Storage. Verifica tramite CLI:
    gcloud storage ls -l -r gs://$GCS_BUCKET/
    

Ecco un elenco di blob e oggetti multimediali caricati che potresti visualizzare nella console del browser Google Cloud Storage nel bucket produzione (gs://$GCS_BUCKET):

Dettagli del bucket della console Google Cloud Storage che mostrano i blob di immagini permanenti

5. ⚠️ Il problema: banner di avviso per i job bloccati

Quando hai caricato l'immagine, ActiveStorage ha messo in coda un job di analisi (ActiveStorage::AnalyzeJob) per estrarre dimensioni e metadati.

Tuttavia, al momento Cloud Run esegue solo un singolo contenitore web (puma). In Rails 8, Solid Queue archivia i job in background nel database, ma nessuno esegue bundle exec rails solid_queue:start.

Guarda la parte superiore della pagina del blog: vedrai un banner di avviso luminoso visualizzato da blog/app/views/layouts/_check_stuck_jobs.html.erb.

Risolviamo questo problema correttamente nel passaggio 6, quando passeremo a un'architettura sidecar multiconteiner di livello di produzione con un container worker dedicato che esegue Solid Queue indipendentemente dal traffico web.

6. Convalida automatica del passaggio 4

Verifica la configurazione di GCS e la telemetria dei job bloccati:

just workshop-eval 4

7. Passaggio 5: elenco di controllo pre-volo: Cloud SQL, secret e connettività al database

Mentre hai acquisito competenze sui container stateless e su Google Cloud Storage nei passaggi 3 e 4, Terraform ha terminato la preparazione del backend di produzione in background.

Prima di eseguire il deployment della nostra architettura sidecar di produzione completa a tre container (Deploy 4 nel passaggio 6), questo passaggio funge da ispezione pre-volo rapida per verificare l'integrità, la connettività e l'accesso ai secret del database.

0. Variabili di ambiente di bootstrap

Esporta tutte le variabili di shell richieste direttamente dagli output di 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. Controllo dell'integrità di Cloud SQL

Verifica che il provisioning iniziale dell'istanza PostgreSQL gestita sia stato completato e che sia in stato integro:

echo "Checking instance: $SQL_INSTANCE_NAME"
gcloud sql instances describe $SQL_INSTANCE_NAME --format="value(state)"

L'output deve essere RUNNABLE.

2. (Facoltativo) Test della connettività del database dal laptop

In produzione, i container Cloud Run si connettono in modo sicuro a Cloud SQL tramite un proxy sidecar criptato (127.0.0.1:5432) senza esporre il database a internet pubblico (0.0.0.0/0).

ℹ️ Nota: questo passaggio è completamente facoltativo. In Cloud Run, il container sidecar ufficiale del Cloud SQL Auth Proxy gestisce automaticamente la connettività mTLS. Non devi installare nulla in locale per completare il workshop.

Se hai installato cloud-sql-proxy e psql sul tuo computer e vuoi verificare la connettività diretta dal tuo laptop:

# Optional: test connectivity via Google-managed mTLS proxy
gcloud sql connect $SQL_INSTANCE_NAME --user=rails_user --database=rails_production

(Se viene richiesta una password, inserisci

$DB_PASSWORD

. In caso contrario, puoi passare direttamente al passaggio 3 di seguito.)

3. Ispezione dei secret di cui è stato eseguito il provisioning con Terraform

Non archiviare mai password di database, chiavi API o chiavi master Rails in testo normale in Git o nelle variabili di ambiente del container. Nell'architettura cloud moderna, utilizziamo Google Cloud Secret Manager per l'inserimento di runtime zero-trust.

ℹ️ Terraform ha gestito questa operazione per te nel passaggio 1:

  1. rails-master-key: per decrittografare le credenziali Rails in fase di runtime.
  2. rails-db-password: una password casuale crittograficamente sicura che corrisponde all'utente Cloud SQL.
  3. rails-admin-password: una password sicura per l'utente amministratore iniziale.

Verifica che i secret siano presenti e attivi in Secret Manager:

gcloud secrets list --filter="name:rails-"

Elenco della console Google Cloud Secret Manager con i secret dell'applicazione configurati

🔑 Sincronizzazione di master.key con il laptop (sincronizzazione locale)

Poiché blog/config/master.key è ignorato da Git per motivi di sicurezza, la tua clonazione locale potrebbe non averlo ancora. Esegui questo snippet di auto-ripristino per estrarlo da Secret Manager in modo che gli strumenti Rails locali possano decriptare le credenziali:

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

🧪 Playground: vuoi creare un secret personalizzato solo per vedere quanto è facile? Secret Manager non è solo per Rails. Puoi creare qualsiasi secret con un semplice comando CLI:

echo -n "MySuperSecretValue123!" | gcloud secrets create workshop-fun-secret --data-file=-
echo "✨ Created workshop-fun-secret in Secret Manager! 🪄"

4. Verifica delle autorizzazioni di Secret Accessor

Affinché i container Cloud Run possano montare questi secret come variabili di ambiente o volumi montati all'avvio, il service account di runtime ($RUN_SA) deve disporre del ruolo roles/secretmanager.secretAccessor per ogni secret.

Terraform lo ha già concesso nel passaggio 1. Verifica l'associazione della policy IAM su 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"

Dovresti vedere:

ROLE                                 MEMBERS
roles/secretmanager.secretAccessor   serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com

Confronto della sicurezza del proxy di autenticazione Cloud SQL

5. Convalida pre-volo automatizzata

Esegui la suite di test automatizzata del passaggio 5 per verificare che il database sia eseguibile, che i secret non siano vuoti e che le autorizzazioni IAM siano verdi:

just workshop-eval 5

8. Passaggio 6: esegui il deployment di 4 - Enterprise Multi-Container Sidecars (lo standard di riferimento)

Ora assembla l'architettura di riferimento definitiva: Cloud Run multiconteiner.

1. Ripristino del Gold Standard

Ripristina la configurazione del repository allo stato canonico main:

just workshop-restore-gold

2. Esame del progetto di produzione con tre container

Apri blog/compose.prod.yaml ed esamina l'architettura:

Architettura di produzione multicanale di Cloud Run

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)]

Il deployment di produzione esegue tre container coordinati che condividono la stessa rete locale:

  1. web: gestisce il traffico HTTP sulla porta 8080 tramite Puma.
  2. worker: container di elaborazione in background dedicato che esegue bundle exec rails solid_queue:start.
  3. cloudsql-proxy: Official Google Cloud SQL Auth Proxy sidecar (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2) bound to 127.0.0.1:5432.

3. Esecuzione delle migrazioni del database tramite il job Cloud Run

Prima di indirizzare il traffico web al nostro nuovo deployment, dobbiamo eseguire le migrazioni del database e il seeding iniziale su Cloud SQL.

⚖️ Come eseguire le migrazioni nei container: due strategie

Negli ambienti cloud containerizzati, esistono due approcci comuni per l'esecuzione delle migrazioni dei database:

  • Opzione A: automatica all'interno di Docker Entrypoint (entrypoint.sh esegue db:migrate all'avvio del container)
    • Vantaggi:completamente automatizzato; gli sviluppatori non devono ricordarsi di eseguire un comando.
    • Svantaggi:pericoloso nella scalabilità automatica serverless. Quando Cloud Run esegue lo scale up fino a 10 istanze contemporaneamente in caso di traffico elevato, 10 container competono per eseguire le migrazioni contemporaneamente, causando il blocco del database, race condition dello schema o timeout di avvio.
  • Opzione B: esplicita tramite Cloud Run Job (la nostra scelta architetturale)
    • Vantaggi:controllo completo, sicurezza transazionale e nessuna condizione di competizione. Viene eseguito come attività di container dedicata una tantum completamente separata dalla gestione del traffico web. Se una migrazione non riesce, la pubblicazione web non viene interessata.
    • Svantaggi:richiede l'attivazione di un job CLI o di un passaggio CI/CD prima del deployment.

Scegliamo l'opzione B per la massima affidabilità della produzione.

⚠️ Critico: DATABASE_URL formato e rigore RFC 3986 di Ruby 3.4.5

Cloud Run Jobs si connette a Cloud SQL utilizzando il proxy socket Unix nativo (/cloudsql/PROJECT:REGION:INSTANCE). DATABASE_URL deve utilizzare il formato con tre barre:

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'app utilizza 4 database (principale, coda, cache, cavo) per database.yml. Tutte e quattro le variabili di ambiente DATABASE_*_URL devono puntare alla stessa istanza Cloud SQL, altrimenti le tabelle Solid Queue/Cache/Cable non verranno create.

Dopo db:prepare, carica gli schemi 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

Output di gcloud run jobs executions describe JOBNAMENella figura sopra, puoi vedere un possibile output di

gcloud run jobs executions describe $JOBNAME

Console Google Cloud Run Jobs che mostra lo stato Riuscito del job rails-migrateConsole Google Cloud Run Jobs che mostra

rails-migrate

execution Succeeded

4. Quarto deployment: deployment di Cloud Run multiconteiner tramite Docker Compose

Ora implementa lo stack di produzione a tre container originale (web, worker, cloudsql-proxy) direttamente da 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

Scheda Container della console Google Cloud Run che mostra i tre container collateraliConsole Google Cloud Run: scheda "Container" che mostra tutti e tre i container collaterali (

cloudsql-proxy

,

web

,

worker

)

💡 Curiosità dell'interfaccia utente / Bug della console: nel menu a discesa della console, potresti notare che Cloud Run etichetta la prima voce come Ingress container: cloudsql-proxy. Non preoccuparti. Si tratta di un semplice problema visivo dell'interfaccia utente nella console Google Cloud (monitorato in #149) causato dall'ordinamento delle dipendenze dei container. In realtà, web è l'unico container in ascolto sulla porta HTTP 8080 e gestisce il 100% del traffico web in entrata, mentre cloudsql-proxy gestisce rigorosamente il traffico PostgreSQL privato sulla porta 5432.

5. ✨ Il momento wow: zero avvisi e persistenza completa nel cloud.

Fai un respiro profondo e ammira il tuo schermo: tutti i banner di avviso sono scomparsi! 🪄

Blog di produzione su Cloud Run con zero avvisi e post persistente su CloudIl blog su Rails 8 di produzione: UI incontaminata, nessun banner di avviso e stato Cloud SQL persistente.

L'applicazione è ora ufficialmente di livello di produzione e cloud-native:

  • Supportato da Google Cloud SQL (PostgreSQL) gestito tramite tunneling mTLS sicuro.
  • I contenuti multimediali vengono archiviati in modo sicuro in Google Cloud Storage privato con la firma dei blob IAM.
  • Nessun avviso di spazio di archiviazione effimero e nessun avviso di job bloccati.

Apri l'URL di Cloud Run:

  1. Esamina i badge di telemetria:
    • [CLOUD PERSISTENT 🐘 ☁️] è verificato nel seed del database.
    • I badge Google Cloud Run e GCS nel piè di pagina sono verdi.
    • Il banner di avviso relativo ai job bloccati non è più disponibile.
  2. Creare post e commenti del blog.
  3. Riavvia o esegui nuovamente il deployment tutte le volte che vuoi: i tuoi dati, post, commenti e asset sopravvivono per sempre in Cloud SQL e GCS.

6. Convalida automatizzata del passaggio 6

Verifica la configurazione multi-container:

just workshop-eval 6

9. Passaggio 7: pipeline di AI generativa, Podcastifier e la caccia al tesoro di GCS 🏴‍☠️

Con Solid Queue in esecuzione in un container dedicato e Google Cloud Storage attivo, possiamo scatenare l'AI generativa asincrona.

1. Il generatore di copertine vintage NanoBanana (pipeline di AI generativa pronta all'uso)

Mascotte Nano Banana

Buone notizie: questa funzionalità è già completamente implementata nel codebase. Non devi scrivere alcun codice: il tuo compito qui è attivare e osservare una pipeline cloud GenAI asincrona in azione.

🧠 Come funziona dietro le quinte

  • Autenticazione:non sono necessarie chiavi API. Utilizza automaticamente le credenziali predefinite dell'applicazione (roles/aiplatform.user su Vertex AI) tramite il service account di runtime Cloud Run. In alternativa, se viene fornito GEMINI_API_KEY, chiama direttamente l'API Gemini.
  • La pipeline:quando viene creato un articolo senza un'immagine di copertina, un callback after_commit inserisce GenerateCoverImageJob in Solid Queue.
  • Modello: chiede al modello multimodale di Google (Gemini 2.5 Flash Image / Imagen su Vertex AI) di generare un autentico poster di un film italiano vintage degli anni'60 ("Locandina di un film 1960") con una banana cameo e un rubino lucido a forma di "8".
  • Filigrana di provenienza dinamica:utilizza libvips per stampare dinamicamente un badge visivo nell'angolo (timbro a forma di nuvola colorata su GCS).
  • Turbo Stream Hot-Swap: quando l'immagine viene allegata, Turbo::StreamsChannel.broadcast_refresh_to(post) aggiorna il browser del lettore in tempo reale senza ricaricare manualmente la pagina.

Diagramma di sequenza dell'architettura asincrona di NanoBanana AI

📐 Visualizza l'origine del diagramma di sequenza 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!

🧪 Prova le live!

Puoi attivarlo in due modi:

  • Opzione A (interfaccia utente web): fai clic su Nuovo post, inserisci un titolo come "Architettura serverless con Ruby on Rails 8", scrivi dei contenuti e lascia vuoto l'input del file dell'immagine di copertina. Fai clic su Crea post.
  • Opzione B (CLI Runner): esegui dal terminale:
    bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
    

Attendi circa 15-30 secondi: guarda i log del worker Solid Queue in Cloud Run. Il poster vintage verrà visualizzato direttamente nel tuo post.

📸 TODO(riccardo): add screenshot of a blog post with an AI-generated vintage 1960s Italian movie poster featuring a cameo banana and ruby 8

2. La quest di Bilingual Podcastifier (esercizio di programmazione pratico di antigravità)

⚠️ Avviso importante:a differenza del generatore di copertine sopra, questa funzionalità NON è ancora implementata.

Se apri blog/app/jobs/podcastifier_job.rb, troverai uno stub di scaffolding vuoto:

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

La tua missione in questo esercizio pratico è programmare in coppia con Google Antigravity per completare questa funzionalità:

  1. Prompt Antigravity:"Aiutami a implementare PodcastifierJob in app/jobs/podcastifier_job.rb. Deve recuperare il post, tradurre o sintetizzare una panoramica audio in italiano utilizzando Google Cloud Text-to-Speech (voce "it-IT-Wavenet-A" con codice lingua "it-IT" tramite credenziali ADC) e allegare il file audio a post.podcast_audio_it utilizzando ActiveStorage."
  2. Aggiungi player alla visualizzazione:in app/views/posts/show.html.erb, aggiungi un player HTML5 quando @post.podcast_audio_it.attached?.
  3. Attiva sintesi:una volta salvato, Solid Queue eseguirà il lavoro di sintesi vocale in modo asincrono in background.

UI bilingue di Podcastifier con due lettori audio

🎧 Esempi di output di Podcastifier:

💡 Hai bisogno di aiuto o non riesci ad andare avanti? Una soluzione di riferimento completa e testata è disponibile nel ramo solutions/podcastifier. Puoi esaminarlo in qualsiasi momento con git diff solutions/podcastifier.

3. [FACOLTATIVO] 🏴‍☠️ La caccia al tesoro di GCS (recupero dei blob della console)

ℹ️ Controllo prerequisiti: locale

cloud-sql-proxy

CLI Required Questo esercizio facoltativo connette il tuo laptop locale direttamente a Google Cloud SQL tramite un tunnel mTLS criptato. Verifica se hai installato il proxy CLI:

which cloud-sql-proxy
  • Se è installato, continua a leggere.
  • Se NON è installato e vuoi completare questa missione: installalo con:
    # 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
    
  • Altrimenti: puoi passare direttamente alla convalida automatica del passaggio 7.

Ricordi la foto che hai caricato nel passaggio 4 prima che il riavvio del container cancellasse il database SQLite temporaneo? Il file immagine è ancora al sicuro nel tuo bucket GCS privato come "blob orfano".

Utilizziamo la console Rails per recuperarlo e allegarlo a un post di 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

All'interno della 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): add screenshot of the interactive Rails console executing the GCS Treasure Hunt snippet and rescuing orphaned ActiveStorage blobs

Aggiorna il blog: la foto caricata durante lo Stateless Shock del passaggio 4 viene ripristinata e allegata in modo permanente al post di Cloud SQL.

4. Convalida automatizzata del passaggio 7

Verifica i job e gli asset di AI generativa:

just workshop-eval 7

10. Passaggio 8: scegli la tua avventura / missioni avanzate 🏆

Ora che hai acquisito dimestichezza con l'architettura di riferimento canonica, scegli la tua missione finale.

🛡️ Quest 1: Zero-Trust Google IAP (Identity-Aware Proxy)

  • Difficoltà: media (sicurezza aziendale)
  • L'obiettivo:proteggere l'applicazione Cloud Run in modo che solo i tuoi account Google / Gmail verificati possano accedervi, eliminando completamente l'accesso con password.
  • Come funziona:
    1. Un bilanciatore del carico delle applicazioni HTTPS esterno termina Google OAuth e verifica l'identità prima che il traffico raggiunga Cloud Run.
    2. Google inoltra le intestazioni dell'identità verificata (X-Goog-Authenticated-User-Email).
    3. Rails esegue automaticamente l'accesso con l'identità Google verificata tramite 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
      
    4. Attiva il modulo Terraform in iac/iap.tf con enable_iap = true e specifica i tuoi Account Google consentiti:
      iap_allowed_users = [var.google_cloud_account]
      

Banner Accesso riconosciuto da IAP

📊 Quest 2: Telemetria SRE di produzione e Cloud Logging

  • Difficoltà:media (osservabilità)
  • Obiettivo:trasmetti in streaming i log delle applicazioni JSON strutturati con ID di correlazione delle tracce direttamente a Google Cloud Logging e rileva le eccezioni di produzione in tempo reale con Cloud Error Reporting.
  • Come funziona:
    • Configura blog/config/environments/production.rb per generare log JSON strutturati con etichette di traccia Google Cloud.
    • Attiva un'eccezione di test intenzionale e guarda Google Cloud Error Reporting raggruppare e inviarti una notifica istantaneamente.

📸 TODO(riccardo): add screenshot of Google Cloud Error Reporting showing grouped production exceptions and Cloud Logging structured JSON payload with trace labels

🧠 Quest 3: pgvector Ricerca semantica e RAG di Gemini

  • Difficoltà: difficile (progetto finale sull'AI generativa)
  • Obiettivo:cercare articoli in modo concettuale utilizzando i vector embedding archiviati in PostgreSQL su Cloud SQL.
  • Come funziona:
    • Abilita l'estensione vector sull'istanza Cloud SQL PostgreSQL:
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • Aggiungi il gem neighbor a blog/Gemfile e genera text embedding con Gemini (text-embedding-004) su Post#after_save.
    • Esegui query di distanza del coseno (<=>) per potenziare una barra di ricerca semantica con Turbo Streams.

📸 TODO(riccardo): add screenshot of the live blog semantic search bar returning conceptual matches with cosine similarity scores

⚡ Missione 4: assistente per l'audit SEO e del rendimento

  • Difficoltà:facile / divertente (esperienza di sviluppo)
  • L'obiettivo: esamina le metriche Core Web Vitals, Largest Contentful Paint (LCP) e gli indici di leggibilità Flesch-Kincaid / Fog utilizzando gli strumenti Antigravity e Speedgrapher.

📸 TODO(riccardo): add screenshot of Speedgrapher / Lighthouse SEO and Core Web Vitals audit summary

🤔 Curiosity Box: Getting a Live Rails Console on Cloud Run

💡 La domanda:nelle VM tradizionali o nelle piattaforme PaaS in stile Heroku, gli sviluppatori utilizzano comunemente SSH in un'istanza in esecuzione o heroku run rails console per ispezionare o correggere i dati di produzione in tempo reale. Qual è l'equivalente nativo di Cloud Run e come si esegue l'hardening?

Perché

gcloud run exec

non esiste per i servizi Cloud Run

Cloud Run è una piattaforma serverless: il tuo container può essere eseguito come zero, una o centinaia di istanze identiche, senza identità stabile o shell indirizzabile. Questo è fondamentalmente diverso da Kubernetes (kubectl exec), dove un pod specifico ha un socket denominato a cui puoi collegarti.

# ❌ 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

I quattro pattern nativi di GCP

Pattern

Comando

Interattivo?

Sicurezza

1. Proxy locale + DB remoto

DATABASE_URL=... rails console

✅ Completo

🔒 mTLS del proxy di autenticazione Cloud SQL

2. Job Cloud Run (una tantum)

gcloud run jobs execute --wait

❌ Solo script

🔒 Ruolo IAM richiesto

3. Gemma della console web + IAP

Browser → /rails/console

✅ Completo

🛡️ IAP Zero-Trust

4. Cloud Shell + Proxy

Cloud Shell → bin/rails c

✅ Completo

🔒 Identità Google

Pattern 1 (consigliato per l'ispezione dei dati): esegui una console Rails locale che comunica direttamente con Cloud SQL PostgreSQL tramite il Cloud SQL Auth Proxy:

# 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

Ottieni la piena interattività IRB con zero esposizione alla rete: il proxy Cloud SQL utilizza mTLS su un socket di dominio Unix o loopback.

Pattern 3 (console web + IAP Zero-Trust, il percorso del browser protetto):

La web-console gem fornisce una sessione IRB basata su browser. Senza protezione, si tratta di una vulnerabilità di sicurezza critica, quindi non esporla mai pubblicamente. Tuttavia, se combinata con Google IAP (Quest 1), la superficie di attacco si riduce: solo le identità Google verificate nella tua lista consentita possono raggiungere l'endpoint /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

⚠️ Il pattern 3 richiede l'abilitazione di Quest 1 (IAP). Senza, /rails/console su un URL pubblico equivale a lasciare aperta la sala server. Con IAP, diventa un potente strumento per gli operatori rafforzato da Zero Trust di livello Google.

Chiedi ad Antigravity: "Mostrami come correre

rails console --sandbox

in modo sicuro sul mio database di produzione Cloud SQL utilizzando il Cloud SQL Auth Proxy".

🏆 Diploma su The Hive: prova di lavoro con convalida del supervisore

Una volta completata la missione che hai scelto, richiedi il trofeo di laurea del passaggio 8 🏆 nella classifica dell'alveare:

  1. Invia la tua missione su GitHub:
    • Apri un nuovo problema nel repository ufficiale: github.com/palladius/rails8-app-on-gcp/issues/new
    • Formato del titolo: 🎓 [Step 8 Completed] :
    • Nel corpo, includi una breve descrizione di ciò che hai realizzato e un link al deployment live di Cloud Run.
  2. Collega l'app Cloud Run al problema:
    • Prendi nota del numero del problema (ad es. 88 o dell'URL completo https://github.com/palladius/rails8-app-on-gcp/issues/88).
    • Inserisci la variabile di ambiente STEP_8_GHI nel tuo servizio Cloud Run:
      gcloud run services update blog \
        --region europe-west1 \
        --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER>
      
    • Verifica che l'endpoint /status.json ora riporti la tua missione:
      curl -s https://<YOUR_APP>.run.app/status.json | jq .quest
      
      Dovresti visualizzare:
      {
        "step_8_completed": true,
        "ghi_issue": 88,
        "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88"
      }
      
  3. Ottieni la revisione del supervisore e sblocca il trofeo d'oro:
    • Controlla la classifica dell'alveare: la tua riga ora mostrerà un badge ambra: ⏳ GHI #XX review pending.
    • Chiedi ai responsabili del workshop (@palladius, @emilianodellacasa o @ricc) di ispezionare la tua implementazione.
    • Non appena un supervisore commenta LGTM il tuo problema, The Hive aumenterà automaticamente i tuoi progressi a 8/8 🏆 con un badge dorato/viola brillante e un trofeo permanente che rimanda direttamente al tuo problema di progetto finale.

11. Conclusione e pulizia 🎓

Complimenti! 🎉

Hai creato ed eseguito il deployment di un'applicazione Rails 8 di livello di produzione e pronta per l'uso aziendale su Google Cloud:

  • Zero-Branch Progression: ha acquisito la padronanza dei workflow moderni con le sovrapposizioni Time-Machine su main.
  • Cloud Storage: spazio di archiviazione di oggetti privato e scalabile con firma dei blob IAM (iam: true).
  • Cloud SQL: PostgreSQL gestito protetto con tunnel mTLS del proxy di autenticazione Cloud SQL.
  • Secret Manager:nessuna perdita di credenziali in testo normale o di file .env.
  • Cloud Run Multi-Container:file collaterali isolati di Puma web, Solid Queue worker e proxy.
  • AI generativa: generazione asincrona di poster vintage e sintesi di podcast TTS con Vertex AI.
  • Missioni:IAP Zero Trust, osservabilità SRE e incorporamenti pgvector.

🧹 Pulizia delle risorse

Per evitare addebiti continui dopo aver completato il workshop, assicurati di liberare spazio dalle risorse Google Cloud. Scegli l'approccio più adatto al tuo ambiente:

Utilizza questa opzione se utilizzi un progetto Google Cloud esistente o condiviso in cui si trovano altre risorse o dati. Distrugge chirurgicamente solo le risorse di cui è stato eseguito il provisioning durante questo workshop (bucket Cloud Run, Cloud SQL, Secret Manager, GCS), lasciando intatto il resto del progetto.

cd iac
terraform destroy -auto-approve

Opzione 2: eliminazione totale del progetto (non lasciare tracce 🌪️)

Utilizza questa opzione se hai creato un progetto workshop dedicato (ad es. utilizzando crediti workshop o una sandbox) e vuoi garantire che non rimanga alcuna traccia, inclusi log, metadati, service account e link di fatturazione.

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet