Rails 8 no Google Cloud: do zero à IA

1. Introdução

Este é o workshop Rails 8 no Google Cloud. Neste codelab prático, você vai migrar um aplicativo Rails 8 moderno de um simples plano de referência local do SQLite para uma arquitetura de referência de nível de produção e pronta para empresas no Google Cloud.

Neste workshop, não vamos apenas implantar um app, mas executar uma modernização da nuvem opinativa e de nível de produção (migração lift-and-shift feita da maneira certa!).

Comece com a configuração local típica de "funciona na minha máquina" (disco temporário, SQLite incorporado, jobs no processo) e evolua progressivamente para uma arquitetura de nuvem empresarial e à prova de falhas no Google Cloud:

  1. 🐘 Do SQLite local → Cloud SQL (PostgreSQL): não há mais limpeza do banco de dados em reinicializações. Migramos para o PostgreSQL gerenciado conectado com segurança pelo proxy de autenticação do Cloud SQL (mTLS, sem IPs públicos).
  2. 🪣 Do armazenamento local temporário → GCS particular:o disco sem servidor é temporário. Conectamos o ActiveStorage ao Google Cloud Storage particular usando URLs assinados do IAM (iam: true). Assim, não há buckets públicos nem chaves vazadas.
  3. 🚀 De jobs no processo → Cloud Run de vários contêineres:fazemos a ponte entre o monólito do Rails e a nuvem sem servidor. Usando o Docker Compose padrão, executamos o Solid Queue como um contêiner de worker dedicado ao lado do Puma em um pod de vários contêineres do Cloud Run. Sem proliferação de microsserviços, sem falta de linhas de execução. O DHH ficaria orgulhoso!

🍌 E como estamos em 2026, turbinamos toda a pilha com o Google Antigravity. Não é algo que pensamos depois. Temos comandos e habilidades para orientar você a aproveitar ao máximo este workshop.

🏛️ Arquitetura de referência de destino (variante 1: empresa de vetor plano limpo)

Esta é a arquitetura de referência canônica do Google Cloud que você vai criar e implantar (Variante 1: Clean Flat Vector Enterprise), mostrando nosso serviço de produção do Cloud Run com vários contêineres conectado a todos os serviços gerenciados de persistência e IA do Google Cloud:

Variante 1: vetor empresarial limpo e plano: arquitetura do Rails 8 no Google Cloud

🎞️ A evolução da arquitetura: do zero para a arquitetura nativa da nuvem

Assista nossa pilha se modernizar progressivamente de um plano de ação efêmero de máquina única para um blueprint totalmente gerenciado do Google Cloud em cada marco do workshop:

Workshop Architectural Evolution

O que você vai aprender

Vamos começar!

2. Etapa 0: pré-requisitos, configuração do Antigravity e verificação de faturamento

💡 O cenário:você e sua equipe estão criando um aplicativo Rails 8 essencial para a missão. Antes de mexer no código ou iniciar recursos da nuvem, precisamos estabelecer nosso conjunto de ferramentas, conectar o Google Antigravity e verificar nossas credenciais do Google Cloud e a base de faturamento.

1. Lista de verificação de pré-requisitos

Antes de começar, verifique se você tem as seguintes ferramentas disponíveis no seu ambiente:

  • just (1.21 ou mais): (just --version) o executor de tarefas. Cada comando neste workshop é uma just receita. Peça para sua plataforma de IA instalar ou consulte skills/rails8app-workshop.
  • Git (2.30 ou mais recente): (git --version) para controle de versão, ramificação e clonagem do repositório.
  • SDK do Google Cloud (CLI gcloud): instalado e atualizado.
  • CLI do Terraform (1.5 ou mais recente): para provisionamento declarativo de infraestrutura.
  • docker e docker-compose: instalados e em execução localmente.
  • Ruby 3.4.5: (ruby -v — fixado por blog/.ruby-version; peça ao seu arnês de IA para instalar 3.4.5 se estiver faltando). O Rails 8 é declarado no Gemfile e instalado automaticamente pelo bundle install.
  • Google Antigravity 2.0:seu assistente autônomo de programação em dupla com IA. Faça o download do Google Antigravity 2.0. Faça login com seu Gmail pessoal. Não é necessário um token de API.

2. Autenticação, configuração dedicada e ADC do Google Cloud

Para evitar conflitos com configurações corporativas, pessoais ou de várias contas do gcloud, recomendamos criar uma configuração nomeada dedicada para este 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

💡 Por que uma configuração dedicada? O uso do gcloud config configurations create rails8-on-gcp-workshop isola todas as configurações da CLI (conta, projeto de cota, região padrão) especificamente para este workshop. Quando terminar, você poderá voltar à configuração normal a qualquer momento com gcloud config configurations activate default.

Configuração ativa da gcloud

3. 🚨 Verificação do faturamento do GCP

⚠️ Observação: as implantações do Google Cloud SQL e do Cloud Run exigem uma conta de faturamento vinculada ativa ou créditos educacionais válidos do workshop. Vamos verificar se a configuração está correta. Para isso, execute a verificação de verificação de faturamento:

gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT

Verifique se billingEnabled: true foi retornado. Você também pode listar todas as suas contas de faturamento:

gcloud billing accounts list

Verifique se pelo menos um deles mostra OPEN: True. Se nenhuma estiver ativa, vincule uma conta de faturamento ou resgate seu cupom de crédito do workshop na página de faturamento do Console do Google Cloud.

Conta de faturamento ativa do GCP

4. Clonar o repositório e parear com o Antigravity

Clone o repositório e entre no diretório. Observe que ficamos totalmente em main:

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

Abra esse diretório no Google Antigravity. O Antigravity inspeciona automaticamente o repositório, lê AGENTS.md e fica disponível como seu programador em dupla.

5. 🪫 Se o Antigravity ficar sem créditos sem custo financeiro

⚠️ Os créditos do Cloud que você acabou de resgatar NÃO recarregam o Antigravity. São duas plataformas de faturamento separadas: a cota do Antigravity fica nos planos com IA do Google (Google One), enquanto os vales e créditos do Cloud ficam em uma conta do Cloud Billing. Vincular uma conta de faturamento ao seu projeto do GCP não vai dar a você um único comando extra do Antigravity.

Se o agente parar com uma mensagem de cota, siga esta ordem. As duas primeiras opções são sem custo financeiro e instantâneas:

  1. Mudar o pool de modelos. A cota é rastreada por família de modelos, e o Antigravity mostra dois pools independentes (modelos do Gemini x modelos do Claude/GPT). Os modelos Claude e GPT-OSS estão disponíveis no nível sem custo financeiro. Por isso, um pool do Gemini esgotado geralmente deixa o outro intacto. Use o seletor de modelos abaixo da caixa de comando.Seletor de modelo antigravidade: mude entre pools do Gemini, Claude e GPT-OSS
  2. Escolha um modelo Flash em vez do Pro. Os limites de taxa estão relacionados à quantidade de trabalho que o agente faz por solicitação. Portanto, um modelo mais leve aumenta o que resta.
  3. Use a CLI do Antigravity com sua própria chave da API Gemini. O recurso BYOK não é compatível com o ambiente de desenvolvimento integrado do Antigravity. A CLI é a alternativa documentada. Crie uma chave sem custo financeiro no Google AI Studio (é necessário um projeto, mas não uma conta de faturamento). Depois, faça o download e configure a CLI do Antigravity.
  4. Aguarde ou faça upgrade. A cota do nível sem custo financeiro é atualizada semanalmente. A mensagem de erro informa a data de redefinição. O Google AI Pro/Ultra é a única maneira documentada de aumentar o valor de referência no app para computador. Esses planos são atualizados a cada cinco horas e podem usar os créditos de IA comprados no excedente.
  5. Embora este seja um workshop do Antigravity, a maioria das ferramentas de programação de IA também funciona. Basta apontar a sua para o diretório AGENTS.md e skills/ no repositório.

6. Validação automática da etapa 0

Verifique se o ambiente da etapa 0 está 100% em conformidade com o pacote de avaliação:

just workshop-eval 0

✨ O momento incrível:o Antigravity e o mecanismo de avaliação do workshop verificam suas versões da CLI, o ambiente Ruby e a autenticação do ADC em menos de 2 segundos!

3. Etapa 1: início da infraestrutura do Terraform e diagnósticos de pré-teste

💡 A estratégia:bancos de dados gerenciados, como o Google Cloud SQL PostgreSQL, levam aproximadamente de 10 a 12 minutos para serem provisionados. Em vez de esperar depois, vamos iniciar a infraestrutura imutável com o Terraform agora mesmo em segundo plano enquanto desenvolvemos localmente.

1. Executar o pacote de diagnóstico automático de pré-simulação

Antes de lançar a infraestrutura em nuvem, execute o pacote de testes abrangente de pré-simulação:

just workshop-test

Este script (bin/workshop_diagnostics.rb):

  • Verifica sua configuração de identidade do GOOGLE_CLOUD_ACCOUNT.
  • Verifica o faturamento ativo e a vinculação do projeto.
  • Valida as Application Default Credentials (ADC) para a Vertex AI.
  • Confirma a imagem de canary seed do ActiveStorage (blog/app/assets/images/gcs_dev_image.jpg).

apenas saída de diagnóstico de teste de workshop

Se .env estiver faltando, copie do modelo documentado:

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

💡 Dica:se just workshop-test informar que a API Billing está desativada no seu projeto, ative-a rapidamente usando:

gcloud services enable cloudbilling.googleapis.com

2. ⏱️ Iniciar a infraestrutura do Terraform de forma assíncrona

Execute a receita do Terraform de uma etapa, que cria o bucket de estado do back-end remoto e aciona a implantação da infraestrutura:

just terraform-apply

O Terraform foi inicializado com sucesso

Alternativa manual (ou execute `iac/bin/terraform-apply.sh` diretamente)

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

O que o Terraform provisiona:

  • Bucket do Google Cloud Storage:criado com acesso privado e assinatura de credenciais do IAM (iam: true).
  • Imagem de teste canário:faz upload de gcs_dev_image.jpg para o bucket e ativa a verificação completa de blobs.
  • Instância do Google Cloud SQL PostgreSQL:inicia o provisionamento em segundo plano (~10 a 12 minutos).

Instância do Cloud SQL PostgreSQL no console do Google Cloud

💡 Observação:você pode ignorar com segurança o aviso sobre backups automáticos ausentes. Não vamos precisar deles para este banco de dados do workshop, embora os backups diários automáticos sejam uma prática recomendada essencial para qualquer banco de dados de produção real.

O comando "terraform apply" foi concluído com êxito com saídas

📝 Anote o URL do Cloud Run:a aplicação do Terraform vai ser concluída e imprimir as saídas da infraestrutura. Anote ou copie para a área de transferência seu cloud_run_url. Você vai precisar dele nas etapas posteriores para verificar seu serviço ativo.

3. Validação automática da etapa 1 (opcional)

Verifique se os pré-requisitos e as configurações da etapa 1 foram aprovados:

just workshop-eval 1

⏱️ Observação:essa avaliação verifica recursos de nuvem ativos e pode levar alguns minutos para ser concluída. É totalmente opcional. Enquanto ele é executado no terminal, você pode prosseguir imediatamente para a próxima etapa sem esperar.

4. Etapa 2: a linha de base local, o Mailpit e a integração do administrador

Nosso ponto de partida é um aplicativo de blog Rails 8 limpo e moderno em execução no localhost com SQLite, interceptação de e-mail do Mailpit e ActiveStorage baseado em disco.

1. Inicializar o app localmente

📁 Cuidado com o diretório! Esse repositório contém o app Rails em blog/ e o Terraform em iac/. A etapa 1 deixou você na raiz do repositório. Portanto, todos os comandos da etapa 2 são executados de

blog/: Gemfile, bin/rails e compose.yaml moram lá. A execução deles na raiz falha com no configuration file provided: not found ou um Gemfile ausente.

Acesse o app Rails, confirme se você está no Ruby fixado, instale as gems e configure o banco de dados:

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

🧯 Erro de permissão ao gravar em

/var/lib/gems/

ou

ruby -v

mostra o Ruby do sistema (por exemplo, 3.3.x)? O Ruby do sistema Linux padrão ignora completamente os arquivos .ruby-version e tenta instalar gems em todo o sistema. Verifique se o rbenv está carregado no shell do terminal ativo:

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

Observação:se você pulou a instalação da versão 3.4.5 na etapa 0, execute rbenv install 3.4.5. Ou simplesmente use o Modo A (Docker Compose) abaixo (just compose-up), que executa a pilha Ruby fixada completamente dentro de contêineres sem tocar em gems do host.

💡 Prefere não pensar nisso? just install e just compose-up da raiz do repositório fazem o cd blog para você.

2. 🌱 Descoberta automática de sementes inteligentes e bootstrap de administrador (problemas nº 21 e 25)

O seed de banco de dados (db/seeds.rb) tem o descobrimento automático de ambiente inteligente:

  • Ele inspeciona seu adaptador de banco de dados ativo (SQLite x Postgres) e a configuração de armazenamento.
  • Ele detecta o Estágio 0 (localhost) e cria automaticamente o usuário administrador inicial e a postagem pedagógica:
    • [LOCAL BASELINE] Welcome to Rails 8 on Localhost!
    • Anexo de imagem triste local pronta para uso (local_sad_image.png) com marca-d'água informando que o armazenamento em disco local é temporário.
  • Ele aciona automaticamente um e-mail de redefinição de senha pelo ActionMailer.

Verifique ou execute novamente a semente (usa automaticamente seu GOOGLE_CLOUD_ACCOUNT de .env):

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

Inicialize o stack de desenvolvimento local usando o Docker Compose:

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

Docker Compose iniciando Rails, Mailpit e pilha local

O aplicativo, o servidor SMTP local do Mailpit e o visualizador de banco de dados do Adminer agora estão sendo executados juntos em contêineres isolados:

  • Rails App: http://localhost:3000
  • Interface da Web do Mailpit: http://localhost:8025
  • Interface do Adminer DB: http://localhost:8081

🧯 Conflito na porta 3000 ou

server.pid

erro? Se você já tiver iniciado um servidor de desenvolvimento nativo (bin/dev), pare-o com Ctrl+C. Se o Docker reclamar que a porta 3000 já está alocada ou encontrar um server.pid restante, limpe com:

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

3. A experiência do Mailpit e o login de administrador

  1. Capturar e-mails enviados com o Mailpit: abra http://localhost:8025 no navegador. Confira a caixa de entrada local. Durante db:seed, o ActionMailer enviou um e-mail de integração de administrador que o Mailpit capturou com segurança localmente sem tocar em servidores ou credenciais de e-mail externos:

Interface da Web do Mailpit mostrando o e-mail de redefinição de senha interceptado 2. Faça login no blog: abra http://localhost:3000 no navegador.

  • Clique no link de redefinição de senha diretamente no e-mail do Mailpit para definir sua senha.
  • Como alternativa, faça login usando o e-mail da sua conta do Google Cloud (de .env) e a senha padrão: Ch4ng3m3!!1.
  1. Observe os selos de telemetria visual:
    • Observe o banner e os selos amarelos do ambiente na interface: Notice: Ephemeral Database Active (POSTGRESQL) e [EPHEMERAL DB / STORAGE] 💾 Local.
    • Observe a marca d'água da postagem: o selo de casa (nanobanana_stamp_local.png no canto inferior direito da imagem da capa). Isso fornece uma confirmação visual imediata de que seus recursos e banco de dados estão vinculados ao armazenamento local efêmero.

Página inicial do blog local mostrando avisos e selos de telemetria temporária

💡 Dica profissional opcional: treino do console do Rails. Quer saber como o Rails interage com os dados de seed da CLI? Acesse o console interativo:

bin/rails console

Inspecione o usuário administrador propagado de forma dinâmica usando sua variável de 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. Validação automática da etapa 2

Verifique seu valor de referência local e a configuração de administrador:

just workshop-eval 2

apenas a saída de verificação workshop-eval 2

5. A questão: contêineres sem estado

Os contêineres do Cloud Run não têm estado e são temporários. Se implantarmos nosso banco de dados SQLite e o diretório storage/ local diretamente no Cloud Run, todas as postagens e imagens enviadas serão apagadas permanentemente sempre que um contêiner for escalonado para zero ou reiniciado.

Na próxima etapa, vamos implantar intencionalmente essa configuração efêmera no Cloud Run para testemunhar o choque sem estado em primeira mão.

5. Etapa 3: implantação 1: o choque sem estado (WOW inicial em 3 minutos!)

Em vez de esperar de 15 a 20 minutos pelos bancos de dados na nuvem antes de ver algo na Web, o desenvolvimento moderno sem servidor começa com uma vitória inicial: a implantação do nosso aplicativo Rails de contêiner único diretamente no Google Cloud Run em menos de 3 minutos.

1. A retrospectiva da Máquina do tempo

Para garantir que nossa configuração inicial seja 100% temporária (banco de dados SQLite local e armazenamento de arquivos em disco local), use o mecanismo Zero-Branch Time-Machine:

just workshop-rewind 1

💡 O que aconteceu?workshop-rewind 1 aplicou a sobreposição de configuração stage-1-stateless a blog/config/ sem sair da ramificação main. Seu app está configurado com o SQLite no disco do contêiner e o ActiveStorage no sistema de arquivos local.

2. Como implantar o Puma de contêiner único no Cloud Run

Implante diretamente do código-fonte para o Cloud Run. O Rails 8 moderno gera automaticamente um Dockerfile oficial e pronto para produção (com builds de vários estágios, jemalloc e segurança não raiz). Ao fazer a implantação da pasta blog/, o Google Cloud Build detecta esse Dockerfile nativo diretamente e provisiona um serviço gerenciado sem servidor:

# 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

Você vai ver uma saída de progresso semelhante a esta:

gcloud run deploy building and deploying container

⏳ Isso leva alguns minutos enquanto o Cloud Build empacota o contêiner e provisiona a revisão do Cloud Run.

🧯 Solução de problemas e ressalvas comuns

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

? Duas causas diferentes geram o mesmo erro:

  1. A gcloud acabou de ativar uma API para você. Se a implantação tiver impresso The following APIs are not enabled ... cloudbuild.googleapis.com e você tiver respondido Y, talvez o build tenha começado antes da propagação das novas permissões. O próprio comando gcloud avisa que "isso vai levar alguns minutos". Aguarde um minuto e execute o mesmo comando novamente. Geralmente, isso funciona. A etapa 1 terraform apply normalmente ativa essa API para você, então o aviso não deve aparecer.
  2. A conta de serviço padrão do Compute realmente não tem papéis. Os projetos modernos do GCP aplicam o princípio de privilégio mínimo, o que interrompe as implantações de origem. Corrija com:
    O
    just project-status   # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builder
    
    just workshop-test também informa isso agora. Portanto, vale a pena executar novamente se a implantação continuar falhando.

Durante a implantação:

  1. O Cloud Run cria a imagem do contêiner do Rails.
  2. Ele atribui um domínio TLS público e seguro: https://blog-[hash]-[region].a.run.app.
  3. O tráfego começa a ser encaminhado para o Puma na porta 8080.

3. ✨ O momento WOW inicial

Abra o URL gerado do Cloud Run no seu navegador.

  1. Seu aplicativo moderno do Rails 8 está ativo no Google Cloud.
  2. Faça login com suas credenciais de administrador (GOOGLE_CLOUD_ACCOUNT e APP_ADMIN_PASSWORD). Se não encontrar um botão de login, clique em "Nova postagem". Isso vai pedir o usuário e a senha primeiro:Página de login do Rails 8 com banners de telemetria efêmera
  3. Clique em Nova postagem, escreva um artigo intitulado Minha primeira postagem do Cloud Run, anexe uma imagem e clique em Criar postagem.
  4. Sua postagem é publicada com formatação completa, e a imagem é renderizada.
  5. Observe a telemetria visual:
    • Selo do cabeçalho: [EPHEMERAL DB / STORAGE] 💾 Local
    • Marca-d'água de imagem: o selo 🏠 (127.0.0.1 selo de disco temporário no canto inferior direito).

🐝 Participe do ranking do Live Workshop Hive! Se você estiver on-line e o fiscal estiver mostrando o ranking, e quiser participar dele, adicione o URL do Cloud Run aqui: 👉 Registre seu Cloud Run no ranking

Detalhes da revisão do serviço do blog do console do Google Cloud Run

4. 💥 The Catch: The Stateless Shock & The "Puma Workaround" Trap

O Cloud Run é uma plataforma sem estado e sem servidor. Quando o tráfego da Web cai para zero, o Cloud Run reduz a escala para zero instâncias de contêiner e economiza dinheiro. Quando uma nova solicitação HTTP chega ou uma nova revisão de contêiner é implantada, o Cloud Run inicia uma imagem de contêiner limpa e totalmente nova.

Banner de jobs travados

Quando você cria uma postagem ou anexa uma imagem em uma implantação de contêiner único, o Rails enfileira tarefas do ActiveJob (como análise de dimensão de imagem ou indexação de metadados). Mas como ninguém está executando um worker em segundo plano, o banner de aviso vai aparecer:

Aviso: 8 jobs em segundo plano estão pendentes de execução

A correção tentadora: executar a fila sólida no Puma

Um desenvolvedor inteligente pode dizer: "Espere! O Rails 8 permite executar o Solid Queue diretamente no processo do servidor da Web Puma ao ativar o

SOLID_QUEUE_IN_PUMA=true

variável de ambiente!"

Vamos testar essa solução alternativa no 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

Agora, volte para o navegador e atualize a página:

💥 The Stateless Shock:

  1. A boa notícia:o worker do Solid Queue agora está ativo no Puma. Todos os novos jobs são esgotados imediatamente.
  2. O banho de água fria (o problema!):
    • Como o Cloud Run implantou uma nova revisão, a instância de contêiner anterior foi substituída.
    • O artigo que você escreveu e o arquivo do banco de dados SQLite no disco foram completamente apagados!
  3. A lição de arquitetura:executar workers em segundo plano no Puma consome CPU/RAM valiosas da linha de execução da Web e ainda não resolve a persistência.

5. Validação automatizada da etapa 3

Verifique a implantação do Cloud Run e o processamento de alertas:

just workshop-eval 3

✨ A lição:soluções alternativas de contêiner único, como SOLID_QUEUE_IN_PUMA, são úteis para o desenvolvimento local, mas na arquitetura moderna da nuvem:

  • Os arquivos de mídia exigem o Google Cloud Storage (GCS) (etapa 4).
  • Dados relacionais e filas de jobs exigem o Google Cloud SQL gerenciado (etapa 5).
  • Workers de segundo plano pesados pertencem a um contêiner secundário dedicado (etapa 6).

6. Etapa 4: implantação 3: armazenamento permanente do GCS e aviso de POLA

Nesta etapa, separamos o armazenamento de mídia e arquivos do disco do contêiner ao mudar o ActiveStorage para o Google Cloud Storage (GCS), usando URLs assinados de curta duração pela API IAM Credentials (iam: true).

Antes de mexer em qualquer configuração, entenda as três coisas que o Rails precisa para se comunicar com o GCS:

① ID do projeto + nome do bucket: o Rails precisa saber onde armazenar blobs. O app lê GOOGLE_CLOUD_PROJECT do ambiente e deriva o nome do bucket como ${GOOGLE_CLOUD_PROJECT}-activestorage-prod. O Terraform da etapa 1 já criou esse bucket. Não é necessário fazer mais nada.

② Uma conta de serviço com as permissões certas: o Cloud Run é executado como a conta de serviço padrão do Compute Engine (PROJECT_NUMBER-compute@developer.gserviceaccount.com). Essa conta de serviço precisa de dois papéis do IAM no bucket:

  • roles/storage.objectAdmin: para fazer upload e veicular blobs
  • roles/iam.serviceAccountTokenCreator por si só: para chamar a API IAM Credentials signBlob e gerar URLs assinados de curta duração.

⚠️ O atalho tentador —

public: true A maneira mais rápida de fazer o GCS funcionar é definir public: true em storage.yml e conceder allUsers:objectViewer no bucket. As imagens carregam instantaneamente, sem necessidade de assinatura. Não faça isso. Um bucket público vaza todas as mídias do usuário enviadas para a Internet aberta, para sempre. Nosso blueprint usa o iam: true para manter o bucket 100% particular. Cada URL de imagem é um token assinado e expirado gerado na hora pela API IAM Credentials.

③

ACTIVE_STORAGE_SERVICE=google: a única variável de ambiente que muda o Rails do disco local para o GCS durante a execução. Sem mudança de código, sem nova implantação da lógica do aplicativo: apenas uma alternância de variável de ambiente.

1. A retrospectiva da Máquina do tempo

Avance a Máquina do tempo para a Etapa 2:

just workshop-rewind 2

Inspecione blog/config/storage.yml. Você vai ver que o app define três ambientes nomeados do GCS (dev / test / prod), todos usando o mesmo padrão seguro:

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

Arquitetura de assinatura do IAM do GCS

2. Como conceder permissões de armazenamento e assinatura do IAM

Verifique se a conta de serviço do ambiente de execução do Cloud Run tem permissões para assinar URLs e fazer upload de objetos:

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. Terceira implantação no Cloud Run com o GCS anexado

Faça uma nova implantação com o GCS ativado. Verifique se você está na pasta blog/. Caso contrário, o Cloud Run vai tentar criar usando Buildpacks em vez do Dockerfile do Rails 8, o que vai causar falhas na criação.

Use --update-env-vars (não --set-env-vars) para preservar as variáveis de ambiente atuais (chave principal, configuração do IAP etc.):

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

💡 Dica:se a implantação falhar, clique no link dos registros do Cloud Build fornecido na saída do terminal, conforme mostrado na figura abaixo. Você também pode pedir ajuda ao Antigravity para decifrar e corrigir o erro.

Link do registro de falha do Cloud Build na saída do terminal

4. ✨ A imagem sobrevivente e o carimbo da nuvem

  1. Atualize o URL do blog.
  2. Crie uma postagem de blog chamada "Sobrevivendo à nuvem" e faça upload de uma foto.
  3. Observe o canto inferior direito da imagem:
    • O selo 🏠 sumiu!
    • Ele foi substituído pelo carimbo colorido do GCS na nuvem (nanobanana_stamp_cloud.png).
  4. Force outra reinicialização do contêiner, que vai acionar uma quarta revisão:
    gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s)
    
  5. Atualize a página: enquanto o banco de dados SQLite é redefinido, o binário da imagem fica seguro e protegido no Google Cloud Storage. Verificar via CLI:
    gcloud storage ls -l -r gs://$GCS_BUCKET/
    

Confira uma lista de blobs e objetos de mídia enviados que podem aparecer no console do navegador do Google Cloud Storage no bucket de produção (gs://$GCS_BUCKET):

Detalhes do bucket do console do Google Cloud Storage mostrando blobs de imagem persistentes

5. ⚠️ O problema: banner de aviso de jobs travados

Quando você enviou a imagem, o ActiveStorage enfileirou um job de análise (ActiveStorage::AnalyzeJob) para extrair dimensões e metadados.

No entanto, o Cloud Run está executando apenas um único contêiner da Web (puma). No Rails 8, a Solid Queue armazena jobs em segundo plano no banco de dados, mas ninguém está executando bundle exec rails solid_queue:start.

Na parte de cima da página do blog, você vai encontrar um banner de aviso brilhante renderizado por blog/app/views/layouts/_check_stuck_jobs.html.erb.

Vamos resolver isso corretamente na Etapa 6, quando passarmos para uma arquitetura sidecar de vários contêineres de nível de produção com um contêiner worker dedicado executando o Solid Queue de forma independente do tráfego da Web.

6. Validação automática da etapa 4

Verifique a configuração do GCS e a telemetria de jobs bloqueados:

just workshop-eval 4

7. Etapa 5: lista de verificação de pré-voo: conectividade do Cloud SQL, secrets e banco de dados

Enquanto você aprendia sobre contêineres sem estado e o Google Cloud Storage nas etapas 3 e 4, o Terraform terminou de preparar seu back-end de produção em segundo plano.

Antes de implantar nossa arquitetura sidecar de produção completa com três contêineres (Implantação 4 na etapa 6), esta etapa funciona como uma inspeção prévia rápida para verificar a integridade, a conectividade e o acesso secreto do banco de dados.

0. Variáveis de ambiente de inicialização

Exporte todas as variáveis de shell necessárias diretamente das saídas do 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. Verificação de integridade do Cloud SQL

Verifique se a instância gerenciada do PostgreSQL concluiu o provisionamento inicial e está íntegra:

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

A saída precisa ser RUNNABLE.

2. (Opcional) Testar a conectividade do banco de dados no seu notebook

Em produção, os contêineres do Cloud Run se conectam com segurança ao Cloud SQL por um proxy sidecar criptografado (127.0.0.1:5432) sem expor o banco de dados à Internet pública (0.0.0.0/0).

ℹ️ Observação:esta etapa é totalmente opcional. No Cloud Run, o contêiner secundário oficial do proxy de autenticação do Cloud SQL processa a conectividade mTLS automaticamente. Você não precisa instalar nada localmente para concluir o workshop.

Se você tiver o cloud-sql-proxy e o psql instalados no seu computador e quiser verificar a conectividade direta do seu laptop:

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

Se uma senha for solicitada, insira

$DB_PASSWORD

. Caso contrário, pule direto para a etapa 3 abaixo.

3. Como inspecionar secrets provisionados pelo Terraform

Nunca armazene senhas de banco de dados, chaves de API ou chaves principais do Rails em texto simples no git ou em variáveis de ambiente de contêiner. Na arquitetura de nuvem moderna, usamos o Google Cloud Secret Manager para injeção de tempo de execução de confiança zero.

ℹ️ O Terraform fez isso por você na etapa 1:

  1. rails-master-key para descriptografar credenciais do Rails no ambiente de execução.
  2. rails-db-password: uma senha aleatória criptograficamente segura que corresponde ao usuário do Cloud SQL.
  3. rails-admin-password: uma senha segura para seu usuário administrador inicial.

Verifique se os secrets estão presentes e ativos no Secret Manager:

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

Listagem do console do Secret Manager do Google Cloud com secrets de aplicativos configurados

🔑 Sincronizar o master.key com seu laptop (sincronização local)

Como o blog/config/master.key é ignorado pelo Git por motivos de segurança, seu clone local ainda não tem esse arquivo. Execute este snippet de auto-recuperação amigável para extrair do Secret Manager e permitir que suas ferramentas locais do Rails descriptografem as credenciais:

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: quer criar um secret personalizado só para ver como é fácil? O Secret Manager não é apenas para o Rails. É possível criar qualquer secret com um simples comando da CLI:

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

4. Como verificar as permissões do SecretAccessor

Para que os contêineres do Cloud Run montem esses secrets como variáveis de ambiente ou montagens de volume na inicialização, a conta de serviço do ambiente de execução ($RUN_SA) precisa da função roles/secretmanager.secretAccessor em cada secret.

O Terraform já concedeu isso na etapa 1. Verifique a vinculação da política do IAM em 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"

Você verá:

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

Comparação de segurança do proxy de autenticação do Cloud SQL

5. Validação automática de pré-voo

Execute o conjunto de testes automatizado da etapa 5 para confirmar se o banco de dados pode ser executado, se os secrets não estão vazios e se as permissões do IAM estão verdes:

just workshop-eval 5

8. Etapa 6: implantação 4: sidecars de vários contêineres empresariais (o padrão ouro)

Agora vamos montar a arquitetura de referência definitiva: Cloud Run com vários contêineres.

1. Como restaurar o padrão ouro

Restaure a configuração do repositório para o estado canônico main:

just workshop-restore-gold

2. Como inspecionar o blueprint de produção de três contêineres

Abra blog/compose.prod.yaml e inspecione a arquitetura:

Arquitetura de produção de vários contêineres do 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)]

A implantação de produção executa três contêineres coordenados que compartilham a mesma rede local:

  1. web: atende ao tráfego HTTP na porta 8080 via Puma.
  2. worker: contêiner dedicado de processamento em segundo plano que executa bundle exec rails solid_queue:start.
  3. cloudsql-proxy: sidecar oficial do proxy de autenticação do Google Cloud SQL (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2) vinculado a 127.0.0.1:5432.

3. Executar migrações de banco de dados com um job do Cloud Run

Antes de rotear o tráfego da Web para nossa nova implantação, precisamos executar migrações de banco de dados e seed inicial no Cloud SQL.

⚖️ Como executar migrações em contêineres: duas estratégias

Em ambientes de nuvem em contêineres, há duas abordagens comuns para executar migrações de banco de dados:

  • Opção A: automática dentro do ponto de entrada do Docker (entrypoint.sh executa db:migrate na inicialização do contêiner)
    • Prós:totalmente automatizado. Os desenvolvedores não precisam se lembrar de executar um comando.
    • Desvantagem:perigoso no escalonamento automático sem servidor. Quando o Cloud Run aumenta a escala para 10 instâncias simultaneamente em um tráfego alto, 10 contêineres competem para executar migrações simultaneamente, causando bloqueio de banco de dados, condições de corrida de esquema ou tempos limite de inicialização.
  • Opção B: explícita via job do Cloud Run (nossa escolha arquitetônica)
    • Prós:controle completo, segurança transacional e zero condições de corrida. Executa como uma tarefa de contêiner única e dedicada, completamente separada do tráfego da Web. Se uma migração falhar, a exibição na Web não será afetada.
    • Desvantagens:exige o acionamento de um job da CLI ou uma etapa de CI/CD antes da implantação.

Escolhemos a opção B para ter máxima confiabilidade na produção.

⚠️ Crítico: DATABASE_URL Formato e RFC 3986 Strictness do Ruby 3.4.5

Os jobs do Cloud Run se conectam ao Cloud SQL usando o proxy de soquete Unix nativo (/cloudsql/PROJECT:REGION:INSTANCE). O DATABASE_URL precisa usar o formato de barra tripla:

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:o app usa quatro bancos de dados (primário, fila, cache e cabo) por database.yml. Todas as quatro variáveis de ambiente DATABASE_*_URL precisam apontar para a mesma instância do Cloud SQL. Caso contrário, as tabelas do Solid Queue/Cache/Cable não serão criadas.

Depois de db:prepare, carregue os esquemas de fila/cache/cabo do Solid:

# 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

Saída de gcloud run jobs executions describe JOBNAMENa figura acima, você pode conferir uma possível saída de

gcloud run jobs executions describe $JOBNAME

Console de jobs do Google Cloud Run mostrando o job rails-migrate como &quot;Succeeded&quot;Console de jobs do Google Cloud Run mostrando

rails-migrate

execution Succeeded

4. Quarta implantação: implantação do Cloud Run de vários contêineres usando o Docker Compose

Agora implante a pilha de produção genuína de três contêineres (web, worker, cloudsql-proxy) diretamente do 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

Guia &quot;Contêineres&quot; do console do Google Cloud Run mostrando os três contêineres sidecarConsole do Google Cloud Run: guia "Contêineres" mostrando os três contêineres secundários (

cloudsql-proxy

,

web

,

worker

)

💡 Curiosidade da interface / falha do console:no menu suspenso do console, você pode notar que o Cloud Run rotula a entrada superior como Ingress container: cloudsql-proxy. Não se preocupe! Esse é apenas um problema visual na interface do usuário do console do Google Cloud (rastreado em #149) causado pela ordenação de dependências de contêineres. Na realidade, web é o único contêiner que detecta na porta HTTP 8080 e processa 100% do tráfego da Web de entrada, enquanto cloudsql-proxy atende estritamente ao tráfego privado do PostgreSQL na porta 5432.

5. ✨ O momento incrível: zero avisos e persistência total na nuvem!

Respire fundo e admire a tela: todos os banners de alerta desapareceram! 🪄

Blog de produção no Cloud Run com zero avisos e postagem persistente na nuvemO blog de produção do Rails 8: interface impecável, zero banners de aviso e estado persistente do Cloud SQL!

Agora o aplicativo é oficialmente de nível de produção e nativo da nuvem:

  • Com tecnologia do Google Cloud SQL (PostgreSQL) gerenciado por tunelamento mTLS seguro.
  • Mídia armazenada com segurança no Google Cloud Storage particular com assinatura de blob do IAM.
  • Zero alertas de jobs presos e avisos de armazenamento temporário.

Abra o URL do Cloud Run:

  1. Confira os selos de telemetria:
    • [CLOUD PERSISTENT 🐘 ☁️] foi verificado na sua seed de banco de dados.
    • Os selos do Google Cloud Run e do GCS no rodapé ficam verdes.
    • O banner de aviso de jobs presos desapareceu completamente.
  2. Criar postagens e comentários em blogs.
  3. Reinicie ou reimplante quantas vezes quiser: seus dados, postagens, comentários e recursos vão durar para sempre no Cloud SQL e no GCS.

6. Validação automática da etapa 6

Verifique a configuração de vários contêineres:

just workshop-eval 6

9. Etapa 7: pipelines de IA generativa, Podcastifier e a caça ao tesouro do GCS 🏴‍☠️

Com a Solid Queue em execução em um contêiner dedicado e o Google Cloud Storage ativo, podemos liberar a IA generativa assíncrona.

1. O gerador de capas vintage do NanoBanana (pipeline de IA generativa pronto para uso)

Mascote NanoBanana

Boa notícia: essa funcionalidade já está totalmente implementada na base de código. Não é necessário escrever código. Sua tarefa aqui é acionar e observar um pipeline assíncrono de IA generativa na nuvem em ação.

🧠 Como funciona nos bastidores

  • Autenticação:não é necessário usar chaves de API. Ele usa Application Default Credentials (roles/aiplatform.user na Vertex AI) automaticamente pela conta de serviço de ambiente de execução do Cloud Run. Como alternativa, se GEMINI_API_KEY for fornecido, ele vai chamar a API Gemini diretamente.
  • O pipeline:quando um artigo é criado sem uma imagem de capa, um callback after_commit enfileira GenerateCoverImageJob na Solid Queue.
  • Modelo:ele solicita que o modelo multimodal do Google (Gemini 2.5 Flash Image / Imagen na Vertex AI) gere um pôster de filme italiano autêntico da década de 1960 ("Locandina di un film 1960") com uma banana e um rubi brilhante "8".
  • Marca d'água de origem dinâmica:usa libvips para marcar dinamicamente um selo visual no canto (selo de nuvem colorida no GCS).
  • Troca dinâmica do Turbo Stream:quando a imagem é anexada, o Turbo::StreamsChannel.broadcast_refresh_to(post) atualiza o navegador do leitor em tempo real sem recarregar a página manualmente.

Diagrama de sequência da arquitetura assíncrona da IA NanoBanana

📐 Ver fonte do diagrama de sequência do 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!

🧪 Teste agora!

É possível acionar de duas maneiras:

  • Opção A (interface da Web): clique em Nova postagem, insira um título como "Arquitetura sem servidor com Ruby on Rails 8", escreva algum conteúdo e deixe a entrada de arquivo de imagem da capa vazia. Clique em Criar postagem.
  • Opção B (executador da CLI): execute no terminal:
    bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
    

Aguarde de 15 a 30 segundos e confira os registros do worker da fila sólida no Cloud Run. O pôster vintage vai aparecer diretamente na sua postagem.

📸 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. A Quest do Podcastifier bilíngue (exercício prático de programação antigravidade)

⚠️ Aviso importante:ao contrário do Gerador de capas acima, este recurso AINDA NÃO foi implementado.

Se você abrir blog/app/jobs/podcastifier_job.rb, vai encontrar um stub de scaffold vazio:

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

Nesta atividade prática, você vai programar em dupla com o Google Antigravity para concluir este recurso:

  1. Comando Antigravity:"Me ajude a implementar PodcastifierJob em app/jobs/podcastifier_job.rb. Ele precisa buscar a postagem, traduzir ou sintetizar uma visão geral em áudio em italiano usando o Google Cloud Text-to-Speech (voz "it-IT-Wavenet-A" com código de idioma "it-IT" via credenciais do ADC) e anexar o arquivo de áudio a post.podcast_audio_it usando o ActiveStorage!"
  2. Adicionar player à visualização:em app/views/posts/show.html.erb, adicione um player HTML5 quando @post.podcast_audio_it.attached?.
  3. Acionar síntese:quando salvo, o Solid Queue executa o job de conversão de texto em voz de forma assíncrona em segundo plano.

Interface bilíngue do Podcastifier com players de áudio duplos

🎧 Exemplos de saídas do Podcastifier:

💡 Precisa de ajuda ou não sabe o que fazer? Uma solução de referência completa e testada está disponível na ramificação solutions/podcastifier. Você pode inspecionar a qualquer momento com git diff solutions/podcastifier.

3. [OPCIONAL] 🏴‍☠️ A caça ao tesouro do GCS (recuperação de blob do console)

ℹ️ Verificação de pré-requisitos: local

cloud-sql-proxy

CLI obrigatória: este exercício opcional conecta seu laptop local diretamente ao Google Cloud SQL por um túnel mTLS criptografado. Verifique se você instalou o proxy da CLI:

which cloud-sql-proxy
  • Se estiver instalado:siga as instruções abaixo.
  • Se NÃO estiver instalado e você quiser fazer esta missão:instale com:
    # 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
    
  • Caso contrário:pule para a validação automatizada da etapa 7.

Lembra da foto que você enviou na etapa 4 antes de o reinício do contêiner apagar o banco de dados SQLite efêmero? Esse arquivo de imagem ainda está armazenado com segurança no seu bucket particular do GCS como um "blob órfão".

Vamos usar o console do Rails para resgatá-lo e anexá-lo a uma postagem do 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

No console do 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

Atualize seu blog: a foto enviada na etapa 4 do Stateless Shock é ressuscitada e anexada permanentemente à sua postagem do Cloud SQL.

4. Validação automatizada da etapa 7

Verificar jobs e recursos de IA generativa:

just workshop-eval 7

10. Etapa 8: Escolha sua aventura / Missões avançadas 🏆

Agora que você aprendeu a arquitetura de referência canônica, escolha sua missão de conclusão!

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

  • Dificuldade:média (segurança empresarial)
  • O objetivo:bloquear seu aplicativo do Cloud Run para que apenas suas contas verificadas do Google / Gmail possam acessá-lo, eliminando completamente o login com senha.
  • Como funciona:
    1. Um balanceador de carga de aplicativo HTTPS externo encerra o Google OAuth e verifica a identidade antes que o tráfego chegue ao Cloud Run.
    2. O Google encaminha cabeçalhos de identidade verificada (X-Goog-Authenticated-User-Email).
    3. O Rails faz login automaticamente na identidade verificada do Google usando 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. Ative o módulo do Terraform em iac/iap.tf com enable_iap = true e especifique suas Contas do Google permitidas:
      iap_allowed_users = [var.google_cloud_account]
      

Login reconhecido pelo banner do IAP

📊 Quest 2: Production SRE Telemetry & Cloud Logging

  • Dificuldade:média (observabilidade)
  • A meta:fazer streaming de registros de aplicativos JSON estruturados com IDs de correlação de rastreamento diretamente para o Cloud Logging e detectar exceções de produção em tempo real com o Cloud Error Reporting.
  • Como funciona:
    • Configure blog/config/environments/production.rb para emitir registros JSON estruturados com rótulos de rastreamento do GCP.
    • Acione uma exceção de teste intencional e veja o Error Reporting do Google Cloud agrupar e notificar você instantaneamente.

📸 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 pesquisa semântica e RAG do Gemini

  • Dificuldade:difícil (projeto final de IA generativa)
  • Objetivo:pesquisar artigos conceitualmente usando embeddings de vetor armazenados no PostgreSQL no Cloud SQL.
  • Como funciona:
    • Ative a extensão vector na sua instância do Cloud SQL PostgreSQL:
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • Adicione a gema neighbor a blog/Gemfile e gere embeddings de texto com o Gemini (text-embedding-004) em Post#after_save.
    • Realize consultas de distância de cosseno (<=>) para alimentar uma barra de pesquisa semântica com Turbo Streams.

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

⚡ Quest 4: Assistente de auditoria de performance e SEO

  • Dificuldade:fácil / divertido (experiência do desenvolvedor)
  • Objetivo:auditar as Core Web Vitals, a Largest Contentful Paint (LCP) e os índices de legibilidade de Flesch-Kincaid / Fog usando as ferramentas Antigravity e Speedgrapher.

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

🤔 Caixa de curiosidades: como ter um console Rails ativo no Cloud Run

💡 A pergunta:em VMs tradicionais ou plataformas PaaS no estilo Heroku, os desenvolvedores geralmente usam SSH em uma instância em execução ou usam heroku run rails console para inspecionar ou corrigir dados de produção em tempo real. Qual é o equivalente nativo do Cloud Run e como você o protege?

Por que

gcloud run exec

não existe para os serviços do Cloud Run

O Cloud Run é uma plataforma sem servidor: seu contêiner pode ser executado como zero, uma ou centenas de instâncias idênticas, sem uma identidade estável ou um shell endereçável. Isso é fundamentalmente diferente do Kubernetes (kubectl exec), em que um pod específico tem um soquete nomeado ao qual você pode se conectar.

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

Os quatro padrões nativos do GCP

Padrão

Comando

Interativo?

Segurança

1. Proxy local + banco de dados remoto

DATABASE_URL=... rails console

✅ Completo

🔒 mTLS do proxy de autenticação do Cloud SQL

2. Job do Cloud Run (único)

gcloud run jobs execute --wait

❌ Somente script

🔒 Papel do IAM obrigatório

3. Gema do console da Web + IAP

Navegador → /rails/console

✅ Completo

🛡️ Confiança zero do IAP

4. Cloud Shell + proxy

Cloud Shell → bin/rails c

✅ Completo

🔒 Identidade do Google

Padrão 1 (recomendado para inspeção de dados): execute um console Rails local que se comunica diretamente com o Cloud SQL PostgreSQL pelo proxy de autenticação do 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

Você tem total interatividade do IRB sem exposição à rede. O proxy do Cloud SQL usa mTLS em um soquete de domínio Unix ou loopback.

Padrão 3 (console da Web + zero trust do IAP, o caminho do navegador reforçado):

A gem web-console oferece uma sessão do IRB baseada em navegador. Sem proteção, ela é uma vulnerabilidade de segurança crítica. Nunca a exponha publicamente. Mas, combinado com o IAP do Google (Quest 1), a superfície de ataque diminui: apenas identidades verificadas do Google na sua lista de permissões podem acessar o 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

⚠️ O padrão 3 exige que a missão 1 (IAP) seja ativada primeiro. Sem ele, /rails/console em um URL público é equivalente a deixar a sala do servidor destrancada. Com o IAP, ele se torna uma ferramenta de operador poderosa reforçada pela confiança zero de nível Google.

Pergunte ao Antigravity: "Mostre como executar

rails console --sandbox

com segurança no meu banco de dados de produção do Cloud SQL usando o proxy de autenticação do Cloud SQL"

🏆 Graduação no The Hive: prova de trabalho validada por fiscal

Depois de concluir a missão escolhida, reivindique o troféu de graduação da Etapa 8 🏆 no ranking da Hive:

  1. Envie sua missão no GitHub:
    • Abra um novo problema no repositório oficial: github.com/palladius/rails8-app-on-gcp/issues/new
    • Formato do título: 🎓 [Step 8 Completed] :
    • No corpo, inclua uma breve descrição do que você realizou e um link para sua implantação ativa do Cloud Run.
  2. Conecte seu app do Cloud Run ao problema:
    • Anote o número do problema (por exemplo, 88 ou o URL completo https://github.com/palladius/rails8-app-on-gcp/issues/88).
    • Injete a variável de ambiente STEP_8_GHI no serviço do Cloud Run:
      gcloud run services update blog \
        --region europe-west1 \
        --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER>
      
    • Verifique se o endpoint /status.json agora informa sua missão:
      curl -s https://<YOUR_APP>.run.app/status.json | jq .quest
      
      Você vai ver:
      {
        "step_8_completed": true,
        "ghi_issue": 88,
        "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88"
      }
      
  3. Receba sua revisão do proctor e desbloqueie o troféu de ouro:
    • Confira o ranking da Hive: sua linha agora vai mostrar um selo âmbar: ⏳ GHI #XX review pending.
    • Peça aos monitores do workshop (@palladius, @emilianodellacasa ou @ricc) para inspecionar sua implantação.
    • Assim que um fiscal comentar LGTM no seu problema, o Hive vai elevar automaticamente seu progresso para 8/8 🏆 com um selo dourado/roxo brilhante e um troféu permanente que leva diretamente ao seu problema principal.

11. Conclusão e limpeza 🎓

Parabéns! 🎉

Você criou e implantou um aplicativo Rails 8 pronto para produção e empresas no Google Cloud Platform:

  • Progressão de ramificação zero:domínio de fluxos de trabalho modernos com sobreposições de Time-Machine em main.
  • Cloud Storage:armazenamento de objetos escalonável e particular com assinatura de blob do IAM (iam: true).
  • Cloud SQL:PostgreSQL gerenciado e protegido com túneis mTLS do proxy de autenticação do Cloud SQL.
  • Secret Manager:zero credenciais de texto simples ou vazamentos de arquivos .env.
  • Multicontêiner do Cloud Run:arquivos secundários isolados da Web Puma, do worker Solid Queue e de proxy.
  • IA generativa:geração assíncrona de pôsteres vintage e síntese de podcasts de TTS com a Vertex AI.
  • Quests:IAP zero trust, observabilidade de SRE e embeddings do pgvector.

🧹 Limpeza de recursos

Para evitar cobranças contínuas após a conclusão do workshop, limpe seus recursos do Google Cloud. Escolha a abordagem mais adequada ao seu ambiente:

Use essa opção se você estiver usando um projeto na nuvem do Google Cloud compartilhado ou atual em que outros recursos ou dados estão armazenados. Ele destrói apenas os recursos provisionados durante este workshop (Cloud Run, Cloud SQL, Secret Manager, buckets do GCS), deixando o restante do projeto intacto.

cd iac
terraform destroy -auto-approve

Opção 2: exclusão total do projeto (não deixar rastros 🌪️)

Use essa opção se você criou um projeto de workshop dedicado (por exemplo, usando créditos de workshop ou uma sandbox) e quer garantir que nenhum rastreamento permaneça, incluindo registros, metadados, contas de serviço e links de faturamento.

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet