Google Cloud에서 Rails 8 실행: 처음부터 AI까지

1. 소개

Google Cloud의 Rails 8 워크숍에 오신 것을 환영합니다. 이 실습 Codelab에서는 간단한 로컬 SQLite 기준에서 Google Cloud의 프로덕션급 엔터프라이즈 지원 참조 아키텍처로 최신 Rails 8 애플리케이션을 가져옵니다.

이 워크숍에서는 앱을 배포하는 것뿐만 아니라 주관적이고 프로덕션 등급의 클라우드 현대화를 실행합니다 (제대로 된 리프트 앤 시프트).

일반적인 '내 머신에서 작동' 로컬 설정 (이페머럴 디스크, 내장 SQLite, 인프로세스 작업)에서 시작하여 Google Cloud에서 엔터프라이즈, 완벽한 클라우드 아키텍처로 점진적으로 발전시킵니다.

  1. 🐘 로컬 SQLite → Cloud SQL (PostgreSQL): 다시 시작 시 DB가 더 이상 삭제되지 않습니다. Cloud SQL 인증 프록시 (mTLS, 공개 IP 없음)를 통해 안전하게 연결된 관리형 PostgreSQL로 이동합니다.
  2. 🪣 임시 로컬 스토리지 → 비공개 GCS: 서버리스 디스크는 임시입니다. IAM 서명된 URL (iam: true)을 사용하여 ActiveStorage를 비공개 Google Cloud Storage에 연결합니다. 공개 버킷이나 유출된 키는 없습니다.
  3. 🚀 인프로세스 작업 → 멀티 컨테이너 Cloud Run: Rails 모놀리식과 서버리스 클라우드를 연결합니다. 표준 Docker Compose를 사용하여 Cloud Run 다중 컨테이너 포드에서 Puma와 함께 전용 작업자 컨테이너로 Solid Queue를 실행합니다. 마이크로서비스 확산도 없고 스레드 기아도 없습니다. DHH도 자랑스러워할 것입니다.

🍌 2026년인 만큼 Google Antigravity로 전체 스택을 강화합니다. 이 워크숍을 더 잘 실행하고 즐길 수 있도록 안내하는 프롬프트와 기술이 준비되어 있습니다.

🏛️ 타겟 참조 아키텍처 (대안 1: 깔끔한 플랫 벡터 엔터프라이즈)

다음은 빌드하고 배포할 표준 Google Cloud 참조 아키텍처 (변형 1: 깔끔한 플랫 벡터 엔터프라이즈)로, 모든 관리형 Google Cloud 영속성 및 AI 서비스에 연결된 프로덕션 멀티 컨테이너 Cloud Run 서비스를 보여줍니다.

변형 1: 깨끗한 플랫 벡터 엔터프라이즈 - Google Cloud의 Rails 8 아키텍처

🎞️ 아키텍처의 진화: 제로에서 클라우드 네이티브까지

각 워크숍 마일스톤에서 일시적인 단일 머신 기준에서 완전 관리형 Google Cloud 청사진으로 스택이 점진적으로 현대화되는 과정을 확인하세요.

워크숍 아키텍처 발전

학습할 내용

  • Google Antigravity를 사용하여 페어 프로그래밍을 통해 Rails 8과 Google Cloud의 비밀을 푸는 방법
  • 자동 비행 전 진단 (just workshop-test)을 실행하는 방법
  • google 제공자를 사용하여 차단 없이 로컬 개발을 계속하면서 Terraform을 사용하여 Google Cloud 인프라를 비동기식으로 프로비저닝하는 방법
  • 보안 안티패턴을 제거하는 방법: 0.0.0.0/0을 여는 대신 비공개 GCS 버킷 (iam: true) 및 Cloud SQL 인증 프록시 mTLS 터널
  • Google Cloud Secret Manager에서 직접 보안 비밀을 삽입하는 방법
  • Solid Queue를 통해 비동기 생성형 AI 백그라운드 작업 (NanoBanana 커버 생성기, 이중 언어 팟캐스트 합성)을 오케스트레이션하는 방법

지금 시작해 보세요.

2. 0단계: 사전 준비 사항, 무중력 설정, 결제 확인

💡 시나리오: 팀에서 미션 크리티컬 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: (ruby -v - blog/.ruby-version에 의해 고정됨. 누락된 경우 AI 하네스에 3.4.5 설치를 요청하세요.) Rails 8은 Gemfile에 선언되고 bundle install에 의해 자동으로 설치됩니다.
  • Google Antigravity 2.0: 자율 AI 페어 프로그래밍 어시스턴트 (Google Antigravity 2.0 다운로드) 개인 Gmail로 로그인하며 API 토큰은 필요하지 않습니다.

2. Google Cloud 인증, 전용 구성 및 ADC

기존 회사, 개인 또는 다중 계정 gcloud 설정과의 충돌을 방지하려면 이 워크숍을 위한 전용 명명된 구성을 만드는 것이 좋습니다.

# 1. Create and activate dedicated workshop configuration (Fixes Issue #38)
gcloud config configurations create rails8-on-gcp-workshop --activate 2>/dev/null || \
  gcloud config configurations activate rails8-on-gcp-workshop

# 2. Authenticate your account and Application Default Credentials (ADC)
gcloud auth login $GOOGLE_CLOUD_ACCOUNT
gcloud auth application-default login

# 3. Configure active project and region
gcloud config set project $GOOGLE_CLOUD_PROJECT
gcloud config set compute/region europe-west1

💡 전용 구성이 필요한 이유 gcloud config configurations create rails8-on-gcp-workshop를 사용하면 이 워크숍을 위해 모든 CLI 설정 (계정, 할당량 프로젝트, 기본 리전)이 격리됩니다. 완료되면 언제든지 gcloud config configurations activate default를 사용하여 일반 설정으로 다시 전환할 수 있습니다.

활성 gcloud 구성

3. 🚨 GCP 결제 확인

⚠️ 참고: Google Cloud SQL 및 Cloud Run 배포에는 활성 연결된 결제 계정 또는 유효한 워크숍 교육 크레딧이 필요합니다. 지금 설정이 올바른지 확인해 보겠습니다. 이렇게 하려면 결제 확인 검사를 실행하세요.

gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT

billingEnabled: true이 반환되는지 확인합니다. 모든 결제 계정을 나열할 수도 있습니다.

gcloud billing accounts list

OPEN: True가 표시되는 항목이 하나 이상 있어야 합니다. 활성 상태인 계정이 없으면 Google Cloud 콘솔 결제 페이지에서 결제 계정을 연결하거나 워크숍 크레딧 쿠폰을 사용하세요.

활성 GCP 결제 계정

4. 저장소 클론 및 Antigravity와 페어링

저장소를 클론하고 디렉터리로 이동합니다. main에 완전히 머물러 있습니다.

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

Google Antigravity에서 이 디렉터리를 엽니다. Antigravity는 저장소를 자동으로 검사하고 AGENTS.md를 읽고 페어 프로그래머로 대기합니다.

5. 🪫 Antigravity의 무료 크레딧이 소진된 경우

⚠️ 방금 사용한 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. Pro 대신 Flash 모델을 선택합니다. 비율 제한은 요청당 에이전트가 수행하는 작업량과 관련이 있으므로 더 가벼운 모델은 남은 작업을 늘립니다.
  3. Antigravity CLI를 자체 Gemini API 키와 함께 사용하세요. Antigravity IDE에서는 자체 키 사용이 지원되지 않습니다. CLI가 문서화된 대안입니다. Google AI Studio에서 무료 키를 만들고 (프로젝트는 필요하지만 결제 계정은 필요하지 않음) Antigravity CLI를 다운로드하고 구성합니다.
  4. 기다리거나 업그레이드하세요. 무료 등급 할당량은 매주 새로고침됩니다 (오류 메시지에 재설정 날짜가 표시됨). Google AI Pro/Ultra는 데스크톱 앱 내에서 기준선을 높이는 유일한 문서화된 방법입니다. 이러한 요금제는 5시간마다 갱신되며 초과 사용량에 대해 구매한 AI 크레딧을 사용할 수 있습니다.
  5. 이 워크숍은 Antigravity를 사용하지만 대부분의 AI 코딩 도구도 작동합니다. 저장소의 AGENTS.md 및 skills/ 디렉터리를 가리키기만 하면 됩니다.

6. 자동화된 0단계 검증

0단계 환경이 평가 모음을 100% 준수하는지 확인합니다.

just workshop-eval 0

✨ 놀라운 순간: 무중력 및 워크숍 평가 엔진이 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 ID 구성을 확인합니다.
  • 활성 결제 및 프로젝트 연결을 확인합니다.
  • Vertex AI의 애플리케이션 기본 사용자 인증 정보 (ADC)를 검증합니다.
  • ActiveStorage 카나리아 시드 이미지 (blog/app/assets/images/gcs_dev_image.jpg)를 확인합니다.

워크숍 테스트 진단 출력만

.env이 누락된 경우 문서화된 템플릿에서 복사합니다.

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

💡 팁: just workshop-test에서 프로젝트의 결제 API가 사용 중지되었다고 보고하는 경우 다음을 통해 빠르게 사용 설정하세요.

gcloud services enable cloudbilling.googleapis.com

2. ⏱️ Terraform 인프라 비동기식으로 실행

원격 백엔드 상태 버킷을 만들고 인프라 배포를 트리거하는 원스텝 Terraform 레시피를 실행합니다.

just terraform-apply

Terraform이 성공적으로 초기화됨

수동 대안 (또는 `iac/bin/terraform-apply.sh` 직접 실행)

# Fall back to active gcloud config if environment variables are unset
GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project)}"
GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"

# Create remote state bucket idempotently
gcloud storage buckets create "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" --location="$GOOGLE_CLOUD_REGION" 2>/dev/null || \
  gcloud storage buckets describe "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" >/dev/null

cd iac
terraform init -backend-config="bucket=${GOOGLE_CLOUD_PROJECT}-tfstate"
terraform apply -auto-approve -var="project_id=${GOOGLE_CLOUD_PROJECT}" -var="region=${GOOGLE_CLOUD_REGION}"
cd ..

Terraform에서 프로비저닝하는 항목:

  • Google Cloud Storage 버킷: 비공개 액세스 및 IAM 사용자 인증 정보 서명 (iam: true)으로 생성됩니다.
  • 카나리아 테스트 이미지: 엔드 투 엔드 blob 확인을 사용 설정하기 위해 gcs_dev_image.jpg를 버킷에 업로드합니다.
  • Google Cloud SQL PostgreSQL 인스턴스: 백그라운드 프로비저닝을 시작합니다 (~10~12분).

Google Cloud 콘솔의 Cloud SQL PostgreSQL 인스턴스

💡 참고: 자동 백업이 누락되었다는 경고는 무시해도 됩니다. 이 워크숍 데이터베이스에는 필요하지 않습니다 (하지만 자동 일일 백업은 실제 프로덕션 데이터베이스에 필수적인 권장사항입니다).

Terraform 적용이 출력과 함께 완료됨

📝 Cloud Run URL 기록: Terraform 적용이 성공하고 인프라 출력이 출력됩니다. cloud_run_url를 기록해 두세요 (또는 클립보드에 복사). 나중에 라이브 서비스를 확인할 때 필요합니다.

3. 자동 1단계 검증 (선택사항)

1단계 기본 요건 및 구성이 통과하는지 확인합니다.

just workshop-eval 1

⏱️ 참고: 이 평가에서는 실제 클라우드 리소스를 확인하며 완료하는 데 몇 분 정도 걸릴 수 있습니다. 완전히 선택사항입니다. 터미널에서 실행되는 동안 기다리지 않고 다음 단계로 바로 진행할 수 있습니다.

4. 2단계: 로컬 기준, Mailpit, 관리자 온보딩

시작점은 SQLite, Mailpit 이메일 가로채기, 디스크 기반 ActiveStorage를 사용하여 localhost에서 실행되는 깨끗한 최신 Rails 8 블로그 애플리케이션입니다.

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)가 표시되나요? 일반 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)를 사용하면 호스트 gem을 건드리지 않고 고정된 Ruby 스택을 컨테이너 내에서 완전히 실행할 수 있습니다.

💡 생각하고 싶지 않으신가요? 저장소 루트의 just install 및 just compose-up가 cd blog를 실행합니다.

2. 🌱 스마트 시드 자동 검색 및 관리자 부트스트랩 (문제 #21 및 #25)

데이터베이스 시드 (db/seeds.rb)에는 스마트 환경 자동 검색 기능이 있습니다.

  • 활성 데이터베이스 어댑터 (SQLite와 Postgres) 및 스토리지 구성을 검사합니다.
  • 0단계 (로컬 호스트)를 감지하고 초기 관리자 사용자를 자동으로 만들고 교육용 게시물을 시드합니다.
    • [LOCAL BASELINE] Welcome to Rails 8 on Localhost!
    • 로컬 디스크 스토리지가 임시 스토리지임을 알리는 워터마크가 있는 기본 로컬 슬픈 이미지 첨부파일 (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

Rails, Mailpit, 로컬 스택을 시작하는 Docker Compose

이제 애플리케이션, 로컬 Mailpit SMTP 서버, Adminer 데이터베이스 뷰어가 격리된 컨테이너에서 함께 실행됩니다.

  • Rails 앱: http://localhost:3000
  • Mailpit 웹 UI: 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. 시각적 원격 분석 배지 관찰:
    • UI에서 노란색 환경 배너와 배지(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

워크숍 평가 2 확인 출력만

5. 문제: 스테이트리스(Stateless) 컨테이너

Cloud Run 컨테이너는 스테이트리스(Stateless)이며 임시적입니다. SQLite 데이터베이스와 로컬 storage/ 디렉터리를 Cloud Run에 직접 배포하면 컨테이너가 0으로 확장되거나 다시 시작될 때마다 모든 게시물과 업로드된 이미지가 영구적으로 삭제됩니다.

다음 단계에서는 스테이트리스(Stateless) 쇼크를 직접 확인하기 위해 이 임시 구성을 Cloud Run에 의도적으로 배포합니다.

5. 3단계: 1단계 배포 - 상태 비저장 쇼크 (3분 만에 초기 WOW)

웹에서 콘텐츠를 확인하기 전에 클라우드 데이터베이스를 15~20분 동안 기다리는 대신 최신 서버리스 개발은 초기 성공으로 시작합니다. 3분 이내에 단일 컨테이너 Rails 애플리케이션을 Google Cloud Run에 직접 배포합니다.

1. 타임머신 되감기

시작 구성이 100% 임시 (로컬 SQLite 데이터베이스 및 로컬 디스크 파일 스토리지)인지 확인하려면 제로 브랜치 타임머신 엔진을 사용하세요.

just workshop-rewind 1

💡 방금 무슨 일이 일어났나요?workshop-rewind 1이(가) main 브랜치를 벗어나지 않고 blog/config/에 stage-1-stateless 구성 오버레이를 적용했습니다. 앱이 컨테이너 디스크의 SQLite와 로컬 파일 시스템의 ActiveStorage로 구성되어 있습니다.

2. 단일 컨테이너 Puma를 Cloud Run에 배포

소스 코드에서 Cloud Run으로 직접 배포 최신 Rails 8은 다단계 빌드, jemalloc, 비루트 보안을 사용하여 공식적이고 프로덕션 준비가 완료된 Dockerfile를 자동으로 생성합니다. 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분 정도 기다렸다가 동일한 명령어를 다시 실행하면 일반적으로 성공합니다. 1단계의 terraform apply는 일반적으로 이 API를 사용 설정하므로 프롬프트가 표시되지 않아야 합니다.
  2. 기본 컴퓨팅 SA에 역할이 실제로 누락되었습니다. 최신 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 URL을 브라우저에서 엽니다.

  1. 이제 Google Cloud에서 최신 Rails 8 애플리케이션을 사용할 수 있습니다.
  2. 관리자 인증 정보 (GOOGLE_CLOUD_ACCOUNT 및 APP_ADMIN_PASSWORD)로 로그인합니다. 로그인 버튼이 없으면 '새 게시물'을 클릭하면 사용자 및 비밀번호를 먼저 묻는 메시지가 표시됩니다.일시적인 원격 분석 배너가 있는 Rails 8 로그인 페이지
  3. '새 게시물'을 클릭하고, '내 첫 번째 Cloud Run 게시물'이라는 제목의 글을 작성하고, 사진을 첨부하고, 게시물 만들기를 클릭합니다.
  4. 게시물이 전체 서식으로 게시되고 이미지가 렌더링됩니다.
  5. 시각적 원격 분석을 확인합니다.
    • 헤더 배지: [EPHEMERAL DB / STORAGE] 💾 Local
    • 이미지 워터마크: 🏠 스탬프 (오른쪽 하단의 127.0.0.1 이페머럴 디스크 배지)

🐝 라이브 워크숍 Hive 리더보드에 참여하세요! 온라인 상태이고 감독관이 리더보드를 표시하고 있으며 리더보드에 참여하려면 여기에서 Cloud Run URL을 추가하세요. 👉 리더보드에 Cloud Run 등록

Google Cloud Run 콘솔 블로그 서비스 버전 세부정보

4. 💥 문제: 스테이트리스(Stateless) 충격 및 '푸마 해결 방법' 함정

Cloud Run은 스테이트리스 서버리스 플랫폼입니다. 웹 트래픽이 0으로 떨어지면 Cloud Run은 비용을 절약하기 위해 컨테이너 인스턴스를 0으로 축소합니다. 새 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

이제 브라우저로 돌아가 페이지를 새로고침합니다.

💥 스테이트리스(Stateless) 충격:

  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 경고 배포

이 단계에서는 IAM Credentials API (iam: true)를 통해 단기 서명된 URL을 사용하여 ActiveStorage를 Google Cloud Storage (GCS)로 전환하여 미디어 및 파일 스토리지를 컨테이너 디스크에서 분리합니다.

구성을 수정하기 전에 Rails가 GCS와 통신하는 데 필요한 세 가지 사항을 이해하세요.

① 프로젝트 ID + 버킷 이름 — Rails는 blob을 저장할 위치를 알아야 합니다. 앱이 환경에서 GOOGLE_CLOUD_PROJECT을 읽고 버킷 이름을 ${GOOGLE_CLOUD_PROJECT}-activestorage-prod로 파생합니다. 1단계의 Terraform에서 이미 이 버킷을 만들었으므로 추가 작업이 필요하지 않습니다.

② 적절한 권한이 있는 서비스 계정 - Cloud Run은 Compute Engine 기본 서비스 계정 (PROJECT_NUMBER-compute@developer.gserviceaccount.com)으로 실행됩니다. 이 SA에는 버킷에 다음 두 가지 IAM 역할이 필요합니다.

  • roles/storage.objectAdmin - blob 업로드 및 제공
  • 자체에서 roles/iam.serviceAccountTokenCreator — IAM Credentials signBlob API를 호출하고 단기 서명된 URL을 생성합니다.

⚠️ 유혹적인 바로가기 —

public: true GCS를 작동시키는 가장 빠른 방법은 storage.yml에서 public: true을 설정하고 버킷에 allUsers:objectViewer을 부여하는 것입니다. 이미지가 즉시 로드되며 서명이 필요하지 않습니다. 이렇게 하지 마세요. 공개 버킷은 업로드된 모든 사용자 미디어를 개방형 인터넷에 영원히 유출합니다. Google 청사진은 iam: true를 사용하여 버킷을 100% 비공개로 유지합니다. 모든 이미지 URL은 IAM Credentials API에 의해 즉석에서 생성된 서명된 만료 토큰입니다.

③

ACTIVE_STORAGE_SERVICE=google - 런타임에 Rails를 로컬 디스크에서 GCS로 전환하는 단일 환경 변수입니다. 코드 변경도, 애플리케이션 로직 재배포도 필요하지 않습니다. 환경 변수만 전환하면 됩니다.

1. 타임머신 되감기

타임머신을 2단계로 진행합니다.

just workshop-rewind 2

blog/config/storage.yml를 검사하면 앱이 동일한 보안 패턴을 사용하는 이름이 지정된 GCS 환경 3개 (dev / test / prod)를 정의하는 것을 확인할 수 있습니다.

<% gcp_project = ENV.fetch("GOOGLE_CLOUD_PROJECT") { Rails.application.credentials.dig(:gcs, :project) } %>
<% gcs_signer_sa = ENV.fetch("GCS_SIGNER_SA_EMAIL") { "rails-cloudrun-sa@#{gcp_project}.iam.gserviceaccount.com" } %>

google_prod: &google_prod
  service: GCS
  project: <%= gcp_project %>
  bucket: <%= gcp_project %>-activestorage-prod  # ← derived from project ID, no extra env var!
  iam: true        # Sign URLs via IAM Credentials signBlob API (no private key JSON required!)
  gsa_email: <%= gcs_signer_sa %>

# Convenience alias — ACTIVE_STORAGE_SERVICE=google points here
google:
  <<: *google_prod

GCS IAM 서명 아키텍처

2. IAM 스토리지 및 서명 권한 부여

Cloud Run 런타임 서비스 계정에 URL에 서명하고 객체를 업로드할 수 있는 권한이 있는지 확인합니다.

export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"

# Grant Storage Object Admin
gcloud storage buckets add-iam-policy-binding gs://$GCS_BUCKET \
  --member="serviceAccount:$RUN_SA" \
  --role="roles/storage.objectAdmin"

# Grant Service Account Token Creator (required for IAM signed URLs)
gcloud iam service-accounts add-iam-policy-binding $RUN_SA \
  --member="serviceAccount:$RUN_SA" \
  --role="roles/iam.serviceAccountTokenCreator"

3. 세 번째: GCS가 연결된 Cloud Run에 배포

GCS를 사용 설정하여 다시 배포합니다. blog/ 폴더 내에 있는지 확인합니다. 그렇지 않으면 Cloud Run에서 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. 블로그 URL을 새로고침합니다.
  2. '클라우드 생존'이라는 제목의 새 블로그 게시물을 만들고 사진을 업로드합니다.
  3. 이미지의 오른쪽 하단을 확인합니다.
    • 🏠 스탬프가 사라졌습니다.
    • 다채로운 Cloud GCS 스탬프 (nanobanana_stamp_cloud.png)로 대체됩니다.
  4. 다른 컨테이너 다시 시작을 강제 실행합니다 (네 번째 버전이 트리거됨).
    gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s)
    
  5. 페이지를 새로고침합니다. SQLite 데이터베이스가 재설정되는 동안 이미지 바이너리는 Google Cloud Storage에 안전하게 보관됩니다. CLI를 통해 확인합니다.
    gcloud storage ls -l -r gs://$GCS_BUCKET/
    

다음은 프로덕션 버킷 (gs://$GCS_BUCKET)의 Google Cloud Storage 브라우저 콘솔에 표시될 수 있는 업로드된 BLOB 및 미디어 객체의 목록입니다.

영구 이미지 BLOB을 보여주는 Google Cloud Storage 콘솔 버킷 세부정보

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단계에서 이 문제를 제대로 해결할 것입니다. 웹 트래픽과 독립적으로 Solid Queue를 실행하는 전용 worker 컨테이너가 있는 프로덕션 등급 멀티 컨테이너 사이드카 아키텍처로 전환할 때입니다.

6. 자동화된 4단계 검증

GCS 구성 및 중단된 작업 원격 분석을 확인합니다.

just workshop-eval 4

7. 5단계: 사전 비행 체크리스트: Cloud SQL, 보안 비밀, DB 연결

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 컨테이너는 데이터베이스를 공개 인터넷에 노출하지 않고 암호화된 사이드카 프록시 (127.0.0.1:5432)를 통해 Cloud SQL에 안전하게 연결됩니다 (0.0.0.0/0).

ℹ️ 참고: 이 단계는 완전히 선택사항입니다. Cloud Run에서 공식 Cloud SQL 인증 프록시 사이드카 컨테이너는 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에서 프로비저닝된 보안 비밀 검사

일반 텍스트 데이터베이스 비밀번호, API 키 또는 Rails 마스터 키를 git 또는 컨테이너 환경 변수에 저장하지 마세요. 최신 클라우드 아키텍처에서는 제로 트러스트 런타임 삽입을 위해 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는 보안을 위해 gitignore되므로 로컬 클론에 아직 없을 수 있습니다. 이 사용자 친화적인 자체 복구 스니펫을 실행하여 Secret Manager에서 가져오면 로컬 Rails 도구가 사용자 인증 정보를 복호화할 수 있습니다.

if [ ! -f blog/config/master.key ]; then
  echo "📥 Pulling rails-master-key from Secret Manager to local blog/config/master.key..."
  gcloud secrets versions access latest --secret=rails-master-key > blog/config/master.key
  echo "🔑 Created local blog/config/master.key! 🎉"
else
  echo "✅ Local blog/config/master.key already exists! 🚀"
fi

🧪 실험실: 얼마나 쉬운지 확인하기 위해 맞춤 보안 비밀을 만들고 싶으신가요? Secret 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 인증 프록시 보안 비교

5. 자동 실행 전 검사

자동화된 5단계 테스트 모음 도구를 실행하여 데이터베이스를 실행할 수 있고, 보안 비밀이 비어 있지 않으며, IAM 권한이 녹색인지 확인합니다.

just workshop-eval 5

8. 6단계: 4 - 엔터프라이즈 다중 컨테이너 사이드카 (골드 표준) 배포

이제 멀티 컨테이너 Cloud Run이라는 궁극적인 참조 아키텍처를 조립해 보겠습니다.

1. 최우선 기준 복원

저장소 구성을 표준 main 상태로 복원합니다.

just workshop-restore-gold

2. 3컨테이너 프로덕션 청사진 검사

blog/compose.prod.yaml를 열고 아키텍처를 검사합니다.

Cloud Run 멀티 컨테이너 프로덕션 아키텍처

graph LR
  Client[Internet / HTTPS Client] -->|Port 8080| Web[Web Container: Puma]
  Web -->|Localhost:5432| Proxy[Sidecar: Cloud SQL Auth Proxy]
  Worker[Worker Container: Solid Queue] -->|Localhost:5432| Proxy
  Proxy -->|IAM mTLS Tunnel| CloudSQL[(Google Cloud SQL PostgreSQL)]

프로덕션 배포는 동일한 로컬 네트워크를 공유하는 세 개의 조정된 컨테이너를 실행합니다.

  1. web: Puma를 통해 포트 8080에서 HTTP 트래픽을 제공합니다.
  2. worker: bundle exec rails solid_queue:start을 실행하는 전용 백그라운드 처리 컨테이너입니다.
  3. cloudsql-proxy: 127.0.0.1:5432에 바인딩된 공식 Google Cloud SQL 인증 프록시 사이드카 (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2)입니다.

3. Cloud Run 작업을 통해 데이터베이스 마이그레이션 실행

웹 트래픽을 새 배포로 라우팅하기 전에 Cloud SQL에 대해 데이터베이스 마이그레이션과 초기 시딩을 실행해야 합니다.

⚖️ 컨테이너에서 마이그레이션을 실행하는 방법: 두 가지 전략

컨테이너화된 클라우드 환경에서 데이터베이스 마이그레이션을 실행하는 일반적인 방법에는 두 가지가 있습니다.

  • 옵션 A: Docker 진입점에서 자동 (컨테이너 부팅 시 entrypoint.sh가 db:migrate 실행)
    • 장점: 완전 자동화되어 개발자가 명령어를 실행하는 것을 기억하지 않아도 됩니다.
    • 단점: 서버리스 자동 확장에서는 위험합니다. 트래픽이 많은 경우 Cloud Run이 최대 10개의 인스턴스로 동시에 확장되면 10개의 컨테이너가 동시에 마이그레이션을 실행하기 위해 경쟁하여 데이터베이스 잠금, 스키마 경합 상태 또는 부팅 시간 초과가 발생합니다.
  • 옵션 B: Cloud Run 작업을 통한 명시적 방법 (Google의 아키텍처 선택)
    • 장점: 완전한 제어, 트랜잭션 안전, 경합 상태 없음 웹 트래픽 제공에서 완전히 분리된 전용 일회성 컨테이너 작업으로 실행됩니다. 마이그레이션이 실패해도 웹 제공에는 영향을 미치지 않습니다.
    • 단점: 배포 전에 CLI 작업 또는 CI/CD 단계를 트리거해야 합니다.

최대 프로덕션 안정성을 위해 옵션 B를 선택합니다.

⚠️ 심각: DATABASE_URL 형식 및 Ruby 3.4.5 RFC 3986 엄격성

Cloud Run Jobs는 네이티브 Unix 소켓 프록시 (/cloudsql/PROJECT:REGION:INSTANCE)를 사용하여 Cloud SQL에 연결합니다. DATABASE_URL은 삼중 슬래시 형식을 사용해야 합니다.

postgresql:///rails_production?user=rails_user&password=PASS&host=/cloudsql/PROJECT:REGION:INSTANCE
# Core environment variables for database migration & initial admin seed
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
echo "Admin user will be seeded for: $GOOGLE_CLOUD_ACCOUNT"

# Get the latest blog image from Cloud Run (jobs don't support --source)
export BLOG_IMAGE=$(gcloud run services describe blog --region=$GOOGLE_CLOUD_REGION --format='value(spec.template.spec.containers[0].image)')

# Build the DATABASE_URL in triple-slash format (required for Unix socket proxy)
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_CONNECTION="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
export DB_URL="postgresql:///rails_production?user=rails_user&password=${DB_PASSWORD}&host=/cloudsql/${CLOUDSQL_CONNECTION}"

# Create migration job — note ALL 4 database URL env vars and GOOGLE_CLOUD_ACCOUNT for seed user creation
gcloud run jobs create rails-migrate \
  --image=$BLOG_IMAGE \
  --command "bin/rails" \
  --args "db:prepare" \
  --set-env-vars="DATABASE_URL=${DB_URL},DATABASE_QUEUE_URL=${DB_URL},DATABASE_CACHE_URL=${DB_URL},DATABASE_CABLE_URL=${DB_URL},GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT},GOOGLE_CLOUD_ACCOUNT=${GOOGLE_CLOUD_ACCOUNT}" \
  --set-secrets="RAILS_MASTER_KEY=rails-master-key:latest" \
  --set-cloudsql-instances="${CLOUDSQL_CONNECTION}" \
  --service-account=$RUN_SA \
  --region $GOOGLE_CLOUD_REGION 2>/dev/null || true

# Execute migration job
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait

💡 Rails 8 다중 데이터베이스: 앱은 database.yml당 4개의 데이터베이스 (기본, 대기열, 캐시, 케이블)를 사용합니다. 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

rails-migrate 작업이 성공한 것으로 표시된 Google Cloud Run 작업 콘솔Google Cloud Run 작업 콘솔에

rails-migrate

실행 성공

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

3개의 사이드카 컨테이너를 표시하는 Google Cloud Run 콘솔 컨테이너 탭Google Cloud Run 콘솔: 모든 세 개의 사이드카 컨테이너를 표시하는 '컨테이너' 탭 (

cloudsql-proxy

,

web

,

worker

)

💡 UI 호기심 / 콘솔 글리치: 콘솔 드롭다운에서 Cloud Run이 상단 항목을 Ingress container: cloudsql-proxy로 라벨링하는 것을 확인할 수 있습니다. 걱정하지 마세요. 이는 컨테이너 종속 항목 순서로 인해 발생하는 Google Cloud 콘솔의 순수한 시각적 UI 결함입니다 (#149에서 추적됨). 실제로 web은 HTTP 포트 8080에서 수신 대기하고 인그레스 웹 트래픽을 100% 처리하는 유일한 컨테이너인 반면 cloudsql-proxy은 포트 5432에서 비공개 PostgreSQL 트래픽만 처리합니다.

5. ✨ 놀라운 순간: 경고가 없고 클라우드 지속성이 완벽합니다.

심호흡을 하고 화면을 살펴보세요. 모든 경고 배너가 사라졌습니다! 🪄

경고가 없고 Cloud 영구 게시물이 있는 Cloud Run의 프로덕션 블로그프로덕션 Rails 8 블로그: 깨끗한 UI, 경고 배너 없음, 지속적인 Cloud SQL 상태

이제 애플리케이션이 공식적으로 프로덕션 등급 및 클라우드 네이티브가 되었습니다.

  • 보안 mTLS 터널링을 통해 관리형 Google Cloud SQL (PostgreSQL)로 지원됩니다.
  • IAM blob 서명을 사용하여 비공개 Google Cloud Storage에 안전하게 저장된 미디어
  • 임시 스토리지 경고가 0개이고 중단된 작업 알림이 0개입니다.

Cloud Run URL을 엽니다.

  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 마스코트

좋은 소식은 이 기능이 코드베이스에 이미 완전히 구현되어 있다는 것입니다. 코드를 작성할 필요는 없습니다. 여기서는 비동기 클라우드 생성형 AI 파이프라인이 작동하는 것을 트리거하고 관찰하면 됩니다.

🧠 백그라운드에서 작동하는 방식

  • 인증: API 키가 필요하지 않습니다. Cloud Run 런타임 서비스 계정을 통해 애플리케이션 기본 사용자 인증 정보 (Vertex AI에서는 roles/aiplatform.user)를 자동으로 사용합니다. (또는 GEMINI_API_KEY가 제공되면 Gemini API를 직접 호출합니다.)
  • 파이프라인: 커버 이미지 없이 아티클이 생성되면 after_commit 콜백이 GenerateCoverImageJob를 Solid Queue에 대기열에 추가합니다.
  • 모델: Google의 멀티모달 모델 (Gemini 2.5 Flash Image / Vertex AI의 Imagen)에 카메오 바나나와 반짝이는 루비 '8'이 포함된 1960년대 이탈리아 영화 포스터 ('Locandina di un film 1960')를 생성하라는 메시지를 표시합니다.
  • 동적 출처 워터마크: libvips를 사용하여 모서리에 시각적 배지를 동적으로 스탬프합니다 (GCS의 다채로운 클라우드 스탬프).
  • 터보 스트림 핫스왑: 이미지가 첨부되면 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 (웹 UI): 새 게시물을 클릭하고 'Ruby on Rails 8을 사용한 서버리스 아키텍처'와 같은 제목을 입력하고 콘텐츠를 작성한 다음 커버 이미지 파일 입력을 비워 둡니다. 게시물 만들기를 클릭합니다.
  • 옵션 B (CLI 러너): 터미널에서 실행합니다.
    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): 카메오 바나나와 루비 8이 등장하는 AI 생성 1960년대 이탈리아 영화 포스터가 포함된 블로그 게시물의 스크린샷 추가

2. 이중 언어 팟캐스트 생성기 퀘스트 (실습형 무중력 코딩 연습)

⚠️ 중요: 위의 커버 생성기와 달리 이 기능은 아직 구현되지 않았습니다.

blog/app/jobs/podcastifier_job.rb를 열면 빈 스캐폴드 스텁이 표시됩니다.

class PodcastifierJob < ApplicationJob
  queue_as :default

  def perform(post_id)
    # TODO(student): COMPLETE_ME — Pair program with Google Antigravity to implement
    # Italian TTS podcast generation using voice 'it-IT-Wavenet-A' via Cloud TTS / ADC!
  end
end

이 실습의 미션은 Google Antigravity와 페어 프로그래밍하여 다음 기능을 완료하는 것입니다.

  1. 프롬프트:'app/jobs/podcastifier_job.rb에서 PodcastifierJob을 구현하도록 도와줘. 게시물을 가져오고, Google Cloud Text-to-Speech를 사용하여 이탈리아어 오디오 개요를 번역하거나 합성하고 (ADC 사용자 인증 정보를 통해 언어 코드 'it-IT'가 있는 음성 'it-IT-Wavenet-A' 사용), ActiveStorage를 사용하여 오디오 파일을 post.podcast_audio_it에 첨부해야 합니다.'
  2. 뷰에 플레이어 추가: app/views/posts/show.html.erb에서 @post.podcast_audio_it.attached?인 경우 HTML5 플레이어를 추가합니다.
  3. 합성 트리거: 저장되면 Solid Queue가 백그라운드에서 텍스트 음성 변환 작업을 비동기식으로 실행합니다.

이중 오디오 플레이어가 있는 Podcastifier 이중 언어 UI

🎧 샘플 Podcastifier 출력:

💡 도움이 필요하거나 문제가 발생했나요? 전투 테스트를 거친 완전한 참조 솔루션은 solutions/podcastifier 브랜치에서 확인할 수 있습니다. git diff solutions/podcastifier를 사용하여 언제든지 검사할 수 있습니다.

3. [선택사항] 🏴‍☠️ GCS 보물찾기 (콘솔 Blob 복구)

ℹ️ 기본 요건 확인: 로컬

cloud-sql-proxy

CLI 필요 이 선택사항 연습에서는 암호화된 mTLS 터널을 통해 로컬 노트북을 Google Cloud SQL에 직접 연결합니다. CLI 프록시가 설치되어 있는지 확인합니다.

which cloud-sql-proxy
  • 설치된 경우: 아래로 진행하세요.
  • 설치되어 있지 않고 이 퀘스트를 진행하려는 경우: 다음 명령어를 사용하여 설치합니다.
    # Linux (x86_64)
    curl -o /tmp/cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.0/cloud-sql-proxy.linux.amd64
    chmod +x /tmp/cloud-sql-proxy && sudo mv /tmp/cloud-sql-proxy /usr/local/bin/cloud-sql-proxy
    # macOS (Homebrew)
    brew install cloud-sql-proxy
    
  • 그 외: 7단계 자동 검증으로 건너뛰어도 됩니다.

컨테이너가 다시 시작되기 전에 4단계에서 업로드한 사진을 기억하시나요? 컨테이너가 다시 시작되면 임시 SQLite 데이터베이스가 삭제됩니다. 해당 이미지 파일은 여전히 비공개 GCS 버킷에 '고아 blob'으로 안전하게 보관되어 있습니다.

Rails 콘솔을 사용하여 이를 복구하고 Cloud SQL 게시물에 연결해 보겠습니다.

# Connect to your production database via Rails console and Cloud SQL Proxy
cloud-sql-proxy --port 5432 ${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME} &
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production bin/rails c

Rails 콘솔 내부:

# 1. Search for orphaned GCS blobs in ActiveStorage
orphan_blobs = ActiveStorage::Blob.where.missing(:attachments)
puts "🏴‍☠️ Found #{orphan_blobs.count} orphaned GCS blobs!"

# 2. Attach the surviving blob to your latest post
if orphan_blobs.any?
  post = Post.last
  post.cover_image.attach(orphan_blobs.first)
  post.save!
  puts "🎉 Rescued blob #{orphan_blobs.first.filename} attached to '#{post.title}'!"
end
exit

📸 TODO(riccardo): GCS Treasure Hunt 스니펫을 실행하고 고아 ActiveStorage 블롭을 복구하는 대화형 Rails 콘솔의 스크린샷 추가

블로그를 새로고침합니다. 4단계의 Stateless Shock 중에 업로드된 사진이 되살아나 Cloud SQL 게시물에 영구적으로 첨부됩니다.

4. 자동화된 7단계 검증

생성형 AI 작업 및 애셋을 확인합니다.

just workshop-eval 7

10. 8단계: 나만의 모험 선택하기 / 고급 퀘스트 🏆

이제 표준 참조 아키텍처를 숙달했으므로 졸업 퀘스트를 선택하세요.

🛡️ 퀘스트 1: 제로 트러스트 Google IAP (Identity-Aware Proxy)

  • 난이도: 중간 (엔터프라이즈 보안)
  • 목표: 인증된 Google / Gmail 계정만 액세스할 수 있도록 Cloud Run 애플리케이션을 잠가 비밀번호 로그인을 완전히 없앱니다.
  • 작동 방식:
    1. 트래픽이 Cloud Run에 도달하기 전에 외부 HTTPS 애플리케이션 부하 분산기가 Google OAuth를 종료하고 ID를 확인합니다.
    2. Google은 확인된 ID 헤더 (X-Goog-Authenticated-User-Email)를 전달합니다.
    3. Rails는 blog/app/controllers/concerns/iap_authenticatable.rb를 통해 인증된 Google ID를 자동으로 로그인합니다.
      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으로 프로덕션 예외를 실시간으로 포착합니다.
  • 작동 방식:
    • 구조화된 JSON 로그를 GCP 추적 라벨과 함께 내보내도록 blog/config/environments/production.rb를 구성합니다.
    • 의도적인 테스트 예외를 트리거하면 Google Cloud Error Reporting에서 즉시 그룹화하고 알림을 보냅니다.

📸 TODO(riccardo): 그룹화된 프로덕션 예외와 추적 라벨이 있는 Cloud Logging 구조화된 JSON 페이로드를 보여주는 Google Cloud Error Reporting 스크린샷 추가

🧠 Quest 3: pgvector 시맨틱 검색 및 Gemini RAG

  • 난이도: 어려움 (생성형 AI 캡스톤)
  • 목표: Cloud SQL의 PostgreSQL에 저장된 벡터 임베딩을 사용하여 개념적으로 문서를 검색합니다.
  • 작동 방식:
    • Cloud SQL PostgreSQL 인스턴스에서 vector 확장 프로그램을 사용 설정합니다.
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • blog/Gemfile에 neighbor gem을 추가하고 Post#after_save에서 Gemini (text-embedding-004)로 텍스트 임베딩을 생성합니다.
    • 코사인 거리 쿼리 (<=>)를 실행하여 Turbo Streams로 시맨틱 검색창을 지원하세요.

📸 TODO(riccardo): 코사인 유사도 점수와 함께 개념적 일치 항목을 반환하는 라이브 블로그 시맨틱 검색창의 스크린샷 추가

⚡ 퀘스트 4: SEO 및 성능 감사 어시스턴트

  • 난이도: 쉬움 / 재미있음 (개발자 환경)
  • 목표: Antigravity 및 Speedgrapher 도구를 사용하여 Core Web Vitals, 최대 콘텐츠 페인트 (LCP), Flesch-Kincaid / Fog 가독성 지수를 감사합니다.

📸 TODO(riccardo): Speedgrapher / Lighthouse SEO 및 Core Web Vitals 감사 요약 스크린샷 추가

🤔 호기심 상자: Cloud Run에서 실시간 Rails 콘솔 가져오기

💡 질문: 기존 VM 또는 Heroku 스타일 PaaS 플랫폼에서 개발자는 일반적으로 실행 중인 인스턴스에 SSH로 연결하거나 heroku run rails console를 사용하여 프로덕션 데이터를 실시간으로 검사하거나 수정합니다. Cloud Run 네이티브에 상응하는 항목은 무엇이며 어떻게 강화하나요?

이유

gcloud run exec

Cloud Run 서비스에는 존재하지 않음

Cloud Run은 서버리스 플랫폼입니다. 컨테이너는 안정적인 ID나 주소 지정 가능한 셸 없이 0개, 1개 또는 수백 개의 동일한 인스턴스로 실행될 수 있습니다. 이는 연결할 수 있는 명명된 소켓이 특정 포드에 있는 Kubernetes (kubectl exec)와 근본적으로 다릅니다.

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

4가지 GCP 네이티브 패턴

패턴

명령어

대화형인가요?

보안

1. 로컬 프록시 + 원격 DB

DATABASE_URL=... rails console

✅ 전체

🔒 Cloud SQL 인증 프록시 mTLS

2. Cloud Run 작업 (일회성)

gcloud run jobs execute --wait

❌ 스크립트만

🔒 IAM 역할 필요

3. Web Console gem + IAP

브라우저 → /rails/console

✅ 전체

🛡️ IAP 제로 트러스트

4. Cloud Shell + 프록시

Cloud Shell → bin/rails c

✅ 전체

🔒 Google ID

패턴 1 (데이터 검사에 권장): Cloud SQL 인증 프록시를 통해 Cloud SQL PostgreSQL과 직접 통신하는 로컬 Rails 콘솔을 실행합니다.

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

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

네트워크 노출 없이 완전한 IRB 상호작용을 얻을 수 있습니다. Cloud SQL 프록시는 Unix 도메인 소켓 또는 루프백을 통해 mTLS를 사용합니다.

패턴 3 (웹 콘솔 + IAP 제로 트러스트, 강화된 브라우저 경로):

web-console gem은 브라우저 기반 IRB 세션을 제공합니다. 보호 기능이 없으면 심각한 보안 취약점이므로 공개적으로 노출하지 마세요. 하지만 Google IAP (Quest 1)와 결합하면 공격 표면이 축소됩니다. 허용 목록에 있는 확인된 Google ID만 /rails/console 엔드포인트에 도달할 수 있습니다.

# Gemfile — only load in development/staging, never production without IAP!
gem "web-console", group: :development

# config/environments/production.rb — if IAP is enabled:
config.web_console.allowed_ips = ["0.0.0.0/0"]  # IAP terminates auth before us

⚠️ 패턴 3을 사용하려면 먼저 Quest 1 (IAP)을 사용 설정해야 합니다. 이 기능이 없으면 공개 URL의 /rails/console는 서버실을 잠그지 않는 것과 같습니다. IAP를 사용하면 Google 등급 제로 트러스트로 강화된 강력한 운영자 도구가 됩니다.

Antigravity에 질문하기: '

rails console --sandbox

Cloud SQL 인증 프록시를 사용하여 Cloud SQL 프로덕션 데이터베이스에 안전하게 연결할 수 있습니다.'

🏆 The Hive 졸업: 감독관 검증 작업 증명

선택한 퀘스트를 완료한 후 The Hive 리더보드에서 8단계 졸업 트로피 🏆를 수령합니다.

  1. GitHub에 퀘스트 제출:
    • 공식 저장소(github.com/palladius/rails8-app-on-gcp/issues/new)에서 새 문제를 엽니다.
    • 제목 형식: 🎓 [Step 8 Completed] :
    • 본문에 달성한 내용에 대한 간단한 설명과 라이브 Cloud Run 배포 링크를 포함합니다.
  2. Cloud Run 앱을 문제에 연결:
    • 문제 번호 (예: 88 또는 전체 URL https://github.com/palladius/rails8-app-on-gcp/issues/88)를 기록합니다.
    • STEP_8_GHI 환경 변수를 Cloud Run 서비스에 삽입합니다.
      gcloud run services update blog \
        --region europe-west1 \
        --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER>
      
    • 이제 /status.json 엔드포인트에서 퀘스트를 보고하는지 확인합니다.
      curl -s https://<YOUR_APP>.run.app/status.json | jq .quest
      
      다음과 같이 표시됩니다.
      {
        "step_8_completed": true,
        "ghi_issue": 88,
        "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88"
      }
      
  3. 감독관 검토를 받고 골든 트로피 획득:
    • The Hive 리더보드를 확인합니다. 이제 행에 ⏳ GHI #XX review pending 주황색 배지가 표시됩니다.
    • 워크숍 감독관 (@palladius, @emilianodellacasa, @ricc)에게 배포를 검사해 달라고 요청합니다.
    • 감독관이 문제에 대해 LGTM 댓글을 달면 The Hive에서 자동으로 진행 상황을 8/8 🏆로 승격하고 금색/보라색 빛나는 배지와 캡스톤 문제로 직접 연결되는 영구 트로피를 표시합니다.

11. 결론 및 정리 🎓

수고하셨습니다 🎉

Google Cloud Platform에서 프로덕션 등급의 엔터프라이즈 지원 Rails 8 애플리케이션을 빌드하고 배포했습니다.

  • 제로 브랜치 진행: main에서 타임머신 오버레이를 사용하여 최신 워크플로를 숙달했습니다.
  • Cloud Storage: IAM blob 서명 (iam: true)이 있는 확장 가능한 비공개 객체 스토리지입니다.
  • Cloud SQL: Cloud SQL 인증 프록시 mTLS 터널로 보안이 유지되는 관리형 PostgreSQL입니다.
  • Secret Manager: 일반 텍스트 사용자 인증 정보 또는 .env 파일 유출이 없습니다.
  • Cloud Run 멀티 컨테이너: 격리된 Puma 웹, Solid Queue 작업자, 프록시 사이드카
  • 생성형 AI: Vertex AI를 사용한 비동기식 빈티지 포스터 생성 및 TTS 팟캐스트 합성
  • 퀘스트: 제로 트러스트 IAP, SRE 관측 가능성, pgvector 임베딩

🧹 리소스 정리

워크숍을 완료한 후에도 요금이 계속 청구되지 않도록 하려면 Google Cloud 리소스를 정리하세요. 환경에 가장 적합한 접근 방식을 선택하세요.

다른 리소스나 데이터가 있는 기존 또는 공유 Google Cloud 프로젝트를 사용하는 경우 이 옵션을 사용하세요. 이 워크숍에서 프로비저닝된 리소스 (Cloud Run, Cloud SQL, Secret Manager, GCS 버킷)만 선택적으로 삭제하여 나머지 프로젝트는 그대로 유지합니다.

cd iac
terraform destroy -auto-approve

옵션 2: 프로젝트 완전 삭제 (흔적 남기지 않기 🌪️)

전용 워크숍 프로젝트 (예: 워크숍 크레딧 또는 샌드박스 사용)를 만들었고 로그, 메타데이터, 서비스 계정, 결제 링크를 비롯한 흔적이 전혀 남지 않도록 하려면 이 옵션을 사용하세요.

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet