Rails 8 trên Google Cloud: Từ con số 0 đến AI

1. Giới thiệu

Chào mừng bạn đến với hội thảo Rails 8 trên Google Cloud! Trong lớp học lập trình thực hành này, bạn sẽ chuyển một ứng dụng Rails 8 hiện đại từ một đường cơ sở SQLite cục bộ đơn giản sang một kiến trúc tham chiếu cấp sản xuất, sẵn sàng cho doanh nghiệp trên Google Cloud.

Trong hội thảo này, chúng ta không chỉ triển khai một ứng dụng mà còn thực hiện một quy trình hiện đại hoá đám mây theo ý kiến riêng và ở cấp độ sản xuất (nâng cấp và di chuyển đúng cách!).

Bạn bắt đầu từ chế độ thiết lập cục bộ "hoạt động trên máy của tôi" điển hình (ổ đĩa tạm thời, SQLite nhúng, các công việc trong quy trình) và dần dần phát triển thành một kiến trúc đám mây doanh nghiệp, không thể xâm nhập trên Google Cloud:

  1. 🐘 Từ SQLite cục bộ → Cloud SQL (PostgreSQL): Không còn tình trạng DB bị xoá khi khởi động lại. Chúng tôi chuyển sang PostgreSQL được quản lý, kết nối an toàn thông qua Cloud SQL Auth Proxy (mTLS, không có IP công khai).
  2. 🪣 Từ Bộ nhớ cục bộ tạm thời → GCS riêng tư: Ổ đĩa không máy chủ là ổ đĩa tạm thời. Chúng tôi kết nối ActiveStorage với Google Cloud Storage riêng tư bằng cách sử dụng URL đã ký IAM (iam: true) – không có bộ chứa công khai, không có khoá bị rò rỉ.
  3. 🚀 Từ các Tác vụ trong quy trình → Cloud Run có nhiều vùng chứa: Chúng tôi kết nối monolith Rails và đám mây không máy chủ! Bằng cách sử dụng Docker Compose tiêu chuẩn, chúng tôi chạy Solid Queue dưới dạng một vùng chứa worker chuyên dụng cùng với Puma trong vùng chứa đa vùng chứa Cloud Run – không có sự lan tràn của vi dịch vụ, không có tình trạng thiếu hụt luồng, DHH sẽ rất tự hào!

🍌 Và vì chúng ta đang sống ở năm 2026, nên chúng tôi sẽ tăng cường toàn bộ ngăn xếp bằng Google Antigravity. Đây không phải là một ý tưởng muộn, chúng tôi có các câu lệnh và kỹ năng để hướng dẫn bạn khai thác sức mạnh của AI nhằm thực hiện (và tận hưởng) hội thảo này một cách hiệu quả hơn!

🏛️ Kiến trúc tham chiếu mục tiêu (Biến thể 1: Doanh nghiệp vectơ phẳng đơn giản)

Sau đây là kiến trúc tham chiếu chuẩn của Google Cloud mà bạn sẽ xây dựng và triển khai (Biến thể 1: Doanh nghiệp vectơ phẳng thuần tuý), minh hoạ dịch vụ Cloud Run đa vùng chứa sản xuất của chúng tôi được kết nối với tất cả các dịch vụ AI và dịch vụ duy trì dữ liệu được quản lý của Google Cloud:

Biến thể 1: Clean Flat Vector Enterprise — Rails 8 trên Kiến trúc Google Cloud

🎞️ Sự phát triển của kiến trúc: Từ không có gì đến kiến trúc thuần đám mây

Theo dõi quá trình hiện đại hoá từng bước của ngăn xếp từ một đường cơ sở tạm thời trên một máy duy nhất đến một bản thiết kế hoàn toàn do Google Cloud quản lý trong từng cột mốc của hội thảo:

Hội thảo về sự phát triển của kiến trúc

Kiến thức bạn sẽ học được

  • Cách lập trình theo cặp với Google Antigravity để làm rõ Rails 8 và Google Cloud.
  • Cách chạy quy trình chẩn đoán tự động trước khi bay (just workshop-test).
  • Cách cung cấp cơ sở hạ tầng Google Cloud không đồng bộ bằng Terraform trong khi tiếp tục phát triển cục bộ mà không bị chặn bằng trình cung cấp google.
  • Cách loại bỏ các mẫu chống bảo mật: các vùng chứa GCS riêng tư (iam: true) và Cloud SQL Auth Proxy các đường hầm mTLS thay vì mở 0.0.0.0/0.
  • Cách chèn trực tiếp các dữ liệu bí mật từ Google Cloud Secret Manager.
  • Cách điều phối các tác vụ nền AI tạo sinh không đồng bộ (trình tạo ảnh bìa NanoBanana, tổng hợp podcast song ngữ) thông qua Solid Queue.

Hãy bắt đầu!

2. Bước 0: Điều kiện tiên quyết, thiết lập Antigravity và xác minh thông tin thanh toán

💡 Tình huống: Bạn và nhóm của bạn đang xây dựng một ứng dụng Rails 8 quan trọng. Trước khi chạm vào mã hoặc khởi chạy tài nguyên đám mây, chúng ta phải thiết lập chuỗi công cụ, kết nối Google Antigravity và xác minh thông tin đăng nhập Google Cloud cũng như nền tảng thanh toán của mình.

1. Danh sách kiểm tra điều kiện tiên quyết

Trước khi bắt đầu, hãy đảm bảo bạn có sẵn các công cụ sau trong môi trường của mình:

  • just (1.21+): (just --version) trình chạy tác vụ – mọi lệnh trong hội thảo này đều là một just công thức (yêu cầu AI Harness cài đặt hoặc xem skills/rails8app-workshop).
  • Git (2.30+): (git --version) để quản lý phiên bản, phân nhánh và sao chép kho lưu trữ.
  • Google Cloud SDK (gcloud CLI): Đã cài đặt và cập nhật.
  • CLI Terraform (1.5+): Để cung cấp cơ sở hạ tầng khai báo.
  • docker và docker-compose: Đã cài đặt và chạy cục bộ.
  • Ruby 3.4.5: (ruby -v – được blog/.ruby-version ghim; yêu cầu AI harness cài đặt 3.4.5 nếu thiếu). Rails 8 được khai báo trong Gemfile và tự động được bundle install cài đặt.
  • Google Antigravity 2.0: Trợ lý lập trình theo cặp bằng AI tự động (Tải Google Antigravity 2.0 xuống). Bạn sẽ đăng nhập bằng tài khoản Gmail cá nhân mà không cần mã thông báo API.

2. Xác thực Google Cloud, Cấu hình chuyên dụng và ADC

Để tránh xung đột với các chế độ thiết lập gcloud hiện có của công ty, cá nhân hoặc nhiều tài khoản, bạn nên tạo một cấu hình chuyên dụng có tên cho hội thảo này:

# 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

💡 Tại sao nên dùng Cấu hình chuyên dụng? Việc sử dụng gcloud config configurations create rails8-on-gcp-workshop sẽ tách biệt tất cả các chế độ cài đặt CLI (tài khoản, hạn mức dự án, khu vực mặc định) dành riêng cho hội thảo này. Khi hoàn tất, bạn có thể chuyển về chế độ thiết lập thông thường bất cứ lúc nào bằng cách nhấn vào biểu tượng gcloud config configurations activate default.

Cấu hình gcloud đang hoạt động

3. 🚨 Xác minh thông tin thanh toán trên Google Cloud Platform (GCP)

⚠️ Lưu ý: Các hoạt động triển khai Google Cloud SQL và Cloud Run yêu cầu phải có tài khoản thanh toán được liên kết đang hoạt động hoặc tín dụng giáo dục hợp lệ cho hội thảo. Hãy đảm bảo bạn đã thiết lập đúng cách. Để thực hiện việc này, hãy chạy quy trình kiểm tra xác minh thông tin thanh toán:

gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT

Đảm bảo billingEnabled: true được trả về. Bạn cũng có thể liệt kê tất cả tài khoản thanh toán của mình:

gcloud billing accounts list

Đảm bảo rằng ít nhất một trong số đó có biểu tượng OPEN: True. Nếu không có tài khoản nào đang hoạt động, hãy liên kết một tài khoản thanh toán hoặc sử dụng phiếu giảm giá tín dụng cho hội thảo của bạn trong Trang thanh toán của Bảng điều khiển Google Cloud.

Tài khoản thanh toán đang hoạt động trên GCP

4. Sao chép Kho lưu trữ và ghép nối với Antigravity

Sao chép kho lưu trữ rồi nhập thư mục. Xin lưu ý rằng chúng ta hoàn toàn ở trên main:

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

Mở thư mục này trong Google Antigravity. Antigravity sẽ tự động kiểm tra kho lưu trữ, đọc AGENTS.md và sẵn sàng hỗ trợ bạn với vai trò là lập trình viên ghép cặp.

5. 🪫 Nếu Antigravity hết tín dụng miễn phí

⚠️ Khoản tín dụng Cloud mà bạn vừa đổi KHÔNG được nạp lại vào Antigravity. Đây là hai nền tảng thanh toán riêng biệt: hạn mức của Antigravity nằm trong các gói AI của Google (Google One), trong khi phiếu giảm giá và tín dụng Cloud nằm trong tài khoản thanh toán trên Cloud. Việc liên kết tài khoản thanh toán với dự án GCP sẽ không giúp bạn nhận được thêm một câu lệnh Antigravity.

Nếu tác nhân dừng hoạt động và hiển thị thông báo về hạn mức, hãy thực hiện theo các bước sau. Hai bước đầu tiên là miễn phí và diễn ra ngay lập tức:

  1. Chuyển đổi nhóm mô hình. Hạn mức được theo dõi theo từng nhóm mô hình và Antigravity cho thấy 2 nhóm độc lập (các mô hình Gemini so với các mô hình Claude/GPT). Các mô hình Claude và GPT-OSS có trong gói miễn phí, vì vậy, khi một nhóm Gemini hết hạn mức, nhóm còn lại thường không bị ảnh hưởng. Sử dụng bộ chọn mô hình bên dưới ô nhập câu lệnh.Bộ chọn mô hình Antigravity – chuyển đổi giữa các nhóm Gemini, Claude và GPT-OSS
  2. Chọn mô hình Flash thay vì Pro. Giới hạn tốc độ tương quan với lượng công việc mà tác nhân thực hiện cho mỗi yêu cầu, vì vậy, một mô hình nhẹ hơn sẽ kéo dài những gì còn lại.
  3. Sử dụng Antigravity CLI bằng khoá Gemini API của riêng bạn. Không được hỗ trợ tính năng mang khoá riêng trong Antigravity IDE – CLI là giải pháp thay thế được ghi lại. Tạo khoá miễn phí trong Google AI Studio (cần có dự án nhưng không cần tài khoản thanh toán), sau đó tải xuống và định cấu hình Antigravity CLI.
  4. Chờ hoặc nâng cấp. Hạn mức của cấp miễn phí được làm mới hằng tuần (thông báo lỗi cho biết ngày đặt lại của bạn). Google AI Pro/Ultra là cách duy nhất được ghi nhận để tăng mức sử dụng cơ bản trong ứng dụng dành cho máy tính; các gói này được làm mới sau mỗi 5 giờ và có thể sử dụng tín dụng AI đã mua cho phần vượt hạn mức.
  5. Mặc dù đây là hội thảo Antigravity, nhưng hầu hết các bộ công cụ lập trình AI cũng sẽ hoạt động. Bạn chỉ cần trỏ bộ công cụ của mình đến thư mục AGENTS.md và skills/ trong kho lưu trữ.

6. Xác thực tự động bước 0

Xác minh rằng môi trường Bước 0 của bạn tuân thủ 100% bộ đánh giá:

just workshop-eval 0

✨ Khoảnh khắc bất ngờ: Antigravity và công cụ đánh giá hội thảo xác minh các phiên bản CLI, môi trường Ruby và hoạt động xác thực ADC của bạn trong vòng chưa đầy 2 giây!

3. Bước 1: Bắt đầu cơ sở hạ tầng Terraform và chẩn đoán trước khi triển khai

💡 Chiến lược: Các cơ sở dữ liệu được quản lý như Google Cloud SQL PostgreSQL mất khoảng 10 đến 12 phút để cung cấp. Thay vì chờ đợi sau này, chúng tôi sẽ ra mắt cơ sở hạ tầng bất biến thông qua Terraform ngay bây giờ ở chế độ nền trong khi phát triển cục bộ!

1. Chạy bộ công cụ chẩn đoán tự động trước khi phát hành

Trước khi ra mắt cơ sở hạ tầng đám mây, hãy chạy bộ kiểm thử toàn diện trước khi phát hành:

just workshop-test

Tập lệnh này (bin/workshop_diagnostics.rb):

  • Xác minh cấu hình danh tính GOOGLE_CLOUD_ACCOUNT của bạn.
  • Xác minh trạng thái thanh toán đang hoạt động và mối liên kết dự án.
  • Xác thực Thông tin xác thực mặc định của ứng dụng (ADC) cho Vertex AI.
  • Xác nhận hình ảnh canary seed ActiveStorage (blog/app/assets/images/gcs_dev_image.jpg).

chỉ đầu ra chẩn đoán kiểm thử tại xưởng

Nếu thiếu .env, hãy sao chép từ mẫu được ghi lại:

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

💡 Lưu ý: Nếu just workshop-test báo cáo rằng API thanh toán bị tắt trong dự án của bạn, hãy nhanh chóng bật API này thông qua:

gcloud services enable cloudbilling.googleapis.com

2. ⏱️ Chạy cơ sở hạ tầng Terraform không đồng bộ

Chạy công thức Terraform một bước để tạo vùng chứa trạng thái phụ trợ từ xa và kích hoạt quá trình triển khai cơ sở hạ tầng:

just terraform-apply

Đã khởi động Terraform thành công

Phương án thay thế thủ công (hoặc chạy trực tiếp `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 ..

Những gì Terraform cung cấp:

  • Bộ chứa Google Cloud Storage: Được tạo bằng quyền truy cập riêng tư và tính năng ký thông tin xác thực IAM (iam: true).
  • Hình ảnh kiểm thử Canary: Tải gcs_dev_image.jpg lên vùng lưu trữ để bật tính năng xác minh blob toàn diện.
  • Phiên bản Google Cloud SQL PostgreSQL: Bắt đầu cung cấp ở chế độ nền (khoảng 10-12 phút).

Phiên bản Cloud SQL PostgreSQL trong Google Cloud Console

💡 Lưu ý: Bạn có thể bỏ qua cảnh báo về việc thiếu tính năng sao lưu tự động mà không gặp vấn đề gì. Chúng ta sẽ không cần tính năng này cho cơ sở dữ liệu của hội thảo (mặc dù việc sao lưu tự động hằng ngày là một phương pháp hay thiết yếu cho mọi cơ sở dữ liệu sản xuất thực tế!).

Đã hoàn tất thành công việc áp dụng Terraform với các đầu ra

📝 Ghi lại URL Cloud Run: Lệnh Terraform apply sẽ thành công và in ra các kết quả đầu ra của cơ sở hạ tầng. Ghi lại cloud_run_url của bạn (hoặc sao chép vào bảng nhớ tạm) – bạn sẽ cần mã này ở các bước sau khi xác minh dịch vụ phát trực tiếp!

3. Xác thực tự động bước 1 (Không bắt buộc)

Xác minh rằng các điều kiện tiên quyết và cấu hình ở Bước 1 của bạn đã được đáp ứng:

just workshop-eval 1

⏱️ Lưu ý: Quá trình đánh giá này kiểm tra các tài nguyên đám mây đang hoạt động và có thể mất vài phút để hoàn tất. Đây là bước hoàn toàn không bắt buộc. Trong khi bước này chạy trong thiết bị đầu cuối, bạn có thể chuyển ngay sang bước tiếp theo mà không cần chờ đợi!

4. Bước 2: Đường cơ sở cục bộ, Mailpit và quy trình làm quen dành cho quản trị viên

Điểm xuất phát của chúng ta là một ứng dụng blog Rails 8 hiện đại, gọn gàng chạy trên localhost với SQLite, tính năng chặn email Mailpit và ActiveStorage dựa trên đĩa.

1. Khởi động ứng dụng cục bộ

📁 Hãy chú ý đến thư mục! Kho lưu trữ này chứa ứng dụng Rails trong blog/ và Terraform trong iac/. Bước 1 đưa bạn đến thư mục gốc của kho lưu trữ, vì vậy mọi lệnh trong Bước 2 đều chạy từ

blog/ – Gemfile, bin/rails và compose.yaml đều sống ở đó. Việc chạy các tệp này từ thư mục gốc sẽ không thành công với no configuration file provided: not found hoặc thiếu Gemfile.

Di chuyển vào ứng dụng Rails, xác nhận rằng bạn đang dùng Ruby được ghim, sau đó cài đặt các gem và thiết lập cơ sở dữ liệu:

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

🧯 Lỗi về quyền khi ghi vào

/var/lib/gems/

hoặc

ruby -v

cho thấy Ruby hệ thống (ví dụ: 3.3.x)? Hệ thống Vanilla Linux Ruby hoàn toàn bỏ qua các tệp .ruby-version và cố gắng cài đặt các gem trên toàn hệ thống! Đảm bảo rằng rbenv được tải trong trình bao của thiết bị đầu cuối đang hoạt động:

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

Lưu ý: Nếu bạn bỏ qua bước cài đặt 3.4.5 ở Bước 0, hãy chạy rbenv install 3.4.5. Hoặc chỉ cần sử dụng Chế độ A (Docker Compose) bên dưới (just compose-up), chế độ này sẽ chạy ngăn xếp Ruby được ghim hoàn toàn bên trong các vùng chứa mà không ảnh hưởng đến các gem của máy chủ lưu trữ!

💡 Bạn không muốn nghĩ về điều đó? just install và just compose-up từ thư mục gốc của kho lưu trữ sẽ thực hiện cd blog cho bạn.

2. 🌱 Tính năng tự động khám phá Smart Seed và khởi động quản trị (Vấn đề số 21 và 25)

Cơ sở dữ liệu ban đầu (db/seeds.rb) có tính năng Tự động phát hiện môi trường thông minh:

  • Thao tác này kiểm tra bộ chuyển đổi cơ sở dữ liệu đang hoạt động (SQLite so với Postgres) và cấu hình bộ nhớ của bạn.
  • Nó phát hiện Giai đoạn 0 (Localhost) và tự động tạo người dùng quản trị ban đầu cũng như gieo mầm bài đăng sư phạm:
    • [LOCAL BASELINE] Welcome to Rails 8 on Localhost!
    • Tệp đính kèm hình ảnh buồn cục bộ (local_sad_image.png) có sẵn kèm hình mờ cho bạn biết rằng bộ nhớ ổ đĩa cục bộ chỉ là tạm thời.
  • Nó tự động kích hoạt email đặt lại mật khẩu thông qua ActionMailer.

Xác minh hoặc chạy lại hạt giống (tự động sử dụng GOOGLE_CLOUD_ACCOUNT của bạn từ .env):

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

Khởi động ngăn xếp phát triển cục bộ thông qua Docker Compose:

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

Docker Compose khởi động Rails, Mailpit và ngăn xếp cục bộ

Giờ đây, ứng dụng, máy chủ SMTP Mailpit cục bộ và trình xem cơ sở dữ liệu Adminer đang chạy cùng nhau trong các vùng chứa riêng biệt:

  • Ứng dụng Rails: http://localhost:3000
  • Giao diện người dùng web Mailpit: http://localhost:8025
  • Giao diện người dùng cơ sở dữ liệu Adminer: http://localhost:8081

🧯 Xung đột cổng 3000 hoặc

server.pid

lỗi? Nếu trước đó bạn đã khởi động một máy chủ phát triển gốc (bin/dev), hãy dừng máy chủ đó bằng Ctrl+C. Nếu Docker báo lỗi rằng cổng 3000 đã được phân bổ hoặc tìm thấy server.pid còn sót lại, hãy dọn dẹp bằng cách:

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

3. Trải nghiệm Mailpit và Đăng nhập bằng quyền quản trị

  1. Bắt email gửi đi bằng Mailpit: Mở http://localhost:8025 trong trình duyệt. Hãy xem hộp thư đến tại địa phương! Trong db:seed, ActionMailer đã gửi một email giới thiệu cho quản trị viên mà Mailpit đã ghi lại một cách an toàn trên thiết bị mà không cần truy cập vào các máy chủ email hoặc thông tin đăng nhập bên ngoài:

Giao diện người dùng web của Mailpit hiển thị email đặt lại mật khẩu bị chặn 2. Đăng nhập vào Blog: Mở http://localhost:3000 trong trình duyệt.

  • Bạn có thể nhấp vào đường liên kết đặt lại mật khẩu ngay trong email Mailpit để đặt mật khẩu.
  • Ngoài ra, hãy đăng nhập bằng email của tài khoản Google Cloud (từ .env) và mật khẩu mặc định được tạo sẵn: Ch4ng3m3!!1.
  1. Quan sát Huy hiệu đo từ xa trực quan:
    • Lưu ý biểu ngữ môi trường màu vàng và huy hiệu trong giao diện người dùng: Notice: Ephemeral Database Active (POSTGRESQL) và [EPHEMERAL DB / STORAGE] 💾 Local.
    • Lưu ý dấu bản quyền trên bài đăng: Dấu 🏠 (nanobanana_stamp_local.png ở góc dưới cùng bên phải của ảnh bìa). Điều này giúp bạn xác nhận ngay bằng hình ảnh rằng các tài sản và cơ sở dữ liệu của bạn hiện đang được liên kết với bộ nhớ cục bộ tạm thời.

Trang chủ của blog cục bộ hiển thị huy hiệu và thông báo đo từ xa tạm thời

💡 Mẹo chuyên nghiệp không bắt buộc: Bài tập về Rails Console Bạn có tò mò về cách Rails tương tác với dữ liệu được gieo từ CLI không? Chuyển đến bảng điều khiển tương tác:

bin/rails console

Kiểm tra người dùng quản trị được gieo hạt một cách linh hoạt bằng cách sử dụng biến môi trường:

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. Xác thực tự động ở bước 2

Xác minh đường cơ sở cục bộ và chế độ thiết lập quản trị viên:

just workshop-eval 2

just workshop-eval 2 verification output

5. Điểm cần lưu ý: Vùng chứa không trạng thái

Vùng chứa Cloud Run là vùng chứa không trạng thái và tạm thời. Nếu chúng ta triển khai cơ sở dữ liệu SQLite và thư mục storage/ cục bộ trực tiếp vào Cloud Run, thì tất cả bài đăng và hình ảnh đã tải lên sẽ bị xoá vĩnh viễn bất cứ khi nào một vùng chứa mở rộng đến 0 hoặc khởi động lại.

Trong bước tiếp theo, chúng ta sẽ cố tình triển khai cấu hình tạm thời này vào Cloud Run để tận mắt chứng kiến hiện tượng Stateless Shock!

5. Bước 3: Triển khai 1 – The Stateless Shock (WOW ban đầu trong 3 phút!)

Thay vì chờ 15 – 20 phút để cơ sở dữ liệu đám mây hoạt động trước khi thấy bất kỳ nội dung nào trên web, hoạt động phát triển không máy chủ hiện đại bắt đầu bằng một thành công ban đầu: triển khai ứng dụng Rails một vùng chứa trực tiếp đến Google Cloud Run trong vòng chưa đầy 3 phút!

1. Tua lại bằng Cỗ máy thời gian

Để đảm bảo rằng cấu hình khởi động của chúng ta hoàn toàn là tạm thời (cơ sở dữ liệu SQLite cục bộ và bộ nhớ tệp trên ổ đĩa cục bộ), hãy sử dụng công cụ Zero-Branch Time-Machine:

just workshop-rewind 1

💡 Điều gì vừa xảy ra?workshop-rewind 1 đã áp dụng lớp phủ cấu hình stage-1-stateless cho blog/config/ mà không rời khỏi nhánh main. Ứng dụng của bạn được định cấu hình bằng SQLite trên ổ đĩa vùng chứa và ActiveStorage trên hệ thống tệp cục bộ.

2. Triển khai Puma một vùng chứa lên Cloud Run

Triển khai trực tiếp từ mã nguồn lên Cloud Run. Rails 8 hiện đại tự động tạo một Dockerfile chính thức, sẵn sàng cho hoạt động sản xuất ngay khi xuất xưởng (với các bản dựng nhiều giai đoạn, jemalloc và bảo mật không phải gốc). Khi bạn triển khai từ thư mục blog/, Google Cloud Build sẽ phát hiện trực tiếp Dockerfile gốc này và cung cấp một dịch vụ không máy chủ được quản lý:

# 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

Bạn sẽ thấy kết quả tiến trình tương tự như sau:

gcloud run deploy building and deploying container

⏳ Quá trình này sẽ mất vài phút trong khi Cloud Build đóng gói vùng chứa và cung cấp bản sửa đổi Cloud Run.

🧯 Khắc phục sự cố và những lưu ý thường gặp

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

? Hai nguyên nhân khác nhau gây ra cùng một lỗi:

  1. gcloud vừa bật một API cho bạn. Nếu quá trình triển khai in The following APIs are not enabled ... cloudbuild.googleapis.com và bạn trả lời Y, thì bản dựng có thể đã bắt đầu trước khi các quyền mới hoàn tất việc truyền tải. Lời nhắc của riêng gcloud cảnh báo "việc này sẽ mất vài phút". Đợi một phút rồi chạy lại chính xác lệnh đó; thường thì lệnh sẽ chạy thành công. terraform apply của Bước 1 thường cho phép bạn sử dụng API này, vì vậy, bạn sẽ không thấy lời nhắc.
  2. SA Điện toán mặc định thực sự thiếu các vai trò. Các dự án hiện đại trên Google Cloud Platform (GCP) thực thi đặc quyền tối thiểu, điều này làm gián đoạn việc triển khai nguồn. Khắc phục bằng cách:
    just project-status   # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builder
    
    just workshop-test hiện cũng báo cáo điều này, vì vậy, bạn nên chạy lại nếu quá trình triển khai vẫn thất bại.

Trong quá trình triển khai:

  1. Cloud Run tạo hình ảnh vùng chứa Rails của bạn.
  2. Thao tác này sẽ chỉ định một miền TLS công khai, bảo mật: https://blog-[hash]-[region].a.run.app.
  3. Lưu lượng truy cập bắt đầu được định tuyến đến Puma trên cổng 8080.

3. ✨ Khoảnh khắc đỉnh cao ban đầu

Mở URL Cloud Run đã tạo trong trình duyệt của bạn!

  1. Ứng dụng Rails 8 hiện đại của bạn đã hoạt động trên Google Cloud!
  2. Đăng nhập bằng thông tin đăng nhập của quản trị viên (GOOGLE_CLOUD_ACCOUNT và APP_ADMIN_PASSWORD). Nếu bạn không thấy nút đăng nhập, hãy nhấp vào "Bài đăng mới". Thao tác này sẽ nhắc bạn nhập tên người dùng và mật khẩu trước:Trang Đăng nhập Rails 8 có biểu ngữ đo từ xa tạm thời
  3. Nhấp vào "Bài đăng mới", viết một bài viết có tiêu đề "Bài đăng đầu tiên của tôi trên Cloud Run", đính kèm một bức ảnh rồi nhấp vào Tạo bài đăng.
  4. Bài đăng của bạn sẽ được xuất bản với đầy đủ định dạng và hình ảnh của bạn sẽ được hiển thị.
  5. Lưu ý về số liệu đo từ xa trực quan:
    • Huy hiệu trên tiêu đề: [EPHEMERAL DB / STORAGE] 💾 Local
    • Hình mờ kiểu hình ảnh: Dấu 🏠 (huy hiệu ổ đĩa tạm thời 127.0.0.1 ở góc dưới cùng bên phải).

🐝 Tham gia Bảng xếp hạng của cộng đồng tham gia Hội thảo trực tuyến! Nếu bạn đang trực tuyến và giám thị viên đang cho thấy bảng xếp hạng, đồng thời bạn muốn tham gia, hãy thêm URL Cloud Run của bạn vào đây: 👉 Đăng ký Cloud Run của bạn trên Bảng xếp hạng

Thông tin chi tiết về bản sửa đổi dịch vụ blog trên Google Cloud Run Console

4. 💥 Điểm mấu chốt: Cú sốc không trạng thái và cạm bẫy "Giải pháp thay thế Puma"

Cloud Run là một nền tảng không trạng thái, không máy chủ. Khi lưu lượng truy cập web giảm xuống bằng 0, Cloud Run sẽ giảm quy mô xuống 0 phiên bản vùng chứa để tiết kiệm tiền. Khi một yêu cầu HTTP mới đến hoặc một bản sửa đổi vùng chứa mới được triển khai, Cloud Run sẽ khởi động một hình ảnh vùng chứa hoàn toàn mới và sạch.

Biểu ngữ Lệnh bị kẹt

Khi bạn tạo một bài đăng hoặc đính kèm một hình ảnh trong một bản triển khai một vùng chứa, Rails sẽ xếp hàng các tác vụ ActiveJob (chẳng hạn như phân tích kích thước hình ảnh hoặc lập chỉ mục siêu dữ liệu). Nhưng vì không có ai đang chạy một worker trong nền, nên bạn sẽ thấy biểu ngữ cảnh báo:

Lưu ý: Hiện có 8 công việc trong nền đang chờ thực thi

Giải pháp hấp dẫn: Chạy hàng đợi ổn định bên trong Puma

Một nhà phát triển thông minh có thể nói: "Khoan đã! Rails 8 cho phép chúng ta chạy Solid Queue ngay trong quy trình máy chủ web Puma bằng cách bật

SOLID_QUEUE_IN_PUMA=true

biến môi trường!"

Hãy thử giải pháp này trên 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

Bây giờ, hãy quay lại trình duyệt rồi làm mới trang:

💥 The Stateless Shock (Cú sốc không trạng thái):

  1. Tin vui: Solid Queue worker hiện đang hoạt động trong Puma! Mọi công việc mới sẽ được xả ngay lập tức.
  2. The Cold Shower (The Catch!):
    • Vì Cloud Run đã triển khai một bản sửa đổi mới, nên phiên bản vùng chứa trước đó đã được thay thế!
    • Bài viết bạn đã viết và tệp cơ sở dữ liệu SQLite trên ổ đĩa đã bị xóa sạch hoàn toàn!
  3. Bài học về kiến trúc: Việc chạy các worker ở chế độ nền trong Puma sẽ tiêu tốn CPU/RAM của luồng web quý giá và vẫn không giải quyết được vấn đề về tính liên tục.

5. Xác thực tự động ở bước 3

Xác minh quá trình triển khai Cloud Run và cách xử lý cảnh báo:

just workshop-eval 3

✨ Bài học: Các giải pháp thay thế cho một vùng chứa như SOLID_QUEUE_IN_PUMA rất hữu ích cho quá trình phát triển cục bộ, nhưng trong cấu trúc đám mây hiện đại:

  • Tệp nội dung nghe nhìn yêu cầu Google Cloud Storage (GCS) (Bước 4).
  • Dữ liệu quan hệ và hàng đợi công việc yêu cầu Google Cloud SQL được quản lý (Bước 5).
  • Các trình chạy nền có mức sử dụng tài nguyên cao thuộc về vùng chứa phụ chuyên dụng (Bước 6).

6. Bước 4: Triển khai 3 – Cảnh báo về Bộ nhớ liên tục và POLA của GCS

Trong bước này, chúng ta sẽ tách bộ nhớ nội dung nghe nhìn và tệp khỏi ổ đĩa vùng chứa bằng cách chuyển ActiveStorage sang Google Cloud Storage (GCS), sử dụng URL đã ký có thời hạn ngắn thông qua API Thông tin xác thực IAM (iam: true).

Trước khi bạn chỉnh sửa bất kỳ cấu hình nào, hãy tìm hiểu 3 điều mà Rails cần để giao tiếp với GCS:

① Mã dự án + Tên bộ chứa – Rails phải biết nơi lưu trữ các blob. Ứng dụng đọc GOOGLE_CLOUD_PROJECT từ môi trường và lấy tên nhóm là ${GOOGLE_CLOUD_PROJECT}-activestorage-prod. Terraform của bạn trong Bước 1 đã tạo vùng chứa này, bạn không cần làm gì thêm.

② Tài khoản dịch vụ có các quyền phù hợp – Cloud Run chạy dưới dạng tài khoản dịch vụ mặc định của Compute Engine (PROJECT_NUMBER-compute@developer.gserviceaccount.com). SA này cần có 2 vai trò IAM trên nhóm:

  • roles/storage.objectAdmin – để tải lên và phân phát các blob
  • roles/iam.serviceAccountTokenCreator trên chính nó – để gọi API signBlob Thông tin xác thực IAM và tạo URL đã ký có thời hạn ngắn

⚠️ Lối tắt đầy cám dỗ –

public: true Cách nhanh nhất để GCS hoạt động là thiết lập public: true trong storage.yml và cấp allUsers:objectViewer trên nhóm. Hình ảnh tải ngay lập tức mà không cần đăng nhập. Đừng làm việc này. Một vùng lưu trữ công khai sẽ mãi mãi để lộ tất cả nội dung nghe nhìn mà người dùng tải lên cho Internet mở. Bản thiết kế của chúng tôi sử dụng iam: true để giữ cho nhóm hoàn toàn riêng tư – mọi URL hình ảnh đều là một mã thông báo đã ký, có thời hạn và được tạo ngay lập tức bằng IAM Credentials API.

③

ACTIVE_STORAGE_SERVICE=google – biến môi trường duy nhất chuyển Rails từ ổ đĩa cục bộ sang GCS trong thời gian chạy. Không cần thay đổi mã, không cần triển khai lại logic ứng dụng – chỉ cần bật/tắt một biến môi trường.

1. Tua lại bằng Cỗ máy thời gian

Chuyển cỗ máy thời gian sang Giai đoạn 2:

just workshop-rewind 2

Kiểm tra blog/config/storage.yml – bạn sẽ thấy ứng dụng xác định 3 môi trường GCS được đặt tên (dev / test / prod), tất cả đều sử dụng cùng một mẫu bảo mật:

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

Cấu trúc ký IAM của GCS

2. Cấp quyền lưu trữ và ký IAM

Đảm bảo tài khoản dịch vụ thời gian chạy Cloud Run có quyền ký URL và tải các đối tượng lên:

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. Triển khai lần thứ ba lên Cloud Run có GCS được đính kèm

Triển khai lại khi đã bật GCS. Đảm bảo bạn đang ở trong thư mục blog/ (nếu không, Cloud Run sẽ cố gắng tạo bằng Buildpacks thay vì Dockerfile của Rails 8, dẫn đến lỗi khi tạo).

Sử dụng --update-env-vars (không phải --set-env-vars) để giữ lại các biến môi trường hiện có (khoá chính, cấu hình IAP, v.v.):

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

💡 Lưu ý: Nếu quá trình triển khai không thành công, hãy nhấp vào đường liên kết đến nhật ký Cloud Build được cung cấp thuận tiện trong đầu ra của thiết bị đầu cuối (như trong hình bên dưới). Bạn cũng có thể yêu cầu Antigravity trợ giúp giải mã và khắc phục lỗi!

Đường liên kết đến nhật ký lỗi của Cloud Build trong đầu ra của thiết bị đầu cuối

4. ✨ Hình ảnh còn sót lại và Dấu thời gian trên đám mây

  1. Làm mới URL blog của bạn.
  2. Tạo một bài đăng mới trên blog có tiêu đề "Vượt qua đám mây" và tải một bức ảnh lên.
  3. Nhìn vào góc dưới cùng bên phải của hình ảnh:
    • Dấu 🏠 đã biến mất!
    • Biểu trưng này được thay thế bằng Tem GCS trên đám mây (nanobanana_stamp_cloud.png) đầy màu sắc.
  4. Buộc khởi động lại một vùng chứa khác (thao tác này sẽ kích hoạt bản sửa đổi thứ tư):
    gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s)
    
  5. Làm mới trang: trong khi cơ sở dữ liệu SQLite được đặt lại, dữ liệu nhị phân của hình ảnh vẫn an toàn trong Google Cloud Storage! Xác minh qua CLI:
    gcloud storage ls -l -r gs://$GCS_BUCKET/
    

Sau đây là danh sách các blob và đối tượng đa phương tiện được tải lên mà bạn có thể thấy trên Bảng điều khiển trình duyệt Google Cloud Storage trong bộ chứa sản xuất (gs://$GCS_BUCKET):

Thông tin chi tiết về bộ chứa trên Google Cloud Storage Console cho thấy các blob hình ảnh liên tục

5. ⚠️ Điểm cần lưu ý: Biểu ngữ cảnh báo về các công việc bị mắc kẹt

Khi bạn tải hình ảnh lên, ActiveStorage sẽ xếp hàng một công việc phân tích (ActiveStorage::AnalyzeJob) để trích xuất kích thước và siêu dữ liệu.

Tuy nhiên, Cloud Run hiện chỉ chạy một vùng chứa trên web duy nhất (puma). Trong Rails 8, Solid Queue lưu trữ các công việc ở chế độ nền trong cơ sở dữ liệu, nhưng không có ai thực thi bundle exec rails solid_queue:start!

Hãy xem ở đầu trang blog của bạn: bạn sẽ thấy một biểu ngữ cảnh báo sáng do blog/app/views/layouts/_check_stuck_jobs.html.erb hiển thị.

Chúng ta sẽ giải quyết vấn đề này một cách thích hợp trong Bước 6, khi chuyển sang kiến trúc phụ nhiều vùng chứa cấp sản xuất với một vùng chứa worker chuyên dụng chạy Solid Queue độc lập với lưu lượng truy cập web!

6. Xác thực tự động bước 4

Xác minh cấu hình GCS và số liệu đo từ xa về các công việc bị kẹt:

just workshop-eval 4

7. Bước 5: Danh sách kiểm tra trước khi triển khai: Cloud SQL, Bí mật và khả năng kết nối cơ sở dữ liệu

Trong khi bạn đã nắm vững các vùng chứa không trạng thái và Google Cloud Storage ở Bước 3 và 4, Terraform đã hoàn tất việc chuẩn bị phần phụ trợ sản xuất của bạn ở chế độ nền!

Trước khi triển khai cấu trúc phụ 3 vùng chứa đầy đủ (Triển khai 4 trong Bước 6), bước này đóng vai trò là một Quy trình kiểm tra trước khi bay nhanh chóng để xác minh tình trạng, khả năng kết nối và quyền truy cập bí mật của cơ sở dữ liệu.

0. Biến môi trường khởi động

Xuất tất cả các biến shell bắt buộc ngay từ đầu ra 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. Kiểm tra tình trạng của Cloud SQL

Xác minh rằng phiên bản PostgreSQL được quản lý của bạn đã hoàn tất quá trình cấp phép ban đầu và hoạt động bình thường:

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

Kết quả đầu ra phải là RUNNABLE.

2. (Không bắt buộc) Kiểm tra khả năng kết nối cơ sở dữ liệu trên máy tính xách tay

Trong quá trình sản xuất, các vùng chứa Cloud Run kết nối an toàn với Cloud SQL thông qua một proxy phụ được mã hoá (127.0.0.1:5432) mà không để lộ cơ sở dữ liệu cho Internet công cộng (0.0.0.0/0).

ℹ️ Lưu ý: Bạn không bắt buộc phải thực hiện bước này! Trong Cloud Run, vùng chứa phụ Cloud SQL Auth Proxy chính thức sẽ tự động xử lý khả năng kết nối mTLS. Bạn không cần cài đặt bất cứ thứ gì trên thiết bị để hoàn thành buổi hội thảo.

Nếu bạn đã cài đặt cloud-sql-proxy và psql trên máy và muốn xác minh khả năng kết nối trực tiếp từ máy tính xách tay, hãy làm như sau:

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

(Nếu được nhắc nhập mật khẩu, hãy nhập

$DB_PASSWORD

. Nếu không, bạn có thể chuyển thẳng đến Bước 3 bên dưới!)

3. Kiểm tra các khoá bí mật được cấp phép bằng Terraform

Tuyệt đối không lưu trữ mật khẩu cơ sở dữ liệu, khoá API hoặc khoá chính Rails dưới dạng văn bản thuần tuý trong git hoặc trong các biến môi trường của vùng chứa. Trong kiến trúc đám mây hiện đại, chúng tôi sử dụng Google Cloud Secret Manager để chèn thời gian chạy không tin cậy.

ℹ️ Terraform đã xử lý việc này cho bạn ở Bước 1:

  1. rails-master-key – để giải mã thông tin đăng nhập Rails trong thời gian chạy.
  2. rails-db-password – một mật khẩu ngẫu nhiên được bảo mật theo cách mã hoá, khớp với người dùng Cloud SQL.
  3. rails-admin-password – mật khẩu an toàn cho người dùng quản trị viên ban đầu.

Xác minh rằng các khoá bí mật của bạn có trong Secret Manager và đang hoạt động:

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

Google Cloud Secret Manager Console liệt kê các bí mật ứng dụng đã định cấu hình

🔑 Đồng bộ hoá master.key với máy tính xách tay (Đồng bộ hoá cục bộ)

Vì blog/config/master.key bị bỏ qua trong git để đảm bảo an toàn, nên bản sao cục bộ của bạn có thể chưa có tệp này. Chạy đoạn mã tự khắc phục thân thiện này để kéo đoạn mã đó từ Secret Manager để các công cụ Rails cục bộ có thể giải mã thông tin đăng nhập:

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: Bạn muốn tạo một khoá bí mật tuỳ chỉnh chỉ để xem quy trình này dễ dàng như thế nào? Secret Manager không chỉ dành cho Rails! Bạn có thể tạo bất kỳ khoá bí mật nào bằng một lệnh CLI đơn giản:

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

4. Xác minh quyền truy cập vào Secret Accessor

Để các vùng chứa Cloud Run gắn những bí mật này dưới dạng biến môi trường hoặc điểm gắn kết ổ đĩa lúc khởi động, tài khoản dịch vụ thời gian chạy ($RUN_SA) cần có vai trò roles/secretmanager.secretAccessor trên mỗi bí mật.

Terraform đã cấp quyền này trong Bước 1! Xác minh mối liên kết chính sách IAM trên 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"

Bạn sẽ thấy:

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

So sánh tính năng bảo mật của Cloud SQL Auth Proxy

5. Xác thực tự động trước khi phát hành

Chạy bộ kiểm thử tự động Bước 5 để xác nhận rằng cơ sở dữ liệu của bạn có thể chạy, các khoá bí mật không trống và quyền IAM có màu xanh lục:

just workshop-eval 5

8. Bước 6: Triển khai 4 – Enterprise Multi-Container Sidecars (Tiêu chuẩn vàng)

Giờ đây, chúng ta sẽ lắp ráp kiến trúc tham chiếu tối ưu: Cloud Run có nhiều vùng chứa!

1. Khôi phục tiêu chuẩn vàng

Khôi phục cấu hình kho lưu trữ về trạng thái chính tắc main:

just workshop-restore-gold

2. Kiểm tra Bản thiết kế sản xuất 3 vùng chứa

Mở blog/compose.prod.yaml rồi kiểm tra cấu trúc:

Kiến trúc sản xuất nhiều vùng chứa của 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)]

Việc triển khai sản xuất chạy 3 vùng chứa phối hợp dùng chung cùng một mạng cục bộ:

  1. web: Phục vụ lưu lượng truy cập HTTP trên cổng 8080 thông qua Puma.
  2. worker: Vùng chứa xử lý nền chuyên dụng đang chạy bundle exec rails solid_queue:start.
  3. cloudsql-proxy: Sidecar Proxy xác thực của Google Cloud SQL chính thức của Google (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2) được liên kết với 127.0.0.1:5432.

3. Chạy quy trình di chuyển cơ sở dữ liệu thông qua Công việc trên Cloud Run

Trước khi định tuyến lưu lượng truy cập web đến bản triển khai mới, chúng ta phải chạy quy trình di chuyển cơ sở dữ liệu và gieo hạt ban đầu dựa trên Cloud SQL.

⚖️ Cách chạy quy trình di chuyển trong vùng chứa: Hai chiến lược

Trong môi trường đám mây được chứa trong vùng chứa, có hai phương pháp phổ biến để thực thi hoạt động di chuyển cơ sở dữ liệu:

  • Lựa chọn A: Tự động bên trong Docker Entrypoint (entrypoint.sh thực thi db:migrate khi khởi động vùng chứa)
    • Ưu điểm: Hoàn toàn tự động; nhà phát triển không cần phải nhớ chạy lệnh.
    • Nhược điểm: Nguy hiểm trong tính năng tự động mở rộng quy mô không cần máy chủ! Khi Cloud Run mở rộng quy mô lên 10 phiên bản đồng thời trong điều kiện lưu lượng truy cập cao, 10 vùng chứa sẽ tranh nhau chạy các quy trình di chuyển đồng thời, gây ra tình trạng khoá cơ sở dữ liệu, tình huống tương tranh lược đồ hoặc hết thời gian khởi động.
  • Lựa chọn B: Rõ ràng thông qua Cloud Run Job (Lựa chọn về kiến trúc của chúng tôi)
    • Ưu điểm: Kiểm soát hoàn toàn, an toàn khi giao dịch và không có tình trạng xung đột. Chạy dưới dạng một tác vụ vùng chứa một lần chuyên dụng, hoàn toàn tách biệt với việc phân phát lưu lượng truy cập web. Nếu quá trình di chuyển không thành công, hoạt động phân phát web sẽ không bị ảnh hưởng.
    • Nhược điểm: Bạn cần kích hoạt một lệnh CLI hoặc bước CI/CD trước khi triển khai.

Chúng tôi chọn Lựa chọn B để có độ tin cậy tối đa trong quá trình sản xuất.

⚠️ Quan trọng: DATABASE_URL Định dạng và độ nghiêm ngặt của Ruby 3.4.5 RFC 3986

Các công việc của Cloud Run kết nối với Cloud SQL bằng proxy socket gốc của Unix (/cloudsql/PROJECT:REGION:INSTANCE). DATABASE_URL phải sử dụng định dạng ba dấu gạch chéo:

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: Ứng dụng sử dụng 4 cơ sở dữ liệu (chính, hàng đợi, bộ nhớ đệm, cáp) cho mỗi database.yml. Tất cả 4 biến môi trường DATABASE_*_URL phải trỏ đến cùng một phiên bản Cloud SQL, nếu không, các bảng Solid Queue/Cache/Cable sẽ không được tạo.

Sau db:prepare, hãy tải các giản đồ 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

Đầu ra của gcloud run jobs executions describe JOBNAMETrong hình trên, bạn có thể thấy một kết quả đầu ra có thể có của

gcloud run jobs executions describe $JOBNAME

Google Cloud Run Jobs Console cho thấy công việc rails-migrate đã thành côngBảng điều khiển Google Cloud Run Jobs cho thấy

rails-migrate

thực thi Thành công

4. Triển khai lần thứ tư: Triển khai Cloud Run nhiều vùng chứa thông qua Docker Compose

Giờ đây, hãy triển khai ngăn xếp sản xuất 3 vùng chứa thực (web, worker, cloudsql-proxy) ngay từ 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

Thẻ Vùng chứa của Google Cloud Run Console hiển thị 3 vùng chứa phụBảng điều khiển Google Cloud Run: Thẻ "Containers" (Các vùng chứa) hiển thị cả 3 vùng chứa phụ (

cloudsql-proxy

,

web

,

worker

)

💡 UI Curiosity / Console Glitch: Trong trình đơn thả xuống Console, bạn có thể nhận thấy Cloud Run gắn nhãn mục nhập trên cùng là Ingress container: cloudsql-proxy. Đừng lo lắng! Đây chỉ là một lỗi giao diện người dùng trực quan trong Google Cloud Console (được theo dõi trong #149) do thứ tự phần phụ thuộc của vùng chứa gây ra. Trên thực tế, web là vùng chứa duy nhất lắng nghe trên cổng HTTP 8080 và xử lý 100% lưu lượng truy cập web đến, trong khi cloudsql-proxy chỉ phân phát lưu lượng truy cập PostgreSQL riêng tư trên cổng 5432!

5. ✨ Khoảnh khắc bất ngờ: Không có cảnh báo và dữ liệu được lưu trữ hoàn toàn trên đám mây!

Hít một hơi thật sâu và ngắm nhìn màn hình của bạn: tất cả biểu ngữ cảnh báo đã biến mất! 🪄

Blog sản xuất trên Cloud Run không có cảnh báo và bài đăng cố định trên đám mâyBlog Rails 8 chính thức: giao diện người dùng nguyên sơ, không có biểu ngữ cảnh báo và trạng thái Cloud SQL liên tục!

Giờ đây, ứng dụng này chính thức là Ứng dụng cấp sản xuất và gốc trên đám mây:

  • Được hỗ trợ bởi Google Cloud SQL (PostgreSQL) được quản lý thông qua đường hầm mTLS bảo mật.
  • Nội dung nghe nhìn được lưu trữ an toàn trong Google Cloud Storage riêng tư bằng tính năng ký blob IAM.
  • Không có cảnh báo nào về bộ nhớ tạm thời và không có cảnh báo nào về các công việc bị kẹt.

Mở URL Cloud Run:

  1. Xem huy hiệu đo từ xa:
    • [CLOUD PERSISTENT 🐘 ☁️] đã được xác minh trong dữ liệu ban đầu của cơ sở dữ liệu!
    • Cả huy hiệu Google Cloud Run và GCS ở chân trang đều có màu xanh lục!
    • Biểu ngữ cảnh báo về các công việc bị kẹt đã biến mất hoàn toàn!
  2. Tạo bài đăng và bình luận trên blog.
  3. Khởi động lại hoặc triển khai lại bao nhiêu lần tuỳ thích: dữ liệu, bài đăng, bình luận và thành phần của bạn sẽ tồn tại mãi mãi trong Cloud SQL và GCS!

6. Xác thực tự động bước 6

Xác minh cấu hình nhiều vùng chứa:

just workshop-eval 6

9. Bước 7: Các quy trình AI tạo sinh, Podcastifier và Cuộc săn lùng kho báu GCS 🏴‍☠️

Khi Solid Queue chạy trong một vùng chứa chuyên dụng và Google Cloud Storage đang hoạt động, chúng ta có thể khai thác sức mạnh của AI tạo sinh không đồng bộ!

1. Công cụ tạo ảnh bìa theo phong cách cổ điển NanoBanana (Quy trình AI tạo sinh sẵn sàng sử dụng)

Linh vật NanoBanana

Tin vui là chức năng này đã được triển khai đầy đủ trong cơ sở mã! Bạn không cần viết mã nào cả. Nhiệm vụ của bạn ở đây là kích hoạt và quan sát một quy trình GenAI không đồng bộ trên đám mây đang hoạt động.

🧠 Cách hoạt động

  • Xác thực: Không cần khoá API! Nền tảng này tự động sử dụng Thông tin xác thực mặc định của ứng dụng (roles/aiplatform.user trên Vertex AI) thông qua tài khoản dịch vụ thời gian chạy Cloud Run. (Hoặc nếu GEMINI_API_KEY được cung cấp, thì yêu cầu này sẽ gọi trực tiếp Gemini API).
  • Quy trình: Khi một bài viết được tạo mà không có ảnh bìa, lệnh gọi lại after_commit sẽ xếp GenerateCoverImageJob vào Hàng đợi cố định.
  • Mô hình: Câu lệnh này sẽ nhắc mô hình đa phương thức của Google (Gemini 2.5 Flash Image / Imagen trên Vertex AI) tạo một áp phích phim Ý chân thực của những năm 1960 ("Locandina di un film 1960") hoàn chỉnh với một quả chuối xuất hiện chớp nhoáng và một viên hồng ngọc "8" sáng bóng.
  • Hình mờ nguồn gốc động: Sử dụng libvips để đóng dấu huy hiệu trực quan ở góc (dấu đám mây nhiều màu trên GCS).
  • Turbo Stream Hot-Swap:Khi hình ảnh được đính kèm, Turbo::StreamsChannel.broadcast_refresh_to(post) sẽ cập nhật trình duyệt của người đọc trực tiếp mà không cần tải lại trang theo cách thủ công!

Sơ đồ trình tự kiến trúc không đồng bộ của AI NanoBanana

📐 Xem nguồn sơ đồ chuỗi 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!

🧪 Thử ngay!

Bạn có thể kích hoạt tính năng này theo hai cách:

  • Cách A (Giao diện người dùng trên web): Nhấp vào Bài đăng mới, nhập tiêu đề như "Cấu trúc không máy chủ với Ruby on Rails 8", viết nội dung và để trống phần nhập tệp hình ảnh bìa. Nhấp vào Tạo bài đăng.
  • Cách B (Trình chạy CLI): Chạy từ thiết bị đầu cuối:
    bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
    

Chờ khoảng 15 đến 30 giây: xem nhật ký của worker Solid Queue trong Cloud Run. Áp phích cổ điển sẽ xuất hiện ngay trên bài đăng của bạn!

📸 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. The Bilingual Podcastifier Quest (Bài tập lập trình thực hành về lực phản trọng lực)

⚠️ Thông báo quan trọng: Không giống như Trình tạo ảnh bìa ở trên, tính năng này CHƯA được triển khai!

Nếu mở blog/app/jobs/podcastifier_job.rb, bạn sẽ thấy một phần khung trống:

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

Trong bài tập thực hành này, nhiệm vụ của bạn là lập trình theo cặp với Google Antigravity để hoàn tất tính năng này:

  1. Câu lệnh Antigravity:"Giúp tôi triển khai PodcastifierJob trong app/jobs/podcastifier_job.rb. Thao tác này sẽ tìm nạp Bài đăng, dịch hoặc tổng hợp một bản tóm tắt bằng âm thanh tiếng Ý bằng Google Cloud Text-to-Speech (giọng "it-IT-Wavenet-A" với mã ngôn ngữ "it-IT" thông qua thông tin đăng nhập ADC) và đính kèm tệp âm thanh vào post.podcast_audio_it bằng ActiveStorage!"
  2. Thêm trình phát vào khung hiển thị: Trong app/views/posts/show.html.erb, hãy thêm trình phát HTML5 khi @post.podcast_audio_it.attached?.
  3. Kích hoạt tính năng tổng hợp: Khi bạn lưu, Solid Queue sẽ thực hiện công việc chuyển văn bản sang lời nói không đồng bộ ở chế độ nền.

Podcastifier Bilingual UI with Dual Audio Players

🎧 Ví dụ về đầu ra của Podcastifier:

💡 Bạn cần trợ giúp hoặc gặp khó khăn? Một giải pháp tham chiếu hoàn chỉnh, đã được kiểm nghiệm có trên nhánh solutions/podcastifier. Bạn có thể kiểm tra bất cứ lúc nào bằng git diff solutions/podcastifier.

3. [KHÔNG BẮT BUỘC] 🏴‍☠️ Săn kho báu GCS (Khôi phục blob trên bảng điều khiển)

ℹ️ Kiểm tra điều kiện tiên quyết: Địa phương

cloud-sql-proxy

Bắt buộc phải có CLI Bài tập không bắt buộc này kết nối trực tiếp máy tính xách tay cục bộ của bạn với Google Cloud SQL thông qua một đường hầm mTLS được mã hoá. Kiểm tra xem bạn đã cài đặt proxy CLI hay chưa:

which cloud-sql-proxy
  • Nếu đã cài đặt: Tiếp tục thực hiện các bước bên dưới!
  • Nếu bạn CHƯA cài đặt và muốn thực hiện nhiệm vụ này: Hãy cài đặt bằng cách:
    # 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
    
  • Nếu không: Bạn có thể chuyển sang bước xác thực tự động 7!

Bạn có nhớ bức ảnh mà bạn đã tải lên ở Bước 4 trước khi quá trình khởi động lại vùng chứa xoá cơ sở dữ liệu SQLite tạm thời không? Tệp hình ảnh đó vẫn nằm an toàn trong bộ chứa GCS riêng tư của bạn dưới dạng "đối tượng blob bị bỏ rơi"!

Hãy sử dụng bảng điều khiển Rails để khôi phục và đính kèm vào một bài đăng trên 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

Trong bảng điều khiển 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

Làm mới blog của bạn: bức ảnh được tải lên trong bước Stateless Shock (Sốc không trạng thái) ở bước 4 sẽ được khôi phục và gắn vĩnh viễn vào bài đăng Cloud SQL của bạn!

4. Xác thực tự động bước 7

Xác minh các thành phần và công việc liên quan đến AI tạo sinh:

just workshop-eval 7

10. Bước 8: Chọn chuyến phiêu lưu của riêng bạn / Nhiệm vụ nâng cao 🏆

Giờ đây, bạn đã nắm vững cấu trúc tham chiếu chuẩn, hãy chọn nhiệm vụ tốt nghiệp!

🛡️ Nhiệm vụ 1: Google IAP (Identity-Aware Proxy) không tin tưởng bất cứ nguồn nào

  • Độ khó: Trung bình (Bảo mật doanh nghiệp)
  • Mục tiêu: Khoá ứng dụng Cloud Run để chỉ những tài khoản Google / Gmail đã xác minh của bạn mới có thể truy cập vào ứng dụng này, loại bỏ hoàn toàn việc đăng nhập bằng mật khẩu!
  • Cách hoạt động:
    1. Trình cân bằng tải ứng dụng HTTPS bên ngoài sẽ chấm dứt Google OAuth và xác minh danh tính trước khi lưu lượng truy cập đến Cloud Run.
    2. Google chuyển tiếp các tiêu đề danh tính đã xác minh (X-Goog-Authenticated-User-Email).
    3. Rails tự động đăng nhập danh tính đã được Google xác minh thông qua 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. Bật mô-đun Terraform trong iac/iap.tf bằng enable_iap = true và chỉ định Tài khoản Google được phép:
      iap_allowed_users = [var.google_cloud_account]
      

Biểu ngữ IAP nhận diện được thông tin đăng nhập

📊 Nhiệm vụ 2: Dữ liệu đo từ xa SRE trong quá trình sản xuất và Cloud Logging

  • Độ khó: Trung bình (Khả năng ghi nhận)
  • Mục tiêu: Truyền trực tiếp các nhật ký ứng dụng JSON có cấu trúc cùng mã nhận dạng tương quan dấu vết đến Google Cloud Logging và phát hiện các trường hợp ngoại lệ trong quá trình sản xuất theo thời gian thực bằng Cloud Error Reporting.
  • Cách hoạt động:
    • Định cấu hình blog/config/environments/production.rb để phát ra nhật ký JSON có cấu trúc bằng nhãn theo dõi GCP.
    • Kích hoạt một ngoại lệ kiểm thử có chủ ý và xem Google Cloud Error Reporting nhóm và thông báo cho bạn ngay lập tức.

📸 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 Tìm kiếm ngữ nghĩa và RAG của Gemini

  • Độ khó: Khó (Dự án cuối khoá về AI tạo sinh)
  • Mục tiêu: Tìm kiếm các bài viết theo khái niệm bằng cách sử dụng các vectơ nhúng được lưu trữ trong PostgreSQL trên Cloud SQL.
  • Cách hoạt động:
    • Bật tiện ích vector trên phiên bản Cloud SQL PostgreSQL:
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • Thêm gem neighbor vào blog/Gemfile và tạo vectơ nhúng văn bản bằng Gemini (text-embedding-004) trên Post#after_save.
    • Thực hiện các truy vấn khoảng cách cosin (<=>) để hỗ trợ thanh tìm kiếm ngữ nghĩa bằng Turbo Streams!

📸 VIỆC CẦN LÀM(riccardo): thêm ảnh chụp màn hình thanh tìm kiếm ngữ nghĩa của blog trực tiếp trả về các kết quả trùng khớp theo khái niệm với điểm tương đồng cosine

⚡ Nhiệm vụ 4: Trợ lý kiểm tra hiệu suất và SEO

  • Độ khó: Dễ / Vui (Trải nghiệm của nhà phát triển)
  • Mục tiêu: Kiểm tra Core Web Vitals, Nội dung lớn nhất hiển thị (LCP) và chỉ số khả năng đọc Flesch-Kincaid / Fog bằng các công cụ Antigravity và Speedgrapher.

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

🤔 Hộp tò mò: Lấy một bảng điều khiển Rails trực tiếp trên Cloud Run

💡 Câu hỏi: Trên các VM truyền thống hoặc nền tảng PaaS theo kiểu Heroku, các nhà phát triển thường SSH vào một phiên bản đang chạy hoặc sử dụng heroku run rails console để kiểm tra hoặc sửa dữ liệu sản xuất trực tiếp. Tương đương với Cloud Run gốc là gì và làm cách nào để tăng cường bảo mật?

Lý do

gcloud run exec

không tồn tại đối với Dịch vụ Cloud Run

Cloud Run là một nền tảng không máy chủ: vùng chứa của bạn có thể đang chạy dưới dạng 0, 1 hoặc hàng trăm phiên bản giống hệt nhau, không có danh tính ổn định hoặc trình bao có thể định địa chỉ. Điều này khác biệt cơ bản so với Kubernetes (kubectl exec), nơi một nhóm cụ thể có một ổ cắm được đặt tên mà bạn có thể gắn vào.

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

Bốn mẫu gốc của GCP

Mẫu

Ra lệnh

Có tính tương tác không?

Bảo mật

1. Proxy cục bộ + DB từ xa

DATABASE_URL=... rails console

✅ Đầy

🔒 mTLS của Proxy xác thực Cloud SQL

2. Công việc trên Cloud Run (một lần)

gcloud run jobs execute --wait

❌ Chỉ có tập lệnh

🔒 Cần có vai trò IAM

3. Gem Web Console + IAP

Trình duyệt → /rails/console

✅ Đầy

🛡️ Zero Trust của IAP

4. Cloud Shell + Proxy

Cloud Shell → bin/rails c

✅ Đầy

🔒 Thông tin nhận dạng của Google

Mẫu 1 (Nên dùng để kiểm tra dữ liệu): Chạy một bảng điều khiển Rails cục bộ giao tiếp trực tiếp với Cloud SQL PostgreSQL thông qua Cloud SQL Auth Proxy:

# In one terminal: start the auth proxy
cloud-sql-proxy rails8-fl-20260907b-xm8ko4:europe-west1:your-instance

# In another terminal: open a read-write or sandbox console
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production \
  bin/rails console --sandbox

Bạn có được khả năng tương tác IRB đầy đủ mà không bị lộ mạng – Cloud SQL Proxy sử dụng mTLS qua một socket miền Unix hoặc vòng lặp.

Mẫu 3 (Web Console + IAP Zero-Trust, đường dẫn trình duyệt được tăng cường bảo mật):

Gem web-console cung cấp một phiên IRB dựa trên trình duyệt. Nếu không có biện pháp bảo vệ, đây là một lỗ hổng bảo mật nghiêm trọng – đừng bao giờ tiết lộ công khai. Nhưng khi kết hợp với Google IAP (Quest 1), bề mặt tấn công sẽ thu hẹp: chỉ những danh tính đã được Google xác minh trong danh sách cho phép của bạn mới có thể truy cập vào điểm cuối /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

⚠️ Mẫu 3 yêu cầu bạn phải bật Nhiệm vụ 1 (IAP) trước. Nếu không có khoá này, việc bật /rails/console trên một URL công khai cũng tương tự như việc bạn để phòng máy chủ không khoá. Với IAP, công cụ này trở thành một công cụ mạnh mẽ dành cho nhà điều hành, được bảo mật theo mô hình Không tin tưởng bất cứ điều gì của Google.

Hỏi Antigravity: "Hướng dẫn tôi cách chạy

rails console --sandbox

một cách an toàn đối với cơ sở dữ liệu sản xuất Cloud SQL của tôi bằng cách sử dụng Cloud SQL Auth Proxy."

🏆 Tốt nghiệp trên The Hive: Bằng chứng công việc được xác thực bởi giám thị

Sau khi hoàn thành nhiệm vụ đã chọn, hãy nhận Chiếc cúp tốt nghiệp ở Bước 8 🏆 trên Bảng xếp hạng của The Hive:

  1. Gửi nhiệm vụ của bạn trên GitHub:
    • Mở một Vấn đề mới trên kho lưu trữ chính thức: github.com/palladius/rails8-app-on-gcp/issues/new
    • Định dạng tiêu đề: 🎓 [Step 8 Completed] :
    • Trong nội dung, hãy mô tả ngắn gọn những gì bạn đã hoàn thành và thêm đường liên kết đến bản triển khai Cloud Run đang hoạt động.
  2. Kết nối ứng dụng Cloud Run với vấn đề của bạn:
    • Ghi lại Số vấn đề (ví dụ: 88 hoặc URL đầy đủ https://github.com/palladius/rails8-app-on-gcp/issues/88).
    • Chèn biến môi trường STEP_8_GHI vào dịch vụ Cloud Run:
      gcloud run services update blog \
        --region europe-west1 \
        --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER>
      
    • Xác minh rằng điểm cuối /status.json hiện báo cáo nhiệm vụ của bạn:
      curl -s https://<YOUR_APP>.run.app/status.json | jq .quest
      
      Bạn sẽ thấy:
      {
        "step_8_completed": true,
        "ghi_issue": 88,
        "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88"
      }
      
  3. Yêu cầu xem xét của giám thị và nhận cúp vàng:
    • Kiểm tra Bảng xếp hạng The Hive: hàng của bạn sẽ hiển thị một huy hiệu màu hổ phách: ⏳ GHI #XX review pending.
    • Yêu cầu người giám sát hội thảo (@palladius, @emilianodellacasa hoặc @ricc) kiểm tra việc triển khai của bạn.
    • Ngay khi giám thị bình luận LGTM về vấn đề của bạn, The Hive sẽ tự động nâng tiến trình của bạn lên 8/8 🏆 kèm theo một huy hiệu phát sáng màu vàng/tím và một chiếc cúp vĩnh viễn liên kết trực tiếp đến vấn đề cuối khoá của bạn!

11. Kết luận và dọn dẹp 🎓

Xin chúc mừng! 🎉

Bạn đã tạo và triển khai một ứng dụng Rails 8 cấp sản xuất, phù hợp với doanh nghiệp trên Google Cloud Platform:

  • Zero-Branch Progression: Mastered modern workflows with Time-Machine overlays on main.
  • Cloud Storage: Bộ nhớ đối tượng riêng tư, có khả năng mở rộng với tính năng ký blob IAM (iam: true).
  • Cloud SQL: PostgreSQL được quản lý và bảo mật bằng các đường hầm mTLS của Proxy xác thực Cloud SQL.
  • Secret Manager: Không có thông tin đăng nhập văn bản thuần tuý hoặc rò rỉ tệp .env.
  • Cloud Run Multi-Container: Web Puma riêng biệt, Solid Queue worker và các sidecar proxy.
  • AI tạo sinh: Tạo áp phích cổ điển không đồng bộ và tổng hợp podcast bằng công nghệ chuyển văn bản sang lời nói với Vertex AI.
  • Nhiệm vụ: IAP không tin tưởng bất cứ điều gì, Khả năng quan sát SRE và các mục nhúng pgvector!

🧹 Dọn dẹp tài nguyên

Để tránh bị tính phí liên tục sau khi hoàn thành hội thảo, hãy nhớ dọn dẹp các tài nguyên trên Google Cloud! Chọn phương pháp phù hợp nhất với môi trường của bạn:

Hãy sử dụng lựa chọn này nếu bạn đang dùng một dự án trên đám mây Google Cloud hiện có hoặc dùng chung, nơi có các tài nguyên hoặc dữ liệu khác. Thao tác này sẽ xoá chỉ những tài nguyên được cung cấp trong hội thảo này (Cloud Run, Cloud SQL, Secret Manager, các vùng chứa GCS), mà không ảnh hưởng đến phần còn lại của dự án.

cd iac
terraform destroy -auto-approve

Cách 2: Xoá hoàn toàn dự án (Không để lại dấu vết 🌪️)

Hãy sử dụng lựa chọn này nếu bạn đã tạo một dự án hội thảo chuyên biệt (ví dụ: sử dụng tín dụng hội thảo hoặc một hộp cát) và muốn đảm bảo rằng không có dấu vết nào còn lại, bao gồm cả nhật ký, siêu dữ liệu, tài khoản dịch vụ và đường liên kết thanh toán.

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet