Google Cloud 上的 Rails 8:從零開始到 AI

1. 簡介

歡迎參加 Google Cloud 上的 Rails 8 研討會!在本實作程式碼研究室中,您將現代化 Rails 8 應用程式從簡單的本機 SQLite 基準,遷移至 Google Cloud 上可供企業使用的正式級參考架構。

在這場研討會中,我們不只是部署應用程式,而是執行有主見的生產環境等級雲端現代化 (正確的 lift-and-shift!)。

您會從典型的「成果僅存在於特定機器」本機設定 (暫時性磁碟、內嵌 SQLite、處理中作業) 開始,逐步演進為 Google Cloud 上的企業級、萬無一失的雲端架構:

  1. 🐘 從本機 SQLite → Cloud SQL (PostgreSQL):重新啟動時不會再清除資料庫。我們改用透過 Cloud SQL Auth Proxy (mTLS,無公開 IP) 安全連線的代管 PostgreSQL。
  2. 🪣 從暫時性本機儲存空間 → 私人 GCS:無伺服器磁碟是暫時性的。我們使用 IAM 已簽署的網址 (iam: true) 將 ActiveStorage 連線至私人 Google Cloud Storage,因此不會有公開 bucket,也不會洩漏金鑰。
  3. 🚀 從程序內工作 → 多容器 Cloud Run:我們將 Rails 單體式架構和無伺服器雲端服務連結起來!我們使用標準 Docker Compose,在 Cloud Run 多重容器 Pod 中,將 Solid Queue 做為專屬工作站容器與 Puma 一併執行,完全沒有微服務蔓延問題,也沒有執行緒資源不足的問題,DHH 一定會很驕傲!

🍌 我們身處 2026 年,因此透過 Google Antigravity 全面提升整個堆疊的效能。這不是事後才想到的,我們有提示和 技能,可引導您善用這些工具,更順利地執行 (並享受) 這場研討會!

🏛️ 目標參考架構 (變體 1:乾淨的平面向量企業)

以下是您將建構及部署的標準 Google Cloud 參考架構 (變體 1:乾淨的扁平向量企業),展示我們與所有代管 Google Cloud 持續性和 AI 服務連線的生產環境多容器 Cloud Run 服務:

變體 1:乾淨的平面向量企業版 - Google Cloud 架構上的 Rails 8

🎞️ 架構演進:從零到雲端原生

在每個研討會里程碑,觀看我們的堆疊從暫時性的單一機器基準,逐步現代化為全代管的 Google Cloud 藍圖:

研討會:架構演進

課程內容

我們開始吧!

2. 步驟 0:先決條件、Antigravity 設定和帳單驗證

💡 情境:您和團隊正在建構任務關鍵型 Rails 8 應用程式。在觸及程式碼或啟動雲端資源之前,我們必須建立工具鍊、連結 Google Antigravity,並驗證 Google Cloud 憑證和帳單基礎。

1. 必要條件檢查清單

開始前,請確認環境中備妥下列工具:

  • just (1.21 以上): (just --version) 工作執行器 - 本研討會中的每個指令都是just配方 (請 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: (由 blog/.ruby-version 釘選;如果缺少 3.4.5,請要求 AI 測試架構安裝)。ruby -vRails 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 的免費點數用完

⚠️ 您剛兌換的 Cloud 抵免額「不會」為 Antigravity 補充點數。這兩者是不同的帳單平台:Antigravity 的配額位於 Google AI 計畫 (Google One),而優待券和 Cloud 抵免額則位於 Cloud Billing 帳戶。將帳單帳戶連結至 GCP 專案後,您不會獲得額外的 Antigravity 提示。

如果代理程式因配額問題停止運作,請依序嘗試下列方法,前兩種方法免費且即時:

  1. 切換模型集區。配額是依模型系列追蹤,Antigravity 則會顯示兩個獨立的配額集區 (Gemini 模型與 Claude/GPT 模型)。免費方案提供 Claude 和 GPT-OSS 模型,因此用盡 Gemini 額度通常不會影響其他模型。使用提示詞輸入框下方的模型選取器。反重力模型選取器:在 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 是唯一有記錄的方法,可提高電腦版應用程式的基準;這些方案每五小時更新一次,且可將購買的 AI 點數用於超額用量。
  5. 雖然這是 Antigravity 工作坊,但大部分的 AI 程式碼輔助工具應該也適用,只要將工具指向存放區中的 AGENTS.md 和 skills/ 目錄即可。

6. 自動化步驟 0 驗證

確認步驟 0 環境 100% 符合評估套件:

just workshop-eval 0

✨ 令人驚豔的時刻:反重力引擎和工作坊評估引擎會在 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 配方,建立遠端後端狀態 bucket 並觸發基礎架構部署作業:

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 Bucket:使用私有存取權和 IAM 憑證簽署 (iam: true) 建立。
  • Canary 測試圖片:將 gcs_dev_image.jpg 上傳至 bucket,啟用端對端 Blob 驗證。
  • Google Cloud SQL PostgreSQL 執行個體:啟動背景佈建 (~10 到 12 分鐘)。

Google Cloud 控制台中的 Cloud SQL PostgreSQL 執行個體

💡 注意:您可以放心地忽略自動備份遺失的警告,因為我們不需要這些備份來進行本研討會的資料庫 (雖然自動每日備份是任何實際生產資料庫的重要最佳做法!)。

Terraform 套用作業已順利完成,並輸出結果

📝 記下 Cloud Run 網址:Terraform 應會成功套用,並列印基礎架構輸出內容。記下 cloud_run_url (或複製到剪貼簿),稍後驗證即時服務時會用到!

3. 自動化步驟 1 驗證 (選用)

確認步驟 1 的必要條件和設定通過驗證:

just workshop-eval 1

⏱️ 注意:這項評估會檢查即時雲端資源,可能需要幾分鐘才能完成。這完全是選用步驟,在終端機執行這項指令時,您可以立即繼續下一個步驟,不必等待!

4. 步驟 2:本機基準、Mailpit 和管理員新手上路

我們的起點是乾淨的現代化 Rails 8 網誌應用程式,在本機託管上執行,並使用 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 已載入至現用終端機殼層:

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) 和儲存空間設定。
  • 系統會偵測第 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 網頁介面:http://localhost:8025
  • Adminer DB UI: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 網頁 UI 顯示攔截的密碼重設電子郵件 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

just workshop-eval 2 verification output

5. 注意事項:無狀態容器

Cloud Run 容器是無狀態的暫時性容器,如果我們直接將 SQLite 資料庫和本機 storage/ 目錄部署到 Cloud Run,每當容器縮減為零或重新啟動時,所有貼文和上傳的圖片都會永久清除。

在下一個步驟中,我們會刻意將這個暫時性設定部署至 Cloud Run,親身體驗 Stateless Shock!

5. 步驟 3:部署 1 - 無狀態衝擊 (3 分鐘內搶先體驗 WOW!)

不必等待雲端資料庫 15 到 20 分鐘,就能在網路上看到內容。現代無伺服器開發作業一開始就獲得勝利:在 3 分鐘內,將單一容器 Rails 應用程式直接部署到 Google Cloud Run!

1. 時光機回顧

如要確保起始設定 100% 為暫時性 (本機 SQLite 資料庫和本機磁碟檔案儲存空間),請使用 Zero-Branch 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 和非根層級安全防護)。從 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 deploy building and deploying container

⏳ 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,則建構作業可能在新的權限傳播完成前就已開始,gcloud 本身的提示會警告「這需要幾分鐘」。稍待片刻,然後重新執行完全相同的指令,通常就能成功。步驟 1 的 terraform apply 通常會為您啟用這個 API,因此您應該不會看到提示。
  2. 預設 Compute 服務帳戶確實缺少角色。新版 GCP 專案會強制執行最低權限,導致來源部署作業中斷。修正方法:
    just project-status   # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builder
    
    just workshop-test 現在也會回報這項資訊,因此如果部署作業持續失敗,建議重新執行。

部署期間:

  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) 登入。如果找不到登入按鈕,請按一下「New Post」,系統會先提示輸入使用者名稱和密碼:Rails 8 登入頁面,顯示臨時遙測資訊橫幅
  3. 按一下「New Post」(新文章),撰寫名為「My First Cloud Run Post」(我的第一篇 Cloud Run 文章) 的文章,附上圖片,然後按一下「Create Post」(建立文章)。
  4. 貼文會以完整格式發布,圖片也會轉譯。
  5. 請注意視覺遙測:
    • 標頭徽章:[EPHEMERAL DB / STORAGE] 💾 Local
    • 圖片浮水印:🏠 戳記 (右下角的 127.0.0.1 暫時性磁碟徽章)。

🐝 加入 Live Workshop Hive 排行榜!如果你在線上,監考人員正在顯示排行榜,且你想加入排行榜,請在此新增 Cloud Run 網址:👉 在排行榜上註冊 Cloud Run

Google Cloud Run 控制台網誌服務修訂版本詳細資料

4. 💥 注意事項:無狀態震盪和「Puma 解決方法」陷阱

Cloud Run 是無狀態的無伺服器平台,當網路流量降至零時,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 中執行背景工作者會耗用寶貴的網頁執行緒 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 憑證 API (iam: true) 使用短期簽署網址,將媒體和檔案儲存空間與容器磁碟分離。

在進行任何設定前,請先瞭解 Rails 與 GCS 通訊時需要的三個項目:

① 專案 ID + Bucket 名稱 - Rails 必須知道要將 Blob 儲存在何處。應用程式會從環境讀取 GOOGLE_CLOUD_PROJECT,並將值區名稱衍生為 ${GOOGLE_CLOUD_PROJECT}-activestorage-prod。步驟 1 的 Terraform 已建立這個 bucket,因此不需要額外作業。

② 具備適當權限的服務帳戶:Cloud Run 會以 Compute Engine 預設服務帳戶 (PROJECT_NUMBER-compute@developer.gserviceaccount.com) 執行。這個服務帳戶需要具備儲存空間的兩項 IAM 角色:

  • roles/storage.objectAdmin - 上傳及提供 blob
  • roles/iam.serviceAccountTokenCreator 本身,用於呼叫 IAM 憑證 signBlob API 並產生短期簽署網址

⚠️ 誘人的捷徑 -

public: true 如要讓 GCS 運作,最快的方法是在 storage.yml 中設定 public: true,並在 bucket 上授予 allUsers:objectViewer。圖片會立即載入,不需登入帳戶。請勿這麼做。公開 bucket 會將所有上傳的使用者媒體洩漏到開放網路,而且永遠不會停止。我們的藍圖使用 iam: true 將儲存空間完全設為私有,每個圖片網址都是由 IAM 憑證 API 即時產生的簽署權杖,並設有到期時間。

③

ACTIVE_STORAGE_SERVICE=google:這個單一環境變數會在執行階段將 Rails 從本機磁碟切換至 GCS。不必變更程式碼,也不必重新部署應用程式邏輯,只要切換一個環境變數即可。

1. 時光機回顧

將時光機推進至第 2 階段:

just workshop-rewind 2

檢查 blog/config/storage.yml,您會看到應用程式定義了三個具名的 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 Storage 和簽署權限

請確認 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. 第三次部署至 Cloud Run,並附加 GCS

重新部署並啟用 GCS。請確認您位於 blog/ 資料夾內 (否則 Cloud Run 會嘗試使用 Buildpacks 而非 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. 建立名為「Surviving the Cloud」的新網誌文章,並上傳相片。
  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 控制台的 bucket 詳細資料,顯示永久圖片 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 中妥善解決這個問題,屆時我們會升級至正式版的多容器 Sidecar 架構,並使用專屬的 worker 容器獨立執行 Solid Queue,不受網路流量影響!

6. 自動化步驟 4 驗證

驗證 GCS 設定和卡住的工作遙測:

just workshop-eval 4

7. 步驟 5:飛行前檢查清單:Cloud SQL、祕密和資料庫連線

您在步驟 3 和 4 中掌握了無狀態容器和 Google Cloud Storage,而 Terraform 已在背景中完成實際工作環境後端的烹調作業!

部署完整的 3 個容器生產環境輔助架構 (步驟 6 中的「部署 4」) 之前,這個步驟會做為快速的飛行前檢查,驗證資料庫健康狀態、連線能力和密鑰存取權。

0. 啟動環境變數

直接從 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. Cloud SQL 健康檢查

確認代管 PostgreSQL 執行個體已完成初始佈建,且運作正常:

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

輸出內容必須是 RUNNABLE。

2. (選用) 從筆記型電腦測試資料庫連線

在實際運作環境中,Cloud Run 容器會透過加密的 Sidecar Proxy (127.0.0.1:5432) 安全連線至 Cloud SQL,不會將資料庫暴露在公開網路上 (0.0.0.0/0)。

ℹ️ 附註:這個步驟完全是選用步驟!在 Cloud Run 中,官方 Cloud SQL Auth Proxy 邊車容器會自動處理 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 佈建的密鑰

切勿在 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 Manager 中存在且有效:

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

Google Cloud Secret Manager 控制台列出已設定的應用程式密鑰

🔑 將「master.key」同步到筆電 (本機同步)

由於 blog/config/master.key 會因安全考量而遭 Git 忽略,因此本機複製版本可能還沒有這個檔案。執行這個友善的自我修復程式碼片段,從 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

🧪 Playground:想建立自訂密鑰,瞭解有多簡單嗎?Secret Manager 不只適用於 Rails!您可以使用簡單的 CLI 指令建立任何密鑰:

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

4. 驗證密鑰存取者權限

如要讓 Cloud Run 容器在啟動時將這些密鑰掛接為環境變數或磁碟區掛接點,執行階段服務帳戶 ($RUN_SA) 必須具備每個密鑰的 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 Proxy 安全性比較

5. 自動化預檢驗證

執行自動化步驟 5 測試套件,確認資料庫可執行、密鑰不為空白,且 IAM 權限為綠色:

just workshop-eval 5

8. 步驟 6:部署 4 - 企業多容器 Sidecar (黃金標準)

現在,我們來組建最終的參考架構:多容器 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 Proxy Sidecar (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2)。

3. 透過 Cloud Run 工作執行資料庫遷移作業

將網路流量導向新部署作業前,我們必須對 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 Socket Proxy (/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 個資料庫 (主要、佇列、快取、連線)。所有 4 個 DATABASE_*_URL 環境變數都必須指向同一個 Cloud SQL 執行個體,否則系統不會建立 Solid Queue/Cache/Cable 資料表。

在 db:prepare 之後,載入 Solid Queue/Cache/Cable 結構定義:

# Schema load for queue, cache, and cable databases (creates solid_queue_jobs, etc.)
gcloud run jobs update rails-migrate \
  --command "bash" \
  --args "-c,DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bin/rails db:schema:load:queue db:schema:load:cache db:schema:load:cable" \
  --region $GOOGLE_CLOUD_REGION

gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait

gcloud run jobs executions describe JOBNAME 的輸出內容上圖顯示

gcloud run jobs executions describe $JOBNAME

Google Cloud Run Jobs 控制台顯示 rails-migrate 工作「成功」Google Cloud Run Jobs 控制台顯示

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 控制台的「Containers」分頁,顯示 3 個 Sidecar 容器Google Cloud Run 控制台:「Containers」(容器) 分頁,顯示所有三個 Sidecar 容器 (

cloudsql-proxy

、

web

、

worker

)

💡 使用者介面好奇心 / 控制台故障:在控制台下拉式選單中,您可能會發現 Cloud Run 將頂端項目標示為 Ingress container: cloudsql-proxy。別擔心!這是 Google Cloud 控制台的純視覺化 UI 故障 (追蹤記錄為 #149),是由容器依附元件排序所致。事實上,web 是唯一監聽 HTTP 通訊埠 8080 的容器,負責處理 100% 的傳入網路流量,而 cloudsql-proxy 則嚴格處理通訊埠 5432 上的私人 PostgreSQL 流量!

5. ✨ 令人驚豔的時刻:零警告和完整的雲端持續性!

深吸一口氣,然後看看螢幕:所有警示橫幅都消失了!🪄

Cloud Run 零警告和 Cloud 持久性貼文的製作網誌Rails 8 正式版網誌:提供乾淨的 UI、零警告橫幅,以及持續的 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 管道、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 的多模態模型 (Vertex AI 上的 Gemini 2.5 Flash Image / Imagen) 生成 1960 年代義大利電影海報 (「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 (網頁介面):按一下「New post」,輸入標題 (例如「Serverless Architecture with 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 工作人員記錄。復古海報就會直接顯示在貼文中!

📸 TODO(riccardo):新增網誌文章的螢幕截圖,當中包含 AI 生成的 1960 年代義大利電影海報,海報上出現香蕉和紅寶石 8 的客串畫面

2. 雙語 Podcast 製作工具任務 (反重力程式設計實作練習)

⚠️ 重要通知:與上述封面產生器不同,這項功能尚未實裝!

開啟 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:「Help me implement PodcastifierJob in app/jobs/podcastifier_job.rb. It should fetch the Post, translate or synthesize an Italian audio overview using Google Cloud Text-to-Speech (voice ‘it-IT-Wavenet-A' with language code ‘it-IT' via ADC credentials), and attach the audio file to post.podcast_audio_it using ActiveStorage!"
  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 Proxy:

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 結業專案)
  • 目標:使用儲存在 PostgreSQL 適用的 Cloud SQL 中的向量嵌入,從概念上搜尋文章。
  • 運作方式:
    • 在 Cloud SQL PostgreSQL 執行個體上啟用 vector 擴充功能:
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • 將 neighbor gem 新增至 blog/Gemfile,並使用 Gemini (text-embedding-004) 在 Post#after_save 上生成文字嵌入。
    • 執行餘弦距離查詢 (<=>),透過 Turbo Streams 支援語意搜尋列!

📸 TODO(riccardo):新增即時網誌語意搜尋列的螢幕截圖,顯示概念相符的結果和餘弦相似度分數

⚡ 任務 4:SEO 和成效稽核助理

  • 難度:簡單 / 有趣 (開發人員體驗)
  • 目標:使用 Antigravity 和 Speedgrapher 工具稽核 Core Web Vitals、Largest Contentful Paint (LCP) 和 Flesch-Kincaid / Fog 可讀性指數。

📸 TODO(riccardo):新增 Speedgrapher / Lighthouse 搜尋引擎最佳化和 Core Web Vitals 稽核摘要的螢幕截圖

🤔 Curiosity Box:在 Cloud Run 上取得 Live Rails 控制台

💡 問題:在傳統 VM 或 Heroku 樣式的 PaaS 平台上,開發人員通常會透過 SSH 連線至執行中的執行個體,或使用 heroku run rails console 即時檢查或修正正式環境資料。Cloud Run 原生對應項目為何?如何強化安全性?

建議原因

gcloud run exec

不適用於 Cloud Run 服務

Cloud Run 是無伺服器平台:您的容器可能會以零個、一個或數百個相同執行個體的形式執行,且沒有穩定的身分或可定址的殼層。這與 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. 本機 Proxy + 遠端資料庫

DATABASE_URL=... rails console

✅ 完整

🔒 Cloud SQL Auth Proxy mTLS

2. Cloud Run 工作 (一次性)

gcloud run jobs execute --wait

❌ 僅限指令碼

🔒 需要 IAM 角色

3. Web Console Gem + IAP

瀏覽器 → /rails/console

✅ 完整

🛡️ IAP 零信任

4. Cloud Shell + Proxy

Cloud Shell → bin/rails c

✅ 完整

🔒 Google 身分

模式 1 (建議用於檢查資料):執行本機 Rails 控制台,透過 Cloud SQL 驗證 Proxy 直接與 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 Proxy 會透過 Unix 網域通訊端或迴路介面使用 mTLS。

模式 3 (網頁控制台 + 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 必須先啟用任務 1 (應用程式內購)。如果沒有,在公開網址上使用 /rails/console 就等同於讓伺服器機房門戶洞開。透過 IAP,這項服務會成為強大的運算子工具,並採用 Google 級別的零信任安全機制。

詢問 Antigravity: 「請說明如何執行

rails console --sandbox

使用 Cloud SQL Auth Proxy,安全地對 Cloud SQL 生產環境資料庫執行查詢。

🏆 畢業於 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. 通過監考人員審查並解鎖金盃:
    • 查看蜂巢排行榜:你的資料列現在會顯示琥珀色徽章:⏳ 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 Proxy mTLS 管道保護代管 PostgreSQL。
  • Secret Manager:完全不會洩漏純文字憑證或 .env 檔案。
  • Cloud Run 多重容器:隔離的 Puma 網頁、Solid Queue 工作站和 Proxy Sidecar。
  • 生成式 AI:使用 Vertex AI 製作復古海報和 TTS 播客。
  • 任務:零信任 IAP、SRE 可觀測性,以及 pgvector 嵌入項目!

🧹 清除資源

如要避免在完成研討會後持續產生費用,請務必清理 Google Cloud 資源!請選擇最適合您環境的方法:

如果您使用現有或共用的 Google Cloud 雲端專案,其中有其他資源或資料,請選取這個選項。這項指令只會清除研討會期間佈建的資源 (Cloud Run、Cloud SQL、Secret Manager、GCS bucket),專案中的其他資源則不受影響。

cd iac
terraform destroy -auto-approve

選項 2:徹底刪除專案 (不留任何痕跡 🌪️)

如果您已建立專屬研討會專案 (例如使用研討會抵免額或沙箱),並想確保完全清除所有內容 (包括記錄、中繼資料、服務帳戶和帳單連結),請使用這個選項。

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet