Rails 8 на Google Cloud: от нуля до искусственного интеллекта

1. Введение

Добро пожаловать на мастер-класс по Rails 8 в Google Cloud ! В этом практическом занятии вы превратите современное приложение Rails 8 из простой локальной SQLite-базовой архитектуры в готовую к использованию в корпоративной среде архитектуру на платформе Google Cloud.

На этом семинаре мы не просто развернем приложение — мы осуществим целенаправленную модернизацию облачной инфраструктуры до производственного уровня ( правильное внедрение технологии «подъем и перенос»! ).

Вы начинаете с типичной локальной конфигурации "работает на моей машине" (временный диск, встроенный SQLite, выполняющиеся в процессе задачи) и постепенно развиваете ее в надежную корпоративную облачную архитектуру на платформе Google Cloud:

  1. 🐘 Переход от локального SQLite к облачному SQL (PostgreSQL): больше никаких сбросов базы данных при перезапуске. Мы переходим на управляемый PostgreSQL, подключенный безопасно через Cloud SQL Auth Proxy (mTLS, без публичных IP-адресов).
  2. 🪣 От временного локального хранилища → Частное хранилище Google Cloud Storage: Бессерверный диск является временным. Мы подключаем ActiveStorage к частному хранилищу Google Cloud Storage с помощью URL-адресов, подписанных IAM ( iam: true ) — никаких публичных корзин, никаких утечек ключей.
  3. 🚀 От незавершенных задач → Многоконтейнерный облачный запуск: Мы соединяем монолит Rails и бессерверное облако! Используя стандартный Docker Compose , мы запускаем Solid Queue в качестве выделенного рабочего контейнера вместе с Puma в многоконтейнерном поде Cloud Run — никакого разрастания микросервисов, никакого голодания потоков, DHH был бы горд!

🍌 А поскольку мы живем в 2026 году, мы значительно улучшаем всю систему с помощью Google Antigravity . Это не просто дополнительная функция, у нас есть подсказки и навыки, которые помогут вам лучше освоить (и получить удовольствие) от этого мастер-класса!

🏛️ Целевая эталонная архитектура (Вариант 1: Clean Flat Vector Enterprise)

Вот каноническая эталонная архитектура Google Cloud, которую вы будете создавать и развертывать ( Вариант 1: Clean Flat Vector Enterprise ), демонстрирующая наш многоконтейнерный сервис Cloud Run, работающий в производственной среде и подключенный ко всем управляемым сервисам Google Cloud для хранения данных и искусственного интеллекта:

Вариант 1: Чистая плоская векторная корпоративная архитектура — Rails 8 на облачной платформе Google.

🎞️ Архитектурная эволюция: от нуля до облачных технологий

Наблюдайте, как наша инфраструктура постепенно модернизируется от временной базовой конфигурации на одной машине до полностью управляемой платформы Google Cloud на каждом этапе семинара:

Мастер-класс по архитектурной эволюции

Что вы узнаете

  • Как использовать Google Antigravity для парного программирования, чтобы разобраться в Rails 8 и Google Cloud .
  • Как запустить автоматизированную предполетную диагностику ( just workshop-test ).
  • Как асинхронно развернуть инфраструктуру Google Cloud с помощью Terraform , продолжая локальную разработку без блокировки со стороны провайдера google .
  • Как устранить антипаттерны безопасности: частные хранилища GCS ( iam: true ) и туннели mTLS Cloud SQL Auth Proxy вместо открытия 0.0.0.0/0 .
  • Как внедрять секреты напрямую из Google Cloud Secret Manager .
  • Как организовать асинхронные фоновые задачи GenAI (генератор обложек NanoBanana, синтез двуязычных подкастов) с помощью Solid Queue .

Давайте начнём!

2. Шаг 0: Предварительные условия, настройка Antigravity и проверка платежных данных.

💡 Ситуация: Вы и ваша команда разрабатываете критически важное приложение на Rails 8. Прежде чем приступать к написанию кода или запуску облачных ресурсов, необходимо настроить цепочку инструментов, подключить Google Antigravity и проверить учетные данные Google Cloud и систему выставления счетов.

1. Контрольный список необходимых условий

Прежде чем начать, убедитесь, что в вашей рабочей среде доступны следующие инструменты:

  • just (1.21+): ( just --version ) средство запуска задач — каждая команда в этом мастер-классе является just рецептом (попросите ваш AI-инструмент установить его или см. skills/rails8app-workshop ).
  • Git (версия 2.30+): ( git --version ) для контроля версий, создания ветвей и клонирования репозитория.
  • Google Cloud SDK ( gcloud CLI): установлен и обновлен.
  • Terraform CLI (1.5+): Для декларативного развертывания инфраструктуры.
  • docker и docker-compose : установлены и запущены локально.
  • Ruby 3.4.5 : ( ruby -v — зафиксировано в blog/.ruby-version ; попросите ваш AI-инструмент установить 3.4.5 , если он отсутствует). Rails 8 объявлен в Gemfile и устанавливается автоматически командой bundle install .
  • Google Antigravity 2.0: Ваш автономный помощник по парному программированию на основе искусственного интеллекта ( скачать 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. Клонируйте репозиторий и подключитесь к антигравитации.

Клонируйте репозиторий и перейдите в указанную директорию. Обратите внимание, что мы полностью остаёмся в main :

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

Откройте эту директорию в Google Antigravity . Antigravity автоматически проверит репозиторий, прочтёт файл AGENTS.md и будет выступать в роли вашего партнёра по программированию.

5. 🪫 Если в Antigravity закончатся бесплатные кредиты

⚠️ Облачные кредиты, которые вы только что использовали, НЕ пополняют баланс Antigravity. Это две разные системы оплаты: квота Antigravity находится в рамках тарифных планов Google AI (Google One), а ваучеры и облачные кредиты поступают в облачный биллинговый аккаунт . Привязка биллингового аккаунта к вашему проекту GCP не даст вам ни одного дополнительного запроса Antigravity.

Если оператор останавливается с сообщением о превышении квоты, двигайтесь по этой лестнице — первые два варианта бесплатны и мгновенны:

  1. Переключитесь на другой пул моделей. Квота отслеживается для каждого семейства моделей , и Antigravity отображает два независимых пула (модели Gemini и модели Claude/GPT). Модели Claude и GPT-OSS доступны на бесплатном уровне, поэтому исчерпавшийся пул Gemini часто оставляет другой пул нетронутым. Используйте селектор моделей под окном запроса. Селектор моделей Antigravity — переключение между пулами Gemini, Claude и GPT-OSS.
  2. Выбирайте модель Flash вместо Pro. Ограничения по количеству запросов зависят от объема работы, выполняемой агентом за один запрос, поэтому более легкая модель позволит максимально эффективно использовать оставшийся ресурс.
  3. Используйте Antigravity CLI со своим собственным ключом API Gemini. Использование собственного ключа не поддерживается в Antigravity IDE — CLI является документированной альтернативой. Создайте бесплатный ключ в Google AI Studio (требуется проект, но не требуется учетная запись для выставления счетов ), затем загрузите и настройте Antigravity CLI.
  4. Подождите или перейдите на более дорогой тариф . Квота бесплатного уровня обновляется еженедельно (в сообщении об ошибке указана дата сброса). Google AI Pro/Ultra — единственный документально подтвержденный способ повысить базовый уровень в настольном приложении; эти тарифы обновляются каждые пять часов, и для покрытия превышения лимита можно использовать приобретенные кредиты AI.
  5. Хотя это мастер-класс по Antigravity, большинство программных средств для программирования ИИ также должны подойти — просто укажите в своем файле путь к каталогу AGENTS.md и skills/ в репозитории.

6. Автоматизированная проверка на этапе 0

Убедитесь, что ваша среда на этапе 0 на 100% соответствует требованиям ознакомительного пакета:

just workshop-eval 0

✨ Вау-момент: Antigravity и механизм оценки мастерской проверяют ваши версии CLI, среду Ruby и аутентификацию ADC менее чем за 2 секунды!

3. Шаг 1: Запуск инфраструктуры Terraform и предварительная диагностика.

💡 Стратегия: На развертывание управляемых баз данных, таких как Google Cloud SQL PostgreSQL, уходит примерно 10–12 минут. Вместо того чтобы ждать, мы запускаем неизменяемую инфраструктуру с помощью Terraform прямо сейчас в фоновом режиме, пока разрабатываем локально!

1. Запустите автоматизированный комплекс предполетной диагностики.

Перед запуском облачной инфраструктуры выполните комплексный набор предварительных тестов:

just workshop-test

Этот скрипт ( bin/workshop_diagnostics.rb ):

  • Проверяет конфигурацию вашей учетной записи GOOGLE_CLOUD_ACCOUNT .
  • Проверяет наличие активных счетов и связь с проектом.
  • Проверяет учетные данные приложения по умолчанию (ADC) для Vertex AI.
  • Подтверждает наличие тестового образа 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 Provisions:

  • Корзина Google Cloud Storage: создана с приватным доступом и подписью учетных данных IAM ( iam: true ).
  • Тестовое изображение Canary: Загружает файл gcs_dev_image.jpg в хранилище для обеспечения сквозной проверки больших двоичных объектов.
  • Экземпляр Google Cloud SQL PostgreSQL: Инициирует фоновое выделение ресурсов (примерно 10-12 минут).

Экземпляр Cloud SQL PostgreSQL в консоли Google Cloud

💡 Примечание: Предупреждение об отсутствии автоматических резервных копий можно смело игнорировать — они нам не понадобятся для базы данных, используемой в этом мастер-классе (хотя автоматическое ежедневное резервное копирование является важной передовой практикой для любой реальной рабочей базы данных!).

Команда `Terraform apply` успешно завершена, получены следующие результаты.

📝 Запишите URL-адрес вашего Cloud Run: команда Terraform apply должна выполниться успешно и вывести результаты работы вашей инфраструктуры. Запишите свой cloud_run_url (или скопируйте его в буфер обмена) — он понадобится вам на последующих этапах проверки работающего сервиса!

3. Автоматизированная проверка на шаге 1 (необязательно)

Убедитесь, что все предварительные условия и настройки, указанные в Шаге 1, выполнены:

just workshop-eval 1

⏱️ Примечание: Эта проверка проверяет работающие облачные ресурсы и может занять несколько минут. Она совершенно необязательна — пока она выполняется в вашем терминале, вы можете сразу перейти к следующему шагу, не дожидаясь завершения!

4. Шаг 2: Создание локальной базовой конфигурации, Mailpit и ввод в эксплуатацию административной панели.

В качестве отправной точки мы используем современное, чистое приложение для ведения блога на Rails 8, работающее на локальном сервере с SQLite, перехватом электронной почты Mailpit и дисковым хранилищем ActiveStorage.

1. Запустите приложение локально.

📁 Обратите внимание на каталог! В этом репозитории приложение Rails находится в blog/ а Terraform — в iac/ . На шаге 1 вы оказались в корневом каталоге репозитория, поэтому все команды на шаге 2 выполняются из этого каталога.

blog/ — Gemfile , bin/rails и compose.yaml находятся там. Запуск их из корневой папки завершается ошибкой no configuration file provided: not found или «отсутствует Gemfile .

Перейдите в приложение Rails, убедитесь, что вы используете закрепленную ветку Ruby, затем установите гемы и настройте базу данных:

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)? Системный Ruby в чистом Linux полностью игнорирует файлы .ruby-version и пытается установить гемы по всей системе! Убедитесь, что rbenv загружен в вашей активной оболочке терминала:

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

Примечание: Если вы пропустили установку версии 3.4.5 на шаге 0, выполните команду rbenv install 3.4.5 . Или просто используйте режим A (Docker Compose), описанный ниже ( just compose-up ), который запускает закрепленный стек Ruby полностью внутри контейнеров, не затрагивая гемы хоста!

💡 Не хотите об этом думать? just install и just compose-up из корневой директории репозитория, и cd blog сделает это за вас.

2. 🌱 Автоматическое обнаружение «умных» семян и загрузка административной панели (Выпуски № 21 и № 25)

В исходном файле базы данных ( db/seeds.rb ) реализована функция автоматического обнаружения среды (Smart Environment Auto-Discovery ):

  • Программа проверяет активный адаптер базы данных (SQLite или Postgres) и конфигурацию хранилища.
  • Программа определяет нулевой этап (Localhost) , автоматически создает начального пользователя-администратора и размещает учебный пост:
    • [LOCAL BASELINE] Welcome to Rails 8 on Localhost!
    • Встроенное локальное изображение с грустным содержанием ( local_sad_image.png ) с водяным знаком, сообщающим о том, что локальное дисковое хранилище является временным.
  • Это автоматически запускает отправку письма для сброса пароля через ActionMailer.

Проверьте или повторно запустите генератор случайных чисел (автоматически используется ваш GOOGLE_CLOUD_ACCOUNT из .env ):

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

Запустите локальный стек разработки с помощью Docker Compose:

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

Docker Compose запускает Rails, Mailpit и локальный стек.

Теперь приложение, локальный SMTP-сервер Mailpit и средство просмотра базы данных Adminer работают вместе в изолированных контейнерах:

  • Приложение Rails : http://localhost:3000
  • Веб-интерфейс Mailpit : http://localhost:8025
  • Пользовательский интерфейс базы данных администратора : http://localhost:8081.

🧯 Конфликт в порту 3000 или

server.pid

Ошибка? Если вы ранее запускали собственный сервер разработки ( bin/dev ), остановите его с помощью Ctrl+C . Если Docker сообщает, что порт 3000 уже занят, или обнаруживает оставшийся server.pid , выполните очистку с помощью:

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

3. Интерфейс Mailpit и вход в административную панель

  1. Перехват исходящих писем с помощью Mailpit : откройте http://localhost:8025 в браузере. Посмотрите локальный почтовый ящик! Во время выполнения команды db:seed ActionMailer отправил письмо с инструкциями по адаптации администратора, которое Mailpit безопасно перехватил локально, не затрагивая внешние почтовые серверы или учетные данные:

Веб-интерфейс Mailpit отображает перехваченное письмо для сброса пароля. 2. Войдите в блог : откройте http://localhost:3000 в своем браузере.

  • Чтобы установить пароль, вы можете перейти по ссылке для сброса пароля прямо в электронном письме Mailpit.
  • В качестве альтернативы, войдите в систему, используя адрес электронной почты вашей учетной записи Google Cloud (из .env ) и пароль, заданный по умолчанию: Ch4ng3m3!!1 .
  1. Обратите внимание на значки визуальной телеметрии:
    • Обратите внимание на желтый баннер среды и значки в пользовательском интерфейсе: Notice: Ephemeral Database Active (POSTGRESQL) и [EPHEMERAL DB / STORAGE] 💾 Local .
    • Обратите внимание на водяной знак публикации: 🏠-штамп ( nanobanana_stamp_local.png в правом нижнем углу изображения обложки). Он обеспечивает мгновенное визуальное подтверждение того, что ваши ресурсы и база данных в данный момент привязаны к временному локальному хранилищу.

На главной странице локального блога отображаются временные уведомления и значки телеметрии.

💡 Дополнительный совет: тренировка работы с консолью Rails. Хотите узнать, как Rails взаимодействует с вашими данными, введенными через командную строку? Перейдите в интерактивную консоль:

bin/rails console

Проверьте созданного вами пользователя-администратора динамически, используя переменную среды:

user = User.find_by(email_address: ENV.fetch("GOOGLE_CLOUD_ACCOUNT"))
# Or simply grab the first user:
user = User.first
puts "Admin email: #{user.email_address} (created via: #{user.created_via})"
exit

4. Автоматизированная проверка на этапе 2.

Проверьте локальные базовые настройки и параметры администрирования:

just workshop-eval 2

просто результат проверки workshop-eval 2

5. Подвох: контейнеры без сохранения состояния

Контейнеры Cloud Run не сохраняют состояние и являются временными. Если мы развернем нашу базу данных SQLite и локальное storage/ каталог непосредственно в Cloud Run, все сообщения и загруженные изображения будут безвозвратно удалены при каждом масштабировании контейнера до нуля или его перезапуске.

На следующем этапе мы намеренно развернем эту временную конфигурацию в Cloud Run, чтобы воочию убедиться в эффекте Stateless Shock !

5. Шаг 3: Развертывание 1 — Шок без сохранения состояния (Впечатляющий результат за 3 минуты!)

Вместо того чтобы ждать 15–20 минут, пока облачные базы данных обработаются, прежде чем что-либо отобразится в интернете, современная бессерверная разработка начинается с ранней победы: развертывания нашего одноконтейнерного приложения Rails непосредственно в Google Cloud Run менее чем за 3 минуты!

1. Машина времени: перемотка назад

Чтобы гарантировать, что наша начальная конфигурация на 100% временная (локальная база данных SQLite и локальное файловое хранилище на диске), используйте механизм Zero-Branch Time-Machine:

just workshop-rewind 1

💡 Что только что произошло? В workshop-rewind 1 конфигурация stage-1-stateless была применена к blog/config/ без выхода за пределы main ветки. Ваше приложение настроено с использованием SQLite на диске контейнера и ActiveStorage на локальной файловой системе.

2. Развертывание Puma в одном контейнере в Cloud Run

Развертывайте напрямую из исходного кода в Cloud Run. Современный Rails 8 автоматически генерирует официальный, готовый к использованию в продакшене Dockerfile (с многоэтапной сборкой, jemalloc и безопасностью без прав root). При развертывании из папки blog/ Google Cloud Build напрямую обнаруживает этот нативный Dockerfile и предоставляет управляемый бессерверный сервис:

# Ensure you are inside the Rails application directory
cd blog

# Ensure default region is set
export GOOGLE_CLOUD_REGION="europe-west1"

# Deploy single-container service from source (with dummy key fallback if starting without credentials)
gcloud run deploy blog \
  --source . \
  --region $GOOGLE_CLOUD_REGION \
  --allow-unauthenticated \
  --set-env-vars GOOGLE_CLOUD_ACCOUNT=$GOOGLE_CLOUD_ACCOUNT,SECRET_KEY_BASE_DUMMY=1

В результате выполнения вы должны увидеть примерно следующее:

gcloud run deploy сборка и развертывание контейнера

⏳ На это потребуется несколько минут, пока 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 указано "это займет несколько минут". Подождите минуту и ​​повторно выполните ту же команду ; обычно она выполняется успешно. terraform apply на шаге 1 обычно включает этот API для вас, поэтому вы вообще не должны видеть подсказку.
  2. В конфигурации Default Compute 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. ✨ Первый момент восторга

Откройте сгенерированный URL-адрес Cloud Run в своем браузере!

  1. Ваше современное приложение Rails 8 запущено в Google Cloud!
  2. Войдите в систему, используя свои учетные данные администратора ( GOOGLE_CLOUD_ACCOUNT и APP_ADMIN_PASSWORD ). Если кнопка входа отсутствует, нажмите «Новая запись» , после чего система сначала запросит имя пользователя и пароль. Страница входа в Rails 8 с временными баннерами телеметрии.
  3. Нажмите «Новая запись» , напишите статью под названием «Моя первая запись о работе в облаке» , прикрепите изображение и нажмите «Создать запись» .
  4. Ваш пост опубликован с полным форматированием, и изображение отображено.
  5. Обратите внимание на визуальные данные телеметрии:
    • Значок в заголовке: [EPHEMERAL DB / STORAGE] 💾 Local
    • Водяной знак изображения: почтовая марка 🏠 (значок временного диска 127.0.0.1 в правом нижнем углу).

🐝 Присоединяйтесь к таблице лидеров онлайн-семинара Hive! Если вы онлайн и ваш наблюдатель показывает таблицу лидеров, и вы хотите к ней присоединиться, добавьте URL-адрес вашего Cloud Run здесь: 👉 Зарегистрируйте свой Cloud Run в таблице лидеров

Подробности изменений в службе в блоге Google Cloud Run Console

4. 💥 Подвох: Шок без гражданства и ловушка «обходного пути Puma».

Cloud Run — это бессерверная платформа без сохранения состояния . Когда веб-трафик падает до нуля, Cloud Run сокращает количество экземпляров контейнеров до нуля, чтобы сэкономить деньги. При поступлении нового HTTP-запроса или развертывании новой версии контейнера Cloud Run запускает совершенно новый, чистый образ контейнера.

Баннер "Застрявшие вакансии"

При создании публикации или прикреплении изображения в одноконтейнерном развертывании Rails ставит в очередь задачи ActiveJob (например, анализ размеров изображения или индексирование метаданных). Но поскольку фоновый процесс не запущен, вы увидите предупреждающее сообщение:

Внимание: в данный момент ожидается выполнение 8 фоновых заданий.

Заманчивое решение: запуск Solid Queue внутри Puma.

Умный разработчик мог бы сказать: «Подождите! Rails 8 позволяет запускать Solid Queue непосредственно внутри процесса веб-сервера Puma, включив эту функцию».

SOLID_QUEUE_IN_PUMA=true

переменная окружающей среды!"

Давайте протестируем это обходное решение в Cloud Run:

# Force a revision update to enable Solid Queue inside Puma
gcloud run services update blog \
  --region $GOOGLE_CLOUD_REGION \
  --update-env-vars SOLID_QUEUE_IN_PUMA=true

Теперь вернитесь в браузер и обновите страницу :

💥 Шок, связанный с отсутствием гражданства:

  1. Хорошие новости: обработчик Solid Queue теперь активен в Puma! Все новые задания обрабатываются немедленно.
  2. Холодный душ (подвох!):
    • Поскольку Cloud Run развернул новую версию, предыдущий экземпляр контейнера был заменен!
    • Ваша статья и файл базы данных SQLite на диске были полностью удалены !
  3. Архитектурный урок: запуск фоновых процессов внутри Puma потребляет ценные ресурсы процессора и оперативной памяти веб-потоков и по-прежнему не решает проблему сохранения данных .

5. Автоматизированная проверка на этапе 3.

Проверьте развертывание Cloud Run и обработку оповещений:

just workshop-eval 3

✨ Вывод: Обходные пути для одноконтейнерных систем, такие как SOLID_QUEUE_IN_PUMA удобны для локальной разработки, но в современной облачной архитектуре:

  • Для воспроизведения медиафайлов требуется Google Cloud Storage (GCS) (Шаг 4).
  • Для работы с реляционными данными и очередями заданий требуется управляемый Google Cloud SQL (шаг 5).
  • Работники, выполняющие интенсивную фоновую работу, должны находиться в специальном контейнере-прицепе (Шаг 6).

6. Шаг 4: Развертывание 3 — Постоянное хранилище GCS и предупреждение POLA

На этом этапе мы отделяем хранилище медиафайлов и файлов от диска контейнера, переключая ActiveStorage на Google Cloud Storage (GCS) с использованием кратковременных подписанных URL-адресов через API учетных данных IAM ( iam: true ).

Прежде чем вносить какие-либо изменения в конфигурацию, разберитесь в трех вещах, необходимых Rails для взаимодействия с GCS:

① Идентификатор проекта + имя бакета — Rails должен знать, где хранить блобы. Приложение считывает переменную окружения GOOGLE_CLOUD_PROJECT и определяет имя бакета как ${GOOGLE_CLOUD_PROJECT}-activestorage-prod . Ваш Terraform из шага 1 уже создал этот бакет — никаких дополнительных действий не требуется.

② Сервисная учетная запись с необходимыми правами доступа — Cloud Run работает от имени сервисной учетной записи Compute Engine по умолчанию ( PROJECT_NUMBER-compute@developer.gserviceaccount.com ). Для этой сервисной учетной записи необходимы две роли IAM для хранилища:

  • roles/storage.objectAdmin — для загрузки и предоставления доступа к большим двоичным объектам.
  • Используйте roles/iam.serviceAccountTokenCreator отдельно для вызова API signBlob учетных данных IAM и генерации кратковременных подписанных URL-адресов.

⚠️ Заманчивый короткий путь —

public: true Самый быстрый способ заставить GCS работать — установить public: true в storage.yml и предоставить allUsers:objectViewer доступ к хранилищу. Изображения загружаются мгновенно, подпись не требуется. Не делайте этого. Общедоступное хранилище навсегда раскрывает все загруженные пользователями медиафайлы в открытом интернете. В нашем шаблоне используется iam: true для обеспечения 100% приватности хранилища — каждый URL изображения представляет собой подписанный, истекающий токен, генерируемый на лету API учетных данных IAM.

③

ACTIVE_STORAGE_SERVICE=google — единственная переменная окружения, которая переключает Rails с локального диска на GCS во время выполнения. Никаких изменений в коде, никакого повторного развертывания логики приложения — просто переключение одной переменной окружения.

1. Машина времени: перемотка назад

Переведите машину времени на второй этап:

just workshop-rewind 2

Изучите blog/config/storage.yml — вы увидите, что приложение определяет три именованные среды GCS (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.

Выполните повторное развертывание с включенным GCS. Убедитесь, что вы находитесь в папке blog/ (в противном случае Cloud Run попытается выполнить сборку с использованием Buildpacks вместо Dockerfile Rails 8, что приведет к ошибкам сборки).

Используйте --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 ! Проверьте через командную строку:
    gcloud storage ls -l -r gs://$GCS_BUCKET/
    

Ниже представлен список загруженных BLOB-объектов и медиафайлов, которые вы можете увидеть в консоли браузера Google Cloud Storage в вашем рабочем хранилище ( gs://$GCS_BUCKET ):

Подробная информация о сегменте хранилища в консоли Google Cloud Storage, отображающая постоянные образы (blobs).

5. ⚠️ Подвох: Предупреждающий баннер о застрявших рабочих местах

При загрузке изображения ActiveStorage поставил в очередь задачу анализа ( ActiveStorage::AnalyzeJob ) для извлечения измерений и метаданных.

Однако в настоящее время Cloud Run использует только один веб-контейнер ( puma ). В Rails 8 Solid Queue хранит фоновые задания в базе данных, но никто не выполняет bundle exec rails solid_queue:start !`.

Посмотрите вверху страницы вашего блога: вы увидите яркий предупреждающий баннер, созданный с помощью файла blog/app/views/layouts/_check_stuck_jobs.html.erb .

Мы решим эту проблему должным образом на шаге 6 , когда перейдем к производственной многоконтейнерной архитектуре sidecar с выделенным worker контейнером, в котором Solid Queue будет работать независимо от веб-трафика!

6. Автоматизированная проверка на шаге 4

Проверьте конфигурацию вашей GCS и телеметрию зависших заданий:

just workshop-eval 4

7. Шаг 5: Контрольный список перед запуском: Cloud SQL, секреты и подключение к базе данных.

Пока вы осваивали контейнеры без сохранения состояния и Google Cloud Storage на шагах 3 и 4, Terraform в фоновом режиме завершал подготовку вашей производственной среды !

Перед развертыванием нашей полнофункциональной производственной архитектуры из 3 контейнеров (развертывание 4 на шаге 6) этот шаг служит быстрой предварительной проверкой для подтверждения работоспособности базы данных, подключения и доступа к секретным данным.

0. Переменные среды Bootstrap

Экспортируйте все необходимые переменные оболочки непосредственно из результатов выполнения 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 безопасно подключаются к Cloud SQL через зашифрованный прокси-сервер ( 127.0.0.1:5432 ), не предоставляя доступ к базе данных из общедоступного интернета ( 0.0.0.0/0 ).

ℹ️ Примечание: Этот шаг совершенно необязателен! В Cloud Run официальный контейнер Cloud SQL Auth Proxy автоматически обрабатывает подключение mTLS. Для завершения мастер-класса вам не нужно ничего устанавливать локально.

Если у вас на компьютере установлены cloud-sql-proxy и psql , и вы хотите проверить прямое подключение с вашего ноутбука:

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

(Если потребуется ввести пароль, введите)

$DB_PASSWORD

В противном случае, можете смело переходить к шагу 3 ниже!

3. Проверка секретов, предоставленных Terraform.

Никогда не храните пароли к базам данных, ключи 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 игнорируется в Git по соображениям безопасности, в вашей локальной копии он может ещё отсутствовать. Запустите этот удобный и самовосстанавливающийся фрагмент кода, чтобы получить его из Secret Manager, и ваши локальные инструменты Rails смогут расшифровать учетные данные:

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

🧪 Игровая площадка: Хотите создать собственный секрет, чтобы убедиться, как это просто? 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! Проверьте привязку политики IAM к 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"

Вам следует увидеть:

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

Сравнение безопасности прокси-серверов аутентификации Cloud SQL

5. Автоматизированная предполетная проверка

Запустите автоматизированный набор тестов на шаге 5, чтобы убедиться, что ваша база данных работоспособна, секреты не пусты, а разрешения IAM отображаются корректно:

just workshop-eval 5

8. Шаг 6: Развертывание 4 — Корпоративных многоконтейнерных вспомогательных контейнеров (золотой стандарт)

Теперь мы создаём идеальную эталонную архитектуру: Multi-Container 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 : Обслуживает HTTP-трафик на порту 8080 через Puma.
  2. worker : Выделенный контейнер для фоновой обработки, выполняющий bundle exec rails solid_queue:start .
  3. cloudsql-proxy : Официальный сайдкар Google Cloud SQL Auth Proxy ( gcr.io/cloud-sql-connectors/cloud-sql-proxy:2 ), привязанный к 127.0.0.1:5432 .

3. Выполнение миграции баз данных с помощью задания Cloud Run.

Прежде чем перенаправлять веб-трафик на наше новое развертывание, необходимо выполнить миграцию базы данных и первоначальное заполнение базы данных Cloud SQL.

⚖️ Как выполнять миграции в контейнерах: две стратегии

В контейнеризированных облачных средах существует два распространенных подхода к выполнению миграции баз данных:

  • Вариант A: Автоматический режим внутри Docker Entrypoint ( entrypoint.sh выполняет db:migrate при запуске контейнера)
    • Преимущества: Полная автоматизация; разработчикам не нужно помнить о выполнении команд.
    • Минусы: Опасно при автоматическом масштабировании бессерверных приложений! Когда Cloud Run масштабируется до 10 экземпляров одновременно при высокой нагрузке, 10 контейнеров начинают конкурировать за одновременное выполнение миграций, что приводит к блокировке базы данных, состояниям гонки схем или таймаутам при загрузке.
  • Вариант B: Явное использование через Cloud Run Job (наш архитектурный выбор)
    • Преимущества: Полный контроль, транзакционная безопасность и отсутствие состояний гонки. Работает как выделенная одноразовая задача в контейнере, полностью не связанная с обслуживанием веб-трафика. В случае сбоя миграции обслуживание веб-сайта не пострадает.
    • Минусы: Требуется запуск задания CLI или этапа CI/CD перед развертыванием.

Для обеспечения максимальной надежности производства мы выбираем вариант B.

⚠️ Критическое значение: Формат DATABASE_URL и строгие требования RFC 3986 Ruby 3.4.5

Задания Cloud Run подключаются к Cloud SQL, используя собственный прокси-сервер Unix-сокетов ( /cloudsql/PROJECT:REGION:INSTANCE ). В поле 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: приложение использует 4 базы данных (основную, очередь, кэш, кабель) в каждом database.yml . Все 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 описывает JOBNAME На рисунке выше показан возможный результат выполнения следующего действия:

gcloud run jobs executions describe $JOBNAME

В консоли Google Cloud Run Jobs отображается сообщение об успешном завершении задания rails-migrate. В консоли Google Cloud Run Jobs отображается следующее:

rails-migrate

Выполнение прошло успешно.

4. Четвертое развертывание: развертывание многоконтейнерной облачной среды Cloud Run с помощью Docker Compose.

Теперь разверните реальный производственный стек из 3 контейнеров ( web , worker , cloudsql-proxy ) непосредственно из 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

Вкладка «Контейнеры» в консоли Google Cloud Run отображает 3 дополнительных контейнера (sidecar containers). Консоль Google Cloud Run: вкладка «Контейнеры», отображающая все три контейнера-дополнения (sidecar).

cloudsql-proxy

,

web

,

worker

)

💡 Интересный факт о пользовательском интерфейсе / Ошибка в консоли: В выпадающем списке консоли вы можете заметить, что Cloud Run помечает верхнюю запись как Ingress container: cloudsql-proxy . Не волнуйтесь! Это всего лишь визуальная ошибка пользовательского интерфейса в консоли Google Cloud (отслеживается в #149 ), вызванная порядком зависимостей контейнеров. В действительности, web — единственный контейнер, прослушивающий HTTP-порт 8080 и обрабатывающий 100% входящего веб-трафика, в то время как cloudsql-proxy обслуживает исключительно частный трафик PostgreSQL на порту 5432!

5. ✨ Вау-момент: никаких предупреждений и полная стабильность соединения в облаке!

Сделайте глубокий вдох и полюбуйтесь на свой экран: все предупреждающие баннеры исчезли! 🪄

Блог о работе в облачной среде без предупреждений и с постоянными облачными данными. Блог о производственной версии Rails 8: безупречный пользовательский интерфейс, отсутствие предупреждающих баннеров и постоянное состояние Cloud SQL!

The application is now officially Production-Grade & Cloud-Native :

  • Backed by managed Google Cloud SQL (PostgreSQL) via secure mTLS tunneling.
  • Media safely stored in private Google Cloud Storage with IAM blob signing.
  • Zero ephemeral storage warnings and zero stuck jobs alerts.

Open your Cloud Run URL:

  1. Look at the telemetry badges:
    • [CLOUD PERSISTENT 🐘 ☁️] is verified in your database seed!
    • Both Google Cloud Run and GCS badges in the footer shine green!
    • The stuck jobs warning banner is completely gone !
  2. Create blog posts and comments.
  3. Restart or redeploy as many times as you like: your data, posts, comments, and assets survive forever in Cloud SQL and GCS!

6. Automated Step 6 Validation

Verify the multi-container configuration:

just workshop-eval 6

9. Step 7: Generative AI Pipelines, Podcastifier & The GCS Treasure Hunt 🏴‍☠️

With Solid Queue running in a dedicated container and Google Cloud Storage active, we can unleash asynchronous Generative AI!

1. Генератор винтажных обложек NanoBanana (готовый к использованию конвейер GenAI)

NanoBanana Mascot

Good news: this functionality is already fully implemented in the codebase! You don't need to write any code — your task here is to trigger and observe an asynchronous cloud GenAI pipeline in action.

🧠 How it Works Behind the Scenes

  • Authentication: Zero API keys required! It uses Application Default Credentials ( roles/aiplatform.user on Vertex AI) automatically via the Cloud Run runtime service account. (Alternatively, if GEMINI_API_KEY is provided, it calls the Gemini API directly).
  • The Pipeline: When an article is created without a cover image, an after_commit callback enqueues GenerateCoverImageJob into Solid Queue .
  • Model: It prompts Google's multimodal model ( Gemini 2.5 Flash Image / Imagen on Vertex AI) to generate an authentic vintage 1960s Italian film poster ( "Locandina di un film 1960" ) complete with a cameo banana and a shiny ruby "8".
  • Dynamic Provenance Watermark: Uses libvips to dynamically stamp a visual badge on the corner (colorful cloud stamp on GCS).
  • Turbo Stream Hot-Swap: When the image is attached, Turbo::StreamsChannel.broadcast_refresh_to(post) updates the reader's browser live without a manual page reload!

NanoBanana AI Asynchronous Architecture Sequence Diagram

📐 View Mermaid Sequence Diagram Source

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!

🧪 Try It Live!

You can trigger it in two ways:

  • Option A (Web UI): Click New post , enter a title like "Serverless Architecture with Ruby on Rails 8" , write some content, and leave the cover image file input empty . Click Create Post .
  • Option B (CLI Runner): Run from your terminal:
    bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
    

Wait ~15–30 seconds: watch the Solid Queue worker logs in Cloud Run. The vintage poster will appear directly on your post!

📸 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 (Hands-On Antigravity Coding Exercise)

⚠️ Important Notice: Unlike the Cover Generator above, this feature is NOT yet implemented!

If you open blog/app/jobs/podcastifier_job.rb , you will find an empty scaffold stub:

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

Your mission in this hands-on exercise is to pair-program with Google Antigravity to complete this feature:

  1. Prompt Antigravity: "Help me implement PodcastifierJob in app/jobs/podcastifier_job.rb . It should fetch the Post, translate or synthesize an Italian audio overview using Google Cloud Text-to-Speech (voice 'it-IT-Wavenet-A' with language code 'it-IT' via ADC credentials), and attach the audio file to post.podcast_audio_it using ActiveStorage!"
  2. Add Player to View: In app/views/posts/show.html.erb , add an HTML5 player when @post.podcast_audio_it.attached? .
  3. Trigger Synthesis: When saved, Solid Queue will execute the text-to-speech job asynchronously in the background.

Podcastifier Bilingual UI with Dual Audio Players

🎧 Sample Podcastifier Outputs:

💡 Need Help or Got Stuck? A complete, battle-tested reference solution is available on branch solutions/podcastifier . You can inspect it anytime with git diff solutions/podcastifier .

3. [ДОПОЛНИТЕЛЬНО] 🏴‍☠️ Охота за сокровищами GCS (Восстановление консольных блоков)

ℹ️ Prerequisite Check: Local

cloud-sql-proxy

CLI Required This optional exercise connects your local laptop directly to Google Cloud SQL via an encrypted mTLS tunnel. Check if you have the CLI proxy installed:

which cloud-sql-proxy
  • If installed: Proceed below!
  • If NOT installed and you want to do this quest: Install it with:
    # 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
    
  • Otherwise: Feel free to skip to the Step 7 automated validation!

Remember that photo you uploaded back in Step 4 before the container restart wiped out the ephemeral SQLite database? That image file is still sitting safely in your private GCS bucket as an "orphaned blob"!

Let's use the Rails console to rescue it and attach it to a Cloud SQL post:

# 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

Inside the Rails console:

# 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

Refresh your blog: the photo uploaded during Step 4's Stateless Shock is resurrected and permanently attached to your Cloud SQL post!

4. Automated Step 7 Validation

Verify GenAI jobs and assets:

just workshop-eval 7

10. Step 8: Choose Your Own Adventure / Advanced Quests 🏆

Now that you have mastered the canonical reference architecture, choose your graduation quest!

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

  • Difficulty: Medium (Enterprise Security)
  • The Goal: Lock down your Cloud Run application so only your verified Google / Gmail accounts can access it, eliminating password login entirely!
  • Как это работает:
    1. An External HTTPS Application Load Balancer terminates Google OAuth and verifies identity before traffic ever reaches Cloud Run.
    2. Google forwards verified identity headers ( X-Goog-Authenticated-User-Email ).
    3. Rails automatically logs in the verified Google identity via 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. Enable the Terraform module in iac/iap.tf with enable_iap = true and specify your allowed Google accounts:
      iap_allowed_users = [var.google_cloud_account]
      

Login recognized by IAP banner

📊 Quest 2: Production SRE Telemetry & Cloud Logging

  • Difficulty: Medium (Observability)
  • The Goal: Stream structured JSON application logs with trace correlation IDs directly to Google Cloud Logging and catch production exceptions in real-time with Cloud Error Reporting.
  • Как это работает:
    • Configure blog/config/environments/production.rb to emit structured JSON logs with GCP trace labels.
    • Trigger an intentional test exception and watch Google Cloud Error Reporting group and notify you instantly.

📸 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 Semantic Search & Gemini RAG

  • Difficulty: Hard (GenAI Capstone)
  • The Goal: Search articles conceptually using vector embeddings stored in PostgreSQL on Cloud SQL.
  • Как это работает:
    • Enable the vector extension on your Cloud SQL PostgreSQL instance:
      CREATE EXTENSION IF NOT EXISTS vector;
      
    • Add the neighbor gem to blog/Gemfile and generate text embeddings with Gemini ( text-embedding-004 ) on Post#after_save .
    • Perform cosine distance queries ( <=> ) to power a semantic search bar with Turbo Streams!

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

⚡ Quest 4: SEO & Performance Audit Assistant

  • Difficulty: Easy / Fun (Developer Experience)
  • The Goal: Audit Core Web Vitals, Largest Contentful Paint (LCP), and Flesch-Kincaid / Fog readability indices using Antigravity and Speedgrapher tools.

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

🤔 Curiosity Box: Getting a Live Rails Console on Cloud Run

💡 The Question: On traditional VMs or Heroku-style PaaS platforms, developers commonly SSH into a running instance or use heroku run rails console to inspect or fix production data live. What is the Cloud Run-native equivalent, and how do you harden it?

Почему

gcloud run exec

doesn't exist for Cloud Run Services

Cloud Run is a serverless platform: your container may be running as zero, one, or hundreds of identical instances, with no stable identity or addressable shell. This is fundamentally different from Kubernetes ( kubectl exec ), where a specific pod has a named socket you can attach to.

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

The Four GCP-Native Patterns

Шаблон

Командование

Интерактивный?

Безопасность

1. Local proxy + remote DB

DATABASE_URL=... rails console

✅ Полный

🔒 Cloud SQL Auth Proxy mTLS

2. Cloud Run Job (one-shot)

gcloud run jobs execute --wait

❌ Script only

🔒 IAM role required

3. Web Console gem + IAP

Browser → /rails/console

✅ Полный

🛡️ IAP Zero-Trust

4. Cloud Shell + Proxy

Cloud Shell → bin/rails c

✅ Полный

🔒 Google identity

Pattern 1 (Recommended for data inspection): Run a local Rails console that talks directly to your Cloud SQL PostgreSQL via the 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

You get full IRB interactivity with zero network exposure — the Cloud SQL Proxy uses mTLS over a Unix domain socket or loopback.

Pattern 3 (Web Console + IAP Zero-Trust, the hardened browser path):

The web-console gem provides a browser-based IRB session. Without protection it is a critical security vulnerability — never expose it publicly. But combined with Google IAP (Quest 1), the Attack surface collapses: only verified Google identities on your allowlist can reach the /rails/console endpoint.

# 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

⚠️ Pattern 3 requires Quest 1 (IAP) to be enabled first. Without it, /rails/console on a public URL is equivalent to leaving your server room unlocked. With IAP, it becomes a powerful operator tool hardened by Google-grade Zero-Trust.

Ask Antigravity: "Show me how to run

rails console --sandbox

against my Cloud SQL production database safely using the Cloud SQL Auth Proxy."

🏆 Graduating on The Hive: Proctor-Validated Proof-of-Work

Once you have completed your chosen quest, claim your Step 8 Graduation Trophy 🏆 on The Hive Leaderboard :

  1. Submit Your Quest on GitHub :
    • Open a new Issue on the official repository: github.com/palladius/rails8-app-on-gcp/issues/new
    • Title format: 🎓 [Step 8 Completed] :
    • In the body, include a brief description of what you accomplished and a link to your live Cloud Run deployment.
  2. Connect Your Cloud Run App to Your Issue :
    • Note your Issue number (eg 88 or the full URL https://github.com/palladius/rails8-app-on-gcp/issues/88 ).
    • Inject the STEP_8_GHI environment variable into your Cloud Run service:
      gcloud run services update blog \
        --region europe-west1 \
        --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER>
      
    • Verify that your /status.json endpoint now reports your quest:
      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. Get Your Proctor Review & Unlock the Golden Trophy :
    • Check The Hive Leaderboard : your row will now display an amber badge: ⏳ GHI #XX review pending .
    • Ask workshop proctors ( @palladius , @emilianodellacasa , or @ricc ) to inspect your deployment.
    • As soon as a proctor comments LGTM on your issue, The Hive will automatically elevate your progress to 8/8 🏆 with a golden/purple glowing badge and a permanent trophy linking directly to your capstone issue!

11. Conclusion and Clean Up 🎓

Поздравляем! 🎉

You have built and deployed a production-grade, enterprise-ready Rails 8 application on Google Cloud Platform:

  • Zero-Branch Progression: Mastered modern workflows with Time-Machine overlays on main .
  • Cloud Storage: Scalable, private object storage with IAM blob signing ( iam: true ).
  • Cloud SQL: Managed PostgreSQL secured with Cloud SQL Auth Proxy mTLS tunnels.
  • Secret Manager: Zero plain-text credentials or .env file leaks.
  • Cloud Run Multi-Container: Isolated Puma web, Solid Queue worker, and proxy sidecars.
  • Generative AI: Asynchronous vintage poster generation and TTS podcast synthesis with Vertex AI.
  • Quests: Zero-Trust IAP, SRE Observability, and pgvector embeddings!

🧹 Resource Clean Up

To avoid ongoing charges after completing the workshop, make sure to clean up your Google Cloud resources! Choose the approach that best fits your environment:

Use this option if you are using an existing or shared Google Cloud project where other resources or data live. It surgically destroys only the resources provisioned during this workshop (Cloud Run, Cloud SQL, Secret Manager, GCS buckets), leaving the rest of your project untouched.

cd iac
terraform destroy -auto-approve

Option 2: Total Project Deletion (Leave Zero Trace 🌪️)

Use this option if you created a dedicated workshop project (eg, using workshop credits or a sandbox) and want to guarantee that zero trace remains—including logs, metadata, service accounts, and billing links.

gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet