Google Cloud 上的 Rails 8:从零开始到 AI

1. 简介

欢迎参加 Google Cloud 上的 Rails 8 研讨会!在此实操 Codelab 中,您将把一个现代化的 Rails 8 应用从简单的本地 SQLite 基准迁移到 Google Cloud 上可用于生产的企业级参考架构。

在本研讨会中,我们不仅会部署应用,还会执行有主见、生产级云现代化(正确完成直接原样迁移!)。

您从典型的“在我的机器上运行”本地设置(临时磁盘、嵌入式 SQLite、进程内作业)开始,逐步将其发展为 Google Cloud 上企业级、万无一失的云架构:

  1. 🐘 从本地 SQLite → Cloud SQL (PostgreSQL):重启时不再清除数据库。我们改用通过 Cloud SQL Auth 代理(mTLS,无公共 IP)安全连接的受管 PostgreSQL。
  2. 🪣 从临时本地存储空间 → 私有 GCS:无服务器磁盘是临时的。我们使用 IAM 签名网址 (iam: true) 将 ActiveStorage 连接到私有 Google Cloud Storage,这样就不会有公开存储桶,也不会泄露密钥。
  3. 🚀 从进程内作业 → 多容器 Cloud Run:我们架起了 Rails 单体式应用和无服务器云之间的桥梁!使用标准 Docker Compose,我们在 Cloud Run 多容器 pod 中将 Solid Queue 作为专用工作器容器与 Puma 一起运行 - 没有微服务蔓延,没有线程饥饿,DHH 会感到自豪!

🍌 由于我们生活在 2026 年,因此我们使用 Google Antigravity 为整个堆栈提供强大支持。这并非事后考虑,我们提供了提示和 技能,可帮助您更好地执行(并享受)本次研讨会!

🏛️ 目标参考架构(变体 1:简洁的扁平矢量企业)

以下是您将构建和部署的规范 Google Cloud 参考架构(变体 1:简洁的扁平矢量企业),展示了我们的生产多容器 Cloud Run 服务如何连接到所有受管理的 Google Cloud 持久性和 AI 服务:

变体 1:简洁的扁平向量企业版 - Google Cloud 上的 Rails 8 架构

🎞️ 架构演变:从零到云原生

在每个研讨会里程碑中,观看我们的堆栈从临时单机基准逐步现代化为完全受管理的 Google Cloud 蓝图:

研讨会架构演变

学习内容

让我们开始吧!

2. 第 0 步:前提条件、Antigravity 设置和结算验证

💡 场景:您和您的团队正在构建一个关键任务 Rails 8 应用。在接触代码或启动云资源之前,我们必须建立工具链、连接 Google Antigravity,并验证我们的 Google Cloud 凭据和结算基础。

1. 前提条件核对清单

在开始之前,请确保您的环境中已安装下列工具:

  • just (1.21 及更高版本):(just --version) 任务运行程序 - 本研讨会中的每个命令都是一个 justrecipe(让 AI 框架安装它,或参阅 skills/rails8app-workshop)。
  • Git (2.30 及更高版本): (git --version) 用于版本控制、分支和克隆代码库。
  • Google Cloud SDK (gcloud CLI):已安装且为最新版本。
  • Terraform CLI (1.5 及更高版本):用于声明式基础设施预配。
  • docker 和 docker-compose:已安装并在本地运行。
  • Ruby 3.4.5:(ruby -v - 由 blog/.ruby-version 固定;如果缺少,请让 AI 框架安装 3.4.5)。Rails 8 在 Gemfile 中声明,并由 bundle install 自动安装。
  • Google Antigravity 2.0:您的自主 AI 结对编程助理(下载 Google Antigravity 2.0)。您将使用个人 Gmail 账号登录,无需 API 令牌。

2. Google Cloud 身份验证、专用配置和 ADC

为防止与现有的公司、个人或多账号 gcloud 设置发生冲突,我们强烈建议为此研讨会创建一个专用命名配置:

# 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

💡 为什么要使用专用配置?使用 gcloud config configurations create rails8-on-gcp-workshop 可专门针对本研讨会隔离所有 CLI 设置(账号、配额项目、默认区域)。完成后,您可以随时使用 gcloud config configurations activate default 切换回正常设置。

有效的 gcloud 配置

3. 🚨 GCP 结算验证

⚠️ 注意:Google Cloud SQL 和 Cloud Run 部署需要有效的关联结算账号或有效的研讨会教育积分。现在,我们来确保您的设置正确无误。为此,请运行结算验证检查:

gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT

确保返回 billingEnabled: true。您还可以列出所有结算账号:

gcloud billing accounts list

确保其中至少一个显示 OPEN: True。如果没有有效的结算账号,请在 Google Cloud 控制台的“结算”页面中关联结算账号或兑换研讨会积分优惠券。

有效的 GCP 结算账号

4. 克隆代码库并与 Antigravity 配对

克隆代码库并进入相应目录。请注意,我们完全停留在 main 上:

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

在 Google Antigravity 中打开此目录。Antigravity 会自动检查代码库、读取 AGENTS.md,并随时准备好充当您的结对编程伙伴。

5. 🪫 如果 Antigravity 的免费点数用完

⚠️ 您刚刚兑换的云服务赠金不会为 Antigravity 充值。这是两个不同的结算平台:Antigravity 的配额位于 Google AI 方案 (Google One) 中,而代金券和 Cloud 积分则位于 Cloud Billing 账号中。将结算账号与 GCP 项目相关联不会让您获得一次额外的“反重力”提示。

如果代理因配额问题而停止,请按以下步骤操作 - 前两步是免费的,并且可以立即完成:

  1. 切换模型池。配额是按模型系列跟踪的,Antigravity 显示两个独立的池(Gemini 模型与 Claude/GPT 模型)。Claude 和 GPT-OSS 模型在免费层级中可用,因此即使 Gemini 池耗尽,其他模型池通常也不会受到影响。使用提示框下方的模型选择器。Antigravity 模型选择器 - 在 Gemini、Claude 和 GPT-OSS 池之间切换
  2. 选择 Flash 模型,而不是 Pro 模型。速率限制与代理每次请求执行的工作量相关,因此较轻的模型可以充分利用剩余的资源。
  3. 使用您自己的 Gemini API 密钥运行 Antigravity CLI。Antigravity IDE 不支持自带密钥,但 CLI 是有文档记录的替代方案。在 Google AI Studio 中创建免费密钥(需要项目,但无需结算账号),然后下载并配置 Antigravity CLI。
  4. 等待,或升级。免费层级配额每周刷新一次(错误消息中会显示重置日期)。Google AI Pro/Ultra 是唯一有据可查的提高桌面应用内基准的方式;这些方案每 5 小时刷新一次,并且可以花费购买的 AI 点数来支付超额费用。
  5. 虽然这是一个 Antigravity 工作坊,但大多数 AI 编码框架也应该适用,只需将它们指向代码库中的 AGENTS.md 和 skills/ 目录即可。

6. 自动第 0 步验证

验证您的第 0 步环境是否 100% 符合评估套件的要求:

just workshop-eval 0

✨ 精彩瞬间:Antigravity 和研讨会评估引擎可在 2 秒内验证您的 CLI 版本、Ruby 环境和 ADC 身份验证!

3. 第 1 步:Terraform 基础架构启动和预检诊断

💡 策略:Google Cloud SQL PostgreSQL 等代管式数据库大约需要 10-12 分钟才能完成预配。我们不会在本地开发时闲着等待,而是立即在后台通过 Terraform 启动不可变的基础设施!

1. 运行自动化预检诊断套件

在启动云基础架构之前,请运行全面的预检测试套件:

just workshop-test

此脚本 (bin/workshop_diagnostics.rb):

  • 验证您的GOOGLE_CLOUD_ACCOUNT身份配置。
  • 验证有效的结算和项目关联。
  • 验证 Vertex AI 的应用默认凭据 (ADC)。
  • 确认 ActiveStorage Canary 种子映像 (blog/app/assets/images/gcs_dev_image.jpg)。

仅限工作坊测试诊断输出

如果缺少 .env,请从已记录的模板中复制:

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

💡 提示:如果 just workshop-test 报告您的项目上结算 API 已停用,您可以通过以下方式快速启用它:

gcloud services enable cloudbilling.googleapis.com

2. ⏱️ 异步启动 Terraform 基础设施

运行单步 Terraform 配方,该配方会创建远程后端状态存储桶并触发基础架构部署:

just terraform-apply

Terraform 已成功初始化

手动替代方案(或直接运行 `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 ..

Terraform 配置的内容:

  • Google Cloud Storage 存储桶:创建时具有私密访问权限和 IAM 凭据签名 (iam: true)。
  • Canary 测试映像:将 gcs_dev_image.jpg 上传到相应存储桶,以启用端到端 Blob 验证。
  • Google Cloud SQL PostgreSQL 实例:启动后台预配(约 10-12 分钟)。

Google Cloud 控制台中的 Cloud SQL PostgreSQL 实例

💡注意:您可以放心地忽略有关缺少自动备份的警告,因为我们不需要为本次研讨会数据库进行自动备份(尽管自动每日备份是任何实际生产数据库的基本最佳实践!)。

Terraform apply completed successfully with outputs

📝 记下 Cloud Run 网址:Terraform 应用应成功完成并输出您的基础设施。记下您的 cloud_run_url(或将其复制到剪贴板)- 您将在后续步骤中验证实时服务时需要用到它!

3. 自动执行第 1 步验证(可选)

验证第 1 步的前提条件和配置是否通过:

just workshop-eval 1

⏱️ 注意:此评估会检查实时云资源,可能需要几分钟才能完成。这完全是可选的 - 虽然它会在您的终端中运行,但您可以立即继续执行下一步,而无需等待!

4. 第 2 步:本地基准、Mailpit 和管理员初始配置

我们的起点是一个干净的现代 Rails 8 博客应用,该应用在 localhost 上运行,使用 SQLite、Mailpit 电子邮件拦截和基于磁盘的 ActiveStorage。

1. 在本地启动应用

📁 注意目录!此代码库包含 blog/ 中的 Rails 应用和 iac/ 中的 Terraform。第 1 步让您位于代码库根目录中,因此第 2 步中的每条命令都是从该目录运行的

blog/ - Gemfile、bin/rails 和 compose.yaml 都住在那里。从根目录运行这些测试时,会失败并显示 no configuration file provided: not found 或缺少 Gemfile。

进入 Rails 应用,确认您使用的是固定的 Ruby,然后安装 gem 并设置数据库:

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

🧯 写入权限错误

/var/lib/gems/

或

ruby -v

显示系统 Ruby(例如 3.3.x)?Vanilla Linux 系统 Ruby 完全忽略 .ruby-version 文件,并尝试在系统范围内安装 gem!确保 rbenv 已加载到您的活动终端 shell 中:

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

注意:如果您在第 0 步中跳过了 3.4.5 的安装,请运行 rbenv install 3.4.5。或者,只需使用下面的模式 A (Docker Compose) (just compose-up),即可在容器内完全运行固定的 Ruby 堆栈,而无需触碰主机 gem!

💡 不想考虑这个问题?just install 和 just compose-up 会为您执行 cd blog。

2. 🌱 智能种子自动发现和管理员引导启动(问题 #21 和 #25)

数据库种子 (db/seeds.rb) 具有智能环境自动发现功能:

  • 它会检查您的有效数据库适配器(SQLite 与 Postgres)和存储配置。
  • 它会检测到 Stage 0(本地主机),并自动创建初始管理员用户和种子教学帖子:
    • [LOCAL BASELINE] Welcome to Rails 8 on Localhost!
    • 开箱即用的本地 sad 映像附件 (local_sad_image.png),带有水印,告知您本地磁盘存储空间是临时性的。
  • 它会自动通过 ActionMailer 触发重设密码电子邮件。

验证或重新运行种子(自动使用 .env 中的 GOOGLE_CLOUD_ACCOUNT):

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

通过 Docker Compose 启动本地开发技术栈:

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

Docker Compose 启动 Rails、Mailpit 和本地堆栈

应用、本地 Mailpit SMTP 服务器和 Adminer 数据库查看器现在在隔离的容器中一起运行:

  • Rails 应用:http://localhost:3000
  • Mailpit Web 界面:http://localhost:8025
  • Adminer DB 界面:http://localhost:8081

🧯 端口 3000 冲突或

server.pid

错误?如果您之前启动了原生开发服务器 (bin/dev),请使用 Ctrl+C 停止该服务器。如果 Docker 抱怨端口 3000 已分配或找到剩余的 server.pid,请使用以下命令进行清理:

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

3. Mailpit 体验和管理员登录

  1. 使用 Mailpit 捕获发出的电子邮件:在浏览器中打开 http://localhost:8025。查看本地收件箱!在 db:seed 期间,ActionMailer 发送了一封管理员初始配置电子邮件,Mailpit 在本地安全地捕获了该电子邮件,而无需访问外部电子邮件服务器或凭据:

Mailpit 网页界面显示拦截到的重设密码电子邮件 2. 登录博客:在浏览器中打开 http://localhost:3000。

  • 您可以直接点击 Mailpit 电子邮件中的重设密码链接来设置密码。
  • 或者,您也可以使用 Google Cloud 账号电子邮件地址(来自 .env)和默认初始密码 Ch4ng3m3!!1 登录。
  1. 查看视觉遥测徽章:
    • 请注意界面中的黄色环境横幅和徽章:Notice: Ephemeral Database Active (POSTGRESQL) 和 [EPHEMERAL DB / STORAGE] 💾 Local。
    • 请注意帖子水印:🏠 标记(封面图片右下角的 nanobanana_stamp_local.png)。这样可立即直观地确认您的资源和数据库当前已绑定到临时本地存储空间。

本地博客首页,显示临时遥测通知和徽章

💡 可选的专业提示:Rails 控制台锻炼想知道 Rails 如何通过 CLI 与您的种子数据互动吗?进入交互式控制台:

bin/rails console

使用环境变量动态检查初始化的管理员用户:

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. 自动执行第 2 步验证

验证本地基准和管理员设置:

just workshop-eval 2

仅限 workshop-eval 2 验证输出

5. 注意事项:无状态容器

Cloud Run 容器是无状态的,并且是临时性的。如果我们直接将 SQLite 数据库和本地 storage/ 目录部署到 Cloud Run,那么每当容器缩放到零或重新启动时,所有帖子和上传的图片都会被永久清除。

在下一步中,我们将有意将此临时配置部署到 Cloud Run,以便亲身体验无状态冲击!

5. 第 3 步:部署 1 - 无状态冲击(3 分钟内实现早期 WOW!)

现代无服务器开发从一个早期胜利开始:在不到 3 分钟的时间内将单容器 Rails 应用直接部署到 Google Cloud Run,而不是等待 15-20 分钟才能看到云数据库中的任何内容!

1. 时光机倒带

为了确保我们的初始配置是 100% 临时的(本地 SQLite 数据库和本地磁盘文件存储),请使用零分支 Time-Machine 引擎:

just workshop-rewind 1

💡 刚刚发生了什么?workshop-rewind 1 将 stage-1-stateless 配置叠加层应用到了 blog/config/,而没有离开 main 分支。您的应用已配置为在容器磁盘上使用 SQLite,在本地文件系统上使用 ActiveStorage。

2. 将单容器 Puma 部署到 Cloud Run

直接从源代码部署到 Cloud Run。新版 Rails 8 可自动生成开箱即用的正式版、可用于生产用途的 Dockerfile(具有多阶段构建、jemalloc 和非 root 安全性)。当您从 blog/ 文件夹进行部署时,Google Cloud Build 会直接检测到此原生 Dockerfile 并预配托管式无服务器服务:

# 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

您应该会看到类似于以下内容的进度输出:

gcloud run 部署构建和部署容器

⏳ Cloud Build 打包容器并预配 Cloud Run 修订版本,这应该需要几分钟时间。

🧯 问题排查和常见注意事项

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

? 两种不同的原因导致了同一错误:

  1. gcloud 刚刚为您启用了一个 API。如果部署打印了 The following APIs are not enabled ... cloudbuild.googleapis.com 且您回答了 Y,则 build 可能在新权限完成传播之前就开始了 - gcloud 自己的提示会警告“这需要几分钟时间”。等待一分钟,然后重新运行完全相同的命令;通常会成功。第 1 步的 terraform apply 通常会为您启用此 API,因此您应该根本不会看到该提示。
  2. 默认计算服务账号确实缺少角色。新版 GCP 项目会强制执行最小权限,这会中断源代码部署。使用以下命令修复:
    just workshop-test 现在也会报告此问题,因此如果部署一直失败,值得重新运行。
    just project-status   # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builder
    

在部署期间:

  1. Cloud Run 会构建您的 Rails 容器映像。
  2. 它会分配一个公开且安全的 TLS 网域:https://blog-[hash]-[region].a.run.app。
  3. 流量开始路由到端口 8080 上的 Puma。

3. ✨ 早期 WOW 时刻

在浏览器中打开生成的 Cloud Run 网址!

  1. 您的现代 Rails 8 应用已在 Google Cloud 上线!
  2. 使用您的管理员凭据(GOOGLE_CLOUD_ACCOUNT 和 APP_ADMIN_PASSWORD)登录。如果您找不到登录按钮,请点击“新帖子”,系统会先提示您输入用户名和密码:带有临时遥测横幅的 Rails 8 登录页面
  3. 点击“新帖子”,撰写一篇名为“我的第一篇 Cloud Run 帖子”的文章,附上一张图片,然后点击创建帖子。
  4. 您的帖子将以完整格式发布,并且系统会呈现您的图片。
  5. 请注意直观的遥测数据:
    • 标头徽章:[EPHEMERAL DB / STORAGE] 💾 Local
    • 图片水印:🏠 标记(右下角的 127.0.0.1 临时磁盘徽章)。

🐝 加入直播研讨会蜂群排行榜!如果您在线,并且监考人员正在显示排行榜,而您想加入排行榜,请在此处添加您的 Cloud Run 网址:👉 在排行榜上注册您的 Cloud Run

Google Cloud Run 控制台博客服务修订版本详细信息

4. 💥 陷阱:无状态冲击和“Puma 变通方案”陷阱

Cloud Run 是一个无状态的无服务器平台。当 Web 流量降至零时,Cloud Run 会将容器实例缩减至零,以节省费用。当有新的 HTTP 请求到达或部署新的容器修订版本时,Cloud Run 会启动一个全新的干净容器映像。

“作业卡住”横幅

在单容器部署中创建帖子或附加图片时,Rails 会将 ActiveJob 任务(例如图片尺寸分析或元数据索引)加入队列。但由于没有人运行后台工作器,您会看到警告横幅:

注意:目前有 8 个后台作业等待执行

诱人的修复:在 Puma 中运行 Solid Queue

一位聪明的开发者可能会说:“等等!借助 Rails 8,我们可以通过启用

SOLID_QUEUE_IN_PUMA=true

环境变量!"

我们来在 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

现在,返回到浏览器并刷新页面:

💥 无状态冲击:

  1. 好消息:Solid Queue 工作人员现在在 Puma 中处于活跃状态!任何新作业都会立即耗尽。
  2. 冷水浴(亮点!):
    • 由于 Cloud Run 部署了新修订版本,因此之前的容器实例已被替换!
    • 您撰写的文章和磁盘上的 SQLite 数据库文件被完全清除!
  3. 架构经验:在 Puma 中运行后台工作器会消耗宝贵的 Web 线程 CPU/RAM,并且仍然无法解决持久性问题。

5. 自动执行第 3 步验证

验证 Cloud Run 部署和提醒处理:

just workshop-eval 3

✨ 经验教训:像 SOLID_QUEUE_IN_PUMA 这样的单容器变通方案在本地开发中非常有用,但在现代云架构中:

  • 媒体文件需要 Google Cloud Storage (GCS)(第 4 步)。
  • 关系型数据和作业队列需要托管式 Google Cloud SQL(第 5 步)。
  • 繁重的后台工作器应放在专用边车容器中(第 6 步)。

6. 第 4 步:部署 3 - GCS 永久性存储空间和 POLA 警告

在此步骤中,我们将媒体和文件存储与容器磁盘分离,方法是将 ActiveStorage 切换到 Google Cloud Storage (GCS),并通过 IAM Credentials API (iam: true) 使用短期有效的签名网址。

在修改任何配置之前,请先了解 Rails 与 GCS 通信所需的三个要素:

① 项目 ID + 存储桶名称 - Rails 必须知道将 blob 存储在何处。应用从环境中读取 GOOGLE_CLOUD_PROJECT,并将存储桶名称派生为 ${GOOGLE_CLOUD_PROJECT}-activestorage-prod。您在第 1 步中运行的 Terraform 脚本已创建此存储桶,因此无需执行额外操作。

② 具有相应权限的服务账号 - Cloud Run 以 Compute Engine 默认服务账号 (PROJECT_NUMBER-compute@developer.gserviceaccount.com) 的身份运行。此服务账号需要对存储桶具有以下两个 IAM 角色:

  • roles/storage.objectAdmin - 用于上传和提供 Blob
  • roles/iam.serviceAccountTokenCreator 本身 - 调用 IAM Credentials signBlob API 并生成短期有效的签名网址

⚠️ 诱人的捷径 -

public: true 要让 GCS 正常运行,最快的方法是在 storage.yml 中设置 public: true,并授予对存储桶的 allUsers:objectViewer。图片会立即加载,无需登录。请勿这样做。公开存储桶会将所有上传的用户媒体永久泄露到开放式互联网。我们的蓝图使用 iam: true 将存储桶设为完全私密状态,每个图片网址都是由 IAM Credentials API 临时生成的签名令牌,并且会过期。

③

ACTIVE_STORAGE_SERVICE=google - 在运行时将 Rails 从本地磁盘切换到 GCS 的单个环境变量。无需更改代码,无需重新部署应用逻辑,只需切换一个环境变量。

1. 时光机倒带

将时光机推进到第 2 阶段:

just workshop-rewind 2

检查 blog/config/storage.yml - 您会看到应用定义了 3 个已命名的 GCS 环境(开发 / 测试 / 生产),所有环境都使用相同的安全模式:

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

GCS IAM 签名架构

2. 授予 IAM 存储和签名权限

确保您的 Cloud Run 运行时服务账号具有对网址进行签名和上传对象的权限:

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. 第三次部署到附加了 GCS 的 Cloud Run

重新部署并启用 GCS。确保您位于 blog/ 文件夹中(否则 Cloud Run 会尝试使用 Buildpack 而不是 Rails 8 Dockerfile 进行构建,从而导致构建失败)。

使用 --update-env-vars(而非 --set-env-vars)保留现有环境变量(主密钥、IAP 配置等):

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

💡提示:如果部署失败,请点击终端输出中方便提供的 Cloud Build 日志链接(如下图所示)。您还可以让 Antigravity 帮助您解读和修复错误!

终端输出中的 Cloud Build 失败日志链接

4. ✨ 幸存的影像和云端邮票

  1. 刷新您的博客网址。
  2. 创建一篇名为“在云端生存”的新博文,并上传一张照片。
  3. 查看图片右下角:
    • “🏠”图章不见了!
    • 它已替换为彩色 Cloud GCS 邮票 (nanobanana_stamp_cloud.png)。
  4. 强制重启另一个容器(这应该会触发第四个修订版本):
    gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s)
    
  5. 刷新页面:在 SQLite 数据库重置期间,图片二进制文件在 Google Cloud Storage 中安然无恙!通过 CLI 验证:
    gcloud storage ls -l -r gs://$GCS_BUCKET/
    

以下是您可能会在生产存储桶 (gs://$GCS_BUCKET) 的 Google Cloud Storage 浏览器控制台中看到的已上传 blob 和媒体对象列表:

Google Cloud Storage 控制台存储桶详细信息,显示了持久性映像 blob

5. ⚠️ 陷阱:作业卡住警告横幅

您上传图片时,ActiveStorage 会将分析作业 (ActiveStorage::AnalyzeJob) 加入队列,以提取尺寸和元数据。

不过,Cloud Run 目前仅运行一个网络容器 (puma)。在 Rails 8 中,Solid Queue 会将后台作业存储在数据库中,但没有人执行 bundle exec rails solid_queue:start!

查看博客页面顶部:您会看到 blog/app/views/layouts/_check_stuck_jobs.html.erb 呈现的醒目警告横幅。

我们将在第 6 步中妥善解决此问题,届时我们将升级到生产级多容器边车架构,其中包含一个专用的 worker 容器,用于独立于 Web 流量运行 Solid Queue!

6. 自动执行第 4 步验证

验证您的 GCS 配置和卡住的作业遥测数据:

just workshop-eval 4

7. 第 5 步:预检核对清单:Cloud SQL、密钥和数据库连接

虽然您在第 3 步和第 4 步中熟练掌握了无状态容器和 Google Cloud Storage,但 Terraform 已在后台完成生产后端的烹饪!

在部署完整的 3 容器生产端车架构(第 6 步中的部署 4)之前,此步骤可作为快速飞行前检查来验证数据库健康状况、连接性和密钥访问权限。

0. 引导环境变量

直接从 Terraform 输出中导出所有必需的 shell 变量:

# 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. Cloud SQL 健康状况检查

验证受管 PostgreSQL 实例是否已完成初始配置且运行状况良好:

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

输出必须为 RUNNABLE。

2. (可选)测试笔记本电脑与数据库的连接

在生产环境中,Cloud Run 容器通过加密的边车代理 (127.0.0.1:5432) 安全地连接到 Cloud SQL,而不会将数据库暴露给公共互联网 (0.0.0.0/0)。

ℹ️ 注意:此步骤完全是可选的!在 Cloud Run 中,官方 Cloud SQL Auth 代理边车容器会自动处理 mTLS 连接。您无需在本地安装任何内容即可完成本讲座。

如果您在计算机上安装了 cloud-sql-proxy 和 psql,并且想要验证笔记本电脑的直接连接性,请执行以下操作:

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

(如果系统提示输入密码,请输入

$DB_PASSWORD

。否则,您可以直接跳到下面的第 3 步!)

3. 检查通过 Terraform 预配的 Secret

切勿在 Git 或容器环境变量中存储明文数据库密码、API 密钥或 Rails 主密钥。在现代云架构中,我们使用 Google Cloud Secret Manager 进行零信任运行时注入。

ℹ️ Terraform 在第 1 步中为您处理了此问题:

  1. rails-master-key - 用于在运行时解密 Rails 凭据。
  2. rails-db-password - 与 Cloud SQL 用户匹配的加密安全随机密码。
  3. rails-admin-password - 初始管理员用户的安全密码。

验证您的 Secret 在 Secret Manager 中是否存在且处于有效状态:

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

Google Cloud Secret Manager 控制台列出了已配置的应用密文

🔑 将 master.key 同步到笔记本电脑(本地同步)

由于 blog/config/master.key 因安全原因而被 gitignore,因此您的本地克隆可能尚未包含它。运行此友好的自修复代码段,从 Secret Manager 中提取凭据,以便本地 Rails 工具可以解密凭据:

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

🧪 实践练习:想创建自定义 Secret,看看有多简单?Secret Manager 不仅适用于 Rails!您可以通过一个简单的 CLI 命令创建任何 Secret:

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

4. 验证 Secret 访问者权限

为了让 Cloud Run 容器在启动时将这些 Secret 装载为环境变量或卷装载,运行时服务账号 ($RUN_SA) 需要对每个 Secret 拥有 roles/secretmanager.secretAccessor 角色。

Terraform 已在第 1 步中授予此权限!验证 rails-master-key 上的 IAM 政策绑定:

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

您应该会看到:

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

Cloud SQL Auth 代理安全性比较

5. 自动预检验证

运行自动化步骤 5 测试套件,以确认您的数据库可运行、密钥不为空且 IAM 权限为绿色:

just workshop-eval 5

8. 第 6 步:部署 4 - 企业多容器边车(黄金标准)

现在,我们来组建最终的参考架构:多容器 Cloud Run!

1. 恢复黄金标准

将代码库配置恢复为规范的 main 状态:

just workshop-restore-gold

2. 检查 3 容器生产蓝图

打开 blog/compose.prod.yaml 并检查架构:

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

生产部署运行三个共享同一本地网络的协调容器:

  1. web:通过 Puma 在端口 8080 上提供 HTTP 流量。
  2. worker:运行 bundle exec rails solid_queue:start 的专用后台处理容器。
  3. cloudsql-proxy:绑定到 127.0.0.1:5432 的官方 Google Cloud SQL Auth 代理边车 (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2)。

3. 通过 Cloud Run 作业运行数据库迁移

在将 Web 流量路由到新部署之前,我们必须针对 Cloud SQL 运行数据库迁移和初始种子。

⚖️ 如何在容器中运行迁移:两种策略

在容器化的云环境中,执行数据库迁移有两种常见方法:

  • 选项 A:在 Docker Entrypoint 中自动执行(entrypoint.sh 在容器启动时执行 db:migrate)
    • 优点:完全自动化;开发者无需记住运行命令。
    • 缺点:在无服务器自动扩缩中存在风险!当 Cloud Run 在高流量下同时扩缩到 10 个实例时,10 个容器会争相并发运行迁移,从而导致数据库锁定、架构竞态条件或启动超时。
  • 选项 B:通过 Cloud Run 作业显式调用(我们的架构选择)
    • 优点:完全控制、事务安全性高,且不会出现竞态条件。作为专用的一次性容器任务运行,完全与提供网络流量无关。如果迁移失败,网页服务不会受到影响。
    • 缺点:需要在部署之前触发 CLI 作业或 CI/CD 步骤。

我们选择选项 B,以最大限度地提高生产可靠性。

⚠️ 严重:DATABASE_URL格式和 Ruby 3.4.5 RFC 3986 严格性

Cloud Run 作业使用原生 Unix 套接字代理 (/cloudsql/PROJECT:REGION:INSTANCE) 连接到 Cloud SQL。DATABASE_URL 必须使用三斜杠格式:

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 多数据库:应用为每个 database.yml 使用 4 个数据库(主数据库、队列数据库、缓存数据库、Cable 数据库)。所有 4 个 DATABASE_*_URL 环境变量都必须指向同一 Cloud SQL 实例,否则不会创建 Solid Queue/Cache/Cable 表。

在 db:prepare 之后,加载 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

gcloud run jobs executions describe JOBNAME 的输出在上图中,您可以看到

gcloud run jobs executions describe $JOBNAME

Google Cloud Run 作业控制台显示 rails-migrate 作业已成功完成Google Cloud Run 作业控制台显示

rails-migrate

执行成功

4. 第四次部署:通过 Docker Compose 部署多容器 Cloud Run

现在,直接从 compose.prod.yaml 部署真正的 3 容器生产堆栈(web、worker、cloudsql-proxy):

# 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

Google Cloud Run 控制台的“容器”标签页,显示 3 个边车容器Google Cloud Run 控制台:“容器”标签页显示所有三个边车容器(

cloudsql-proxy

、

web

、

worker

)

💡 界面好奇心 / 控制台故障:在控制台下拉菜单中,您可能会注意到 Cloud Run 将顶部条目标记为 Ingress container: cloudsql-proxy。不用担心!这纯粹是 Google Cloud 控制台中的视觉界面故障(在 #149 中跟踪),由容器依赖项排序引起。实际上,web 是唯一监听 HTTP 端口 8080 并处理 100% 入口 Web 流量的容器,而 cloudsql-proxy 严格来说仅在端口 5432 上处理专用 PostgreSQL 流量!

5. ✨ 惊喜时刻:零警告和完整的云端持久性!

深吸一口气,然后惊叹于您的屏幕:所有警告横幅都消失了!🪄

有关 Cloud Run(无警告)和 Cloud Persistent Post 的制作博客生产版 Rails 8 博客:纯净的界面、零警告横幅和持久的 Cloud SQL 状态!

该应用现在正式成为生产级云原生应用:

  • 通过安全的 mTLS 隧道技术,由代管式 Google Cloud SQL (PostgreSQL) 提供支持。
  • 通过 IAM Blob 签名安全地存储在私有 Google Cloud Storage 中的媒体。
  • 没有临时存储警告,也没有卡住的作业提醒。

打开 Cloud Run 网址:

  1. 查看遥测徽章:
    • [CLOUD PERSISTENT 🐘 ☁️] 已在数据库种子中验证!
    • 页脚中的 Google Cloud Run 和 GCS 徽章都闪耀着绿色光芒!
    • “作业卡住”警告横幅已完全消失!
  2. 创建博文和评论。
  3. 您可以根据需要多次重启或重新部署:您的数据、帖子、评论和资源将永久保存在 Cloud SQL 和 GCS 中!

6. 自动第 6 步验证

验证多容器配置:

just workshop-eval 6

9. 第 7 步:生成式 AI Pipelines、Podcastifier 和 GCS 寻宝游戏 🏴‍☠️

在专用容器中运行 Solid Queue 并激活 Google Cloud Storage 后,我们就可以释放异步生成式 AI 的强大功能了!

1. NanoBanana 复古封面生成器(即用型生成式 AI 流水线)

NanoBanana 吉祥物

好消息:此功能已在代码库中完全实现!您无需编写任何代码,只需触发并观察异步云端 GenAI 流水线的运行情况即可。

🧠 幕后运作方式

  • 身份验证:无需任何 API 密钥!它通过 Cloud Run 运行时服务账号自动使用应用默认凭证(在 Vertex AI 上为 roles/aiplatform.user)。(或者,如果提供了 GEMINI_API_KEY,则直接调用 Gemini API)。
  • 流水线:当创建没有封面图片的文章时,after_commit 回调会将 GenerateCoverImageJob 排入 Solid Queue。
  • 模型:它会提示 Google 的多模态模型(Gemini 2.5 Flash Image / Vertex AI 上的 Imagen)生成一张真实的 20 世纪 60 年代意大利电影海报(“Locandina di un film 1960”),其中包含香蕉客串和闪亮的红宝石“8”。
  • 动态来源水印:使用 libvips 在角落动态添加视觉徽章(GCS 上的彩色云戳)。
  • Turbo Stream 热插拔:附加图片后,Turbo::StreamsChannel.broadcast_refresh_to(post) 会实时更新读者的浏览器,而无需手动重新加载网页!

NanoBanana AI 异步架构序列图

📐 查看 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!

🧪 实时试用!

您可以通过以下两种方式触发该功能:

  • 方案 A(Web 界面):点击新帖子,输入标题(例如“使用 Ruby on Rails 8 的无服务器架构”),撰写一些内容,然后将封面图片文件输入留空。点击创建帖子。
  • 方法 B (CLI Runner):从终端运行:
    bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
    

等待约 15-30 秒:在 Cloud Run 中查看 Solid Queue 工作器日志。复古海报将直接显示在您的帖子中!

📸 待办事项(里卡多):添加一篇博文的屏幕截图,其中包含一张 AI 生成的 20 世纪 60 年代意大利电影海报,海报上有一根香蕉和一颗红宝石 8

2. 双语播客化工具任务(实操反重力编码练习)

⚠️ 重要提示:与上述封面生成器不同,此功能尚未实现!

如果您打开 blog/app/jobs/podcastifier_job.rb,会看到一个空的支架桩:

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

在此实践练习中,您的任务是与 Google Antigravity 结对编程,以完成以下功能:

  1. 提示 Antigravity:“帮我在 app/jobs/podcastifier_job.rb 中实现 PodcastifierJob。它应该获取帖子,使用 Google Cloud Text-to-Speech(通过 ADC 凭据使用语言代码为“it-IT”的语音“it-IT-Wavenet-A”)翻译或合成意大利语音频概览,并使用 ActiveStorage 将音频文件附加到 post.podcast_audio_it!”
  2. 添加播放器以供查看:在 app/views/posts/show.html.erb 中,当 @post.podcast_audio_it.attached? 时添加 HTML5 播放器。
  3. 触发合成:保存后,Solid Queue 将在后台异步执行文字转语音作业。

Podcastifier 双语界面,带有双音频播放器

🎧 Podcastifier 输出示例:

💡 需要帮助或遇到问题?在分支 solutions/podcastifier 上,您可以找到经过实战检验的完整参考解决方案。您可以使用 git diff solutions/podcastifier 随时检查。

3. [可选] 🏴‍☠️ GCS 寻宝活动(控制台 Blob 恢复)

ℹ️ 前提条件检查:本地

cloud-sql-proxy

需要使用 CLI 这项可选练习通过加密的 mTLS 隧道将本地笔记本电脑直接连接到 Google Cloud SQL。检查您是否已安装 CLI 代理:

which cloud-sql-proxy
  • 如果已安装:请继续执行以下步骤!
  • 如果未安装,但您想完成此任务:请使用以下命令进行安装:
    # 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
    
  • 其他情况:您可以直接跳到第 7 步的自动验证!

还记得您在第 4 步中上传的照片吗?在容器重启清除临时 SQLite 数据库之前,您上传了这张照片。该图片文件仍安全地存储在您的私有 GCS 存储桶中,只是变成了“孤立的 blob”!

我们来使用 Rails 控制台抢救它,并将其附加到 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

在 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):添加交互式 Rails 控制台执行 GCS Treasure Hunt 代码段并拯救孤立的 ActiveStorage blob 的屏幕截图

刷新您的博客:在第 4 步“无状态冲击”期间上传的照片已恢复,并永久附加到您的 Cloud SQL 博文中!

4. 自动执行第 7 步验证

验证生成式 AI 作业和素材资源:

just workshop-eval 7

10. 第 8 步:自选冒险 / 高级任务 🏆

现在,您已经掌握了规范的参考架构,接下来请选择毕业任务!

🛡️ 任务 1:零信任 Google IAP (Identity-Aware Proxy)

  • 难度:中等(企业安全)
  • 目标:锁定您的 Cloud Run 应用,使其只能由经过验证的 Google / Gmail 账号访问,从而完全消除密码登录!
  • 运作方式:
    1. 外部 HTTPS 应用负载均衡器会终止 Google OAuth 并验证身份,然后流量才会到达 Cloud Run。
    2. Google 会转发经过验证的身份标头 (X-Goog-Authenticated-User-Email)。
    3. Rails 会通过 blog/app/controllers/concerns/iap_authenticatable.rb 自动登录经过验证的 Google 身份:
      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. 在 iac/iap.tf 中使用 enable_iap = true 启用 Terraform 模块,并指定允许的 Google 账号:
      iap_allowed_users = [var.google_cloud_account]
      

IAP 横幅识别的登录

📊 任务 2:生产 SRE 遥测和 Cloud Logging

  • 难度:中等(可观测性)
  • 目标:将包含跟踪关联 ID 的结构化 JSON 应用日志直接流式传输到 Google Cloud Logging,并使用 Cloud Error Reporting 实时捕获生产环境异常。
  • 运作方式:
    • 配置 blog/config/environments/production.rb 以发出带有 GCP 跟踪标签的结构化 JSON 日志。
    • 触发有意的测试异常,并观看 Google Cloud Error Reporting 如何立即对异常进行分组并通知您。

📸 TODO(riccardo):添加 Google Cloud Error Reporting 的屏幕截图,其中显示了分组的生产环境异常和带有跟踪标签的 Cloud Logging 结构化 JSON 载荷

🧠 任务 3:pgvector 语义搜索和 Gemini RAG

  • 难度:高(生成式 AI 结业项目)
  • 目标:使用存储在 Cloud SQL 上的 PostgreSQL 中的向量嵌入,从概念上搜索文章。
  • 运作方式:
    • 在 Cloud SQL PostgreSQL 实例上启用 vector 扩展程序:
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • 将 neighbor gem 添加到 blog/Gemfile,并在 Post#after_save 上使用 Gemini (text-embedding-004) 生成文本嵌入。
    • 执行余弦距离查询 (<=>),以使用 Turbo Streams 为语义搜索栏提供支持!

📸 TODO(riccardo):添加实时博客语义搜索栏返回概念匹配项和余弦相似度得分的屏幕截图

⚡ 任务 4:SEO 和效果审核助理

  • 难度:简单 / 有趣(开发者体验)
  • 目标:使用 Antigravity 和 Speedgrapher 工具审核 Core Web Vitals、Largest Contentful Paint (LCP) 以及 Flesch-Kincaid / Fog 可读性指数。

📸 TODO(riccardo):添加 Speedgrapher / Lighthouse SEO 和 Core Web Vitals 审核摘要的屏幕截图

🤔 好奇心盒子:在 Cloud Run 上获取实时 Rails 控制台

💡 问题:在传统虚拟机或 Heroku 风格的 PaaS 平台上,开发者通常会通过 SSH 连接到正在运行的实例,或使用 heroku run rails console 实时检查或修复生产数据。Cloud Run 原生等效项是什么?如何强化它?

原因

gcloud run exec

不适用于 Cloud Run 服务

Cloud Run 是一个无服务器平台:您的容器可能以零个、一个或数百个相同的实例运行,没有稳定的身份或可寻址的 shell。这与 Kubernetes (kubectl exec) 从根本上不同,在 Kubernetes 中,特定 pod 具有可附加的命名套接字。

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

四种 GCP 原生模式

模式

命令

互动式?

安全

1. 本地代理 + 远程数据库

DATABASE_URL=... rails console

✅ 完整

🔒 Cloud SQL Auth 代理 mTLS

2. Cloud Run 作业(一次性)

gcloud run jobs execute --wait

❌ 仅限脚本

🔒 需要 IAM 角色

3. Web 控制台 gem + IAP

浏览器 → /rails/console

✅ 完整

🛡️ IAP 零信任

4. Cloud Shell + 代理

Cloud Shell → bin/rails c

✅ 完整

🔒 Google 身份

模式 1(建议用于数据检查):运行本地 Rails 控制台,通过 Cloud SQL Auth 代理直接与 Cloud SQL PostgreSQL 通信:

# 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

您可获得完整的 IRB 交互性,同时实现零网络暴露 - Cloud SQL 代理通过 Unix 域套接字或环回使用 mTLS。

模式 3(Web 控制台 + IAP 零信任,强化浏览器路径):

web-console gem 提供基于浏览器的 IRB 会话。如果没有保护,它将成为严重的安全漏洞,切勿公开暴露。但如果与 Google IAP (Quest 1) 结合使用,攻击面会大幅缩小:只有许可名单中经过验证的 Google 身份才能访问 /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

⚠️ 模式 3 需要先启用 Quest 1 (IAP)。如果没有它,在公开网址上使用 /rails/console 就相当于让服务器机房处于未上锁的状态。借助 IAP,它成为一款强大的操作员工具,并采用 Google 级零信任安全机制。

向 Antigravity 提问: “告诉我如何运行

rails console --sandbox

使用 Cloud SQL Auth 代理安全地针对我的 Cloud SQL 生产数据库运行测试。

🏆 在 The Hive 上毕业:经过监考人员验证的工作量证明

完成所选任务后,在 The Hive 排行榜上领取您的第 8 步毕业奖杯 🏆:

  1. 在 GitHub 上提交您的任务:
  2. 将 Cloud Run 应用与问题相关联:
    • 记下您的支持请求编号(例如 88 或完整网址 https://github.com/palladius/rails8-app-on-gcp/issues/88)。
    • 将 STEP_8_GHI 环境变量注入到 Cloud Run 服务中:
      gcloud run services update blog \
        --region europe-west1 \
        --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER>
      
    • 验证 /status.json 端点现在是否报告您的任务:
      curl -s https://<YOUR_APP>.run.app/status.json | jq .quest
      
      您应该会看到:
      {
        "step_8_completed": true,
        "ghi_issue": 88,
        "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88"
      }
      
  3. 获得监考人员审核并解锁金奖杯:
    • 查看 The Hive 排行榜:您的行现在会显示一个琥珀色徽章:⏳ GHI #XX review pending。
    • 请研讨会监考人员(@palladius、@emilianodellacasa 或 @ricc)检查您的部署。
    • 只要监考人员对您的问题发表评论 LGTM,The Hive 就会自动将您的进度提升到 8/8 🏆,并显示一个金色/紫色发光徽章和一个永久性奖杯,该奖杯可直接链接到您的毕业设计问题!

11. 总结和清理 🎓

恭喜!🎉

您已在 Google Cloud Platform 上构建并部署了生产级、企业就绪型 Rails 8 应用:

  • 零分支进度:通过 main 上的 Time-Machine 叠加层掌握了现代工作流程。
  • Cloud Storage:可扩缩的私密对象存储空间,支持 IAM Blob 签名 (iam: true)。
  • Cloud SQL:通过 Cloud SQL Auth 代理 mTLS 隧道保护的代管 PostgreSQL。
  • Secret Manager:不会泄露任何纯文本凭据或 .env 文件。
  • Cloud Run 多容器:隔离的 Puma Web、Solid Queue 工作器和代理边车。
  • 生成式 AI:使用 Vertex AI 进行异步复古海报生成和 TTS 播客合成。
  • 任务:零信任 IAP、SRE 可观测性和 pgvector 嵌入!

🧹 资源清理

为避免在完成研讨会后持续产生费用,请务必清理您的 Google Cloud 资源!选择最适合您环境的方法:

如果您使用的是现有或共享的 Google Cloud 项目,其中包含其他资源或数据,请使用此选项。它只会有选择性地销毁本次研讨会期间预配的资源(Cloud Run、Cloud SQL、Secret Manager、GCS 存储桶),而不会触及项目的其余部分。

cd iac
terraform destroy -auto-approve

方案 2:彻底删除项目(不留任何痕迹 🌪️)

如果您创建了专用研讨会项目(例如,使用研讨会积分或沙盒),并希望确保不留任何痕迹(包括日志、元数据、服务账号和结算关联),请使用此选项。

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet