在 Cloud Run 环境中运行 Rails

了解如何将示例 Rails 应用部署到 Cloud Run,以及如何将代管式数据库、对象存储、加密 Secret 以及构建流水线与无服务器计算相集成。

部署 Rails 应用涉及将多项服务集成到一起,形成一个整体项目。本教程假定您熟悉 Rails 网页开发。

本教程需要Ruby 3.0 或更高版本(也支持 Ruby 2.7,请参阅“了解代码”部分)和 Rails 6 或更高版本

展示部署架构的图表。
Rails 网站由 Cloud Run 提供,Cloud Run 使用多个支持服务来存储不同的数据类型(关系型数据库信息、媒体资产、配置 Secret 和容器映像)。作为构建和迁移任务的一部分,Cloud Build 会更新后端服务。

目标

  • 创建 Cloud SQL 数据库并将其关联到 Active Record
  • 创建并使用 Secret Manager 安全地存储和访问 Rails 主密钥
  • 通过 Active Storage 托管用户上传的媒体和 Cloud Storage 文件
  • 使用 Cloud Build 自动执行构建和数据库迁移
  • 将 Rails 应用部署到 Cloud Run

费用

开始前须知

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 启用 Cloud Run, Cloud SQL, Cloud Build, Secret Manager, and Compute Engine API。

    启用 API

  5. 安装 Google Cloud CLI。
  6. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  7. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  8. 确保您的 Google Cloud 项目已启用结算功能

  9. 启用 Cloud Run, Cloud SQL, Cloud Build, Secret Manager, and Compute Engine API。

    启用 API

  10. 安装 Google Cloud CLI。
  11. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  12. 确保本教程使用的帐号拥有足够的权限。

准备环境

设置默认项目

运行以下命令,为 gcloud CLI 设置默认项目配置:

gcloud config set project PROJECT_ID

PROJECT_ID 替换为新创建的 Google Cloud 项目 ID

克隆 Rails 应用

Rails 示例应用的代码位于 GitHub 上的 GoogleCloudPlatform/ruby-docs-samples 代码库中。

  1. 克隆代码库:

    git clone https://github.com/GoogleCloudPlatform/ruby-docs-samples.git
    
  2. 转到包含示例代码的目录并运行以下命令,以确保使用所需的 gem 和依赖项正确设置了应用:

    Linux/macOS

    cd ruby-docs-samples/run/rails
    bundle install
    

    Windows

    cd ruby-docs-samples\run\rails
    bundle install
    

准备支持性服务

本教程使用多种 Google Cloud 服务来提供支持已部署 Rails 项目的数据库、媒体存储空间和密文存储空间。这些服务部署在特定地区中。为了提高服务之间的效率,最好将所有服务部署在同一地区。如需详细了解离您最近的地区,请参阅各地区提供的商品

设置 Cloud SQL for PostgreSQL 实例

Rails 支持多个关系型数据库,包括 Cloud SQL 提供的多个关系型数据库。本教程使用 PostgreSQL,后者是 Rails 应用常用的开源数据库。

以下部分介绍如何为 Rails 应用创建 PostgreSQL 实例、数据库和数据库用户。

创建 PostgreSQL 实例

控制台

  1. 在 Cloud Console 中,转到 Cloud SQL 实例页面。

    转到“Cloud SQL 实例”页面

  2. 点击创建实例

  3. 点击选择 PostgreSQL

  4. 实例 ID 字段中,输入实例的名称 (INSTANCE_NAME)。

  5. Password(密码)字段,输入 postgres 用户的密码。

  6. 对于其他字段,请使用默认值。

  7. 点击创建实例

gcloud

  • 创建 PostgreSQL 实例:

    gcloud sql instances create INSTANCE_NAME \
        --database-version POSTGRES_12 \
        --tier db-f1-micro \
        --region REGION
    

    替换以下内容:

    创建实例并准备就绪可供使用需要几分钟时间。

创建数据库

控制台

  1. 在 Cloud Console 中,转到 Cloud SQL 实例页面。

    转到“Cloud SQL 实例”页面

  2. 选择 INSTANCE_NAME 实例。

  3. 转到数据库标签页。

  4. 点击创建数据库

  5. 数据库名称对话框中,输入 DATABASE_NAME

  6. 点击创建

gcloud

  • 在最近创建的实例中创建数据库:

    gcloud sql databases create DATABASE_NAME \
        --instance INSTANCE_NAME
    

    DATABASE_NAME 替换为实例中数据库的名称。

创建用户

为数据库用户生成随机密码,并将其写入名为 dbpassword 的文件:

cat /dev/urandom | LC_ALL=C tr -dc '[:alpha:]'| fold -w 50 | head -n1 > dbpassword

控制台

  1. 在 Cloud Console 中,转到 Cloud SQL 实例页面。

    转到“Cloud SQL 实例”页面

  2. 选择 INSTANCE_NAME 实例。

  3. 转到用户标签页。

  4. 点击添加用户帐号

  5. 内置身份验证对话框下:

    1. 输入用户名 DATABASE_USERNAME
    2. 输入 dbpassword 文件的内容作为密码 PASSWORD
  6. 点击添加

gcloud

  • 在最近创建的实例中创建用户,并将其密码设置为 dbpassword 的内容:

    gcloud sql users create DATABASE_USERNAME \
       --instance=INSTANCE_NAME --password=$(cat dbpassword)
    

    DATABASE_USERNAME 替换为实例中用户的名称。

设置 Cloud Storage 存储分区

您可以使用 Cloud Storage 将 Rails 静态资源和用户上传的媒体托管在高可用性对象存储空间中。

控制台

  1. 在 Google Cloud 控制台中,进入 Cloud Storage 存储桶页面。

    进入“存储桶”页面

  2. 点击创建存储分区
  3. 创建存储分区页面上,输入您的存储分区信息。要转到下一步,请点击继续
    • 指定存储分区的名称中,输入符合存储分区命名要求的名称。
    • 对于位置,选择以下项: us-central1
    • 对于为数据选择一个默认存储类别,选择以下项: Standard
    • 对于选择如何控制对象的访问权限,请选择访问权限控制选项。
    • 对于高级设置(可选),请指定加密方法保留政策存储分区标签
  4. 点击创建

gcloud

在安装 gcloud CLI 的过程中,安装了 gsutil 命令行工具。

  • 创建 Cloud Storage 存储分区。如需创建唯一的 Cloud Storage 存储分区名称,请使用 PROJECT_ID 和您选择的后缀 MEDIA_BUCKET_SUFFIX。在 Cloud Storage 中,存储分区名称必须是全局唯一的。

    gsutil mb -l REGION gs://PROJECT_ID-MEDIA_BUCKET_SUFFIX
    

创建存储分区后,若要将上传的图片设为公开,请将图片对象的权限更改为可供所有人读取。

控制台

  1. 在 Google Cloud Console 中,转到 Cloud Storage 浏览器页面。

    转到浏览器

  2. 在存储分区列表中,点击要公开的存储分区的名称。

  3. 选择页面顶部附近的权限标签页。

  4. 点击添加成员按钮。

    随即会显示“添加成员”对话框。

  5. 新成员字段中,输入 allUsers

  6. 选择角色下拉列表中,选择 Cloud Storage 子菜单,然后点击 Storage Object Viewer 选项。

  7. 点击保存

对象群组被公开共享后,“公共访问权限”列中会针对每个对象显示一个链接图标。您可以点击此图标来获取对象的网址。

如需了解如何在 Ruby 浏览器中获取有关失败操作的详细错误信息,请参阅问题排查

gcloud

  • 使用 gsutil iam ch 命令公开所有对象。请使用您在创建存储分区时使用的 MEDIA_BUCKET_SUFFIX 值。

    gsutil iam ch allUsers:objectViewer gs://PROJECT_ID-MEDIA_BUCKET_SUFFIX
    

在 Secret Manager 中存储密文值

配置后备服务后, Rails 需要密码等安全信息来访问这些服务。本教程使用 Rails CredentialsSecret Manager 安全地存储这些信息,而不是直接将这些值放入 Rails 源代码中。

创建加密凭据文件并将密钥存储为 Secret Manager Secret

Rails 将密钥存储在名为“config/credentials.yml.enc'”的加密文件中。 可以使用本地 config/master.key 或环境变量 ENV[“RAILS_MASTER_KEY”] 解密文件。在凭据文件中,您可以存储 Cloud SQL 实例数据库密码和其他外部 API 访问密钥。

您可以将此密钥安全地存储在 Secret Manager 中。然后,您可以通过向 Cloud Run 和 Cloud Build 分别授予其各自的服务帐号的访问权限,向其授予该密钥的访问权限。服务帐号由包含项目编号的电子邮件地址标识。

  1. 使用以下命令生成 config/credentials.yml.enc 文件:

    bin/rails credentials:edit
    

    如果未定义主密钥,该命令会创建 config/master.key,如果文件不存在,该命令会创建 config/credentials.yml.enc 文件。这将在您的默认 $EDITOR 中打开一个临时文件,其中包含要添加 Secret 的解密内容。

  2. 将新创建的 PostgreSQL 实例数据库密码从 dbpassword 文件复制并粘贴到凭据文件中:

    secret_key_base: GENERATED_VALUE
    gcp:
      db_password: PASSWORD
    

    Secret 可通过 Rails.application.credentials 访问。例如,Rails.application.credentials.secret_key_base 应返回应用的密钥库,Rails.application.credentials.gcp[:db_passsword] 应返回数据库密码。

  3. config/credentials/yml.enc 是以加密方式存储的,但 config/master.key 可存储在 Secret Manager 中。

    控制台

    1. 在 Cloud Console 中,转到 Secret Manager 页面

      转到 Secret Manager 页面

    2. 点击创建 Secret

    3. 名称字段中,输入密钥 RAILS_SECRET_NAME 的名称。

    4. 密钥值对话框中,将 mater.key 值粘贴到框中。

    5. 点击创建密钥

    6. 在您的密钥的“密钥详情”页面上,记下项目编号:

      projects/PROJECTNUM/secrets/RAILS_SECRET_NAME

    7. 权限标签页中,点击添加成员

    8. 新成员字段中,输入 PROJECTNUM-compute@developer.gserviceaccount.com,然后按 Enter

    9. 新成员字段中,输入 PROJECTNUM@cloudbuild.gserviceaccount.com,然后按 Enter

    10. 角色下拉菜单中,选择 Secret Manager Secret Accessor

    11. 点击保存

    gcloud

    1. 使用 config/master.key 的值创建新的 Secret:

      gcloud secrets create RAILS_SECRET_NAME --data-file config/master.key
      

      RAILS_SECRET_NAME 替换为新密钥的名称。

    2. 如需确认创建密钥,请检查:

      gcloud secrets describe RAILS_SECRET_NAME
      
      gcloud secrets versions access latest --secret RAILS_SECRET_NAME
      
    3. 获取项目编号的值:

      gcloud projects describe PROJECT_ID --format='value(projectNumber)'
      
    4. 向 Cloud Run 服务帐号授予对 Secret 的访问权限

      gcloud secrets add-iam-policy-binding RAILS_SECRET_NAME \
          --member serviceAccount:PROJECTNUM-compute@developer.gserviceaccount.com \
          --role roles/secretmanager.secretAccessor
      

      PROJECTNUM 替换为上述项目编号值。

    5. 向 Cloud Build 服务帐号授予对 Secret 的访问权限:

      gcloud secrets add-iam-policy-binding RAILS_SECRET_NAME \
          --member serviceAccount:PROJECTNUM@cloudbuild.gserviceaccount.com \
          --role roles/secretmanager.secretAccessor
      

      在输出中,确认 bindings 将两个服务帐号列为成员。

将 Rails 应用连接到生产数据库和存储

本教程使用 PostgreSQL 实例作为生产数据库,使用 Cloud Storage 作为存储后端。为了让 Rails 连接到新创建的数据库和存储分区,您需要在 .env 文件中指定访问这些数据库所需的所有信息。.env 文件包含应用环境变量的配置。该应用将使用 dotenv gem 读取此文件。由于 Secret 存储在 credentials.yml.enc 和 Secret Manager 中,因此 .env 不必加密,因为它不存储任何敏感凭据。

  1. 如需将 Rails 应用配置为连接到数据库和存储分区,请打开 .env 文件。
  2. .env 文件配置修改为以下内容。请使用您在创建存储分区时使用的 MEDIA_BUCKET_SUFFIX 值。

    PRODUCTION_DB_NAME: DATABASE_NAME
    PRODUCTION_DB_USERNAME: DATABASE_USERNAME
    CLOUD_SQL_CONNECTION_NAME: PROJECT_ID:REGION:INSTANCE_NAME
    GOOGLE_PROJECT_ID: PROJECT_ID
    STORAGE_BUCKET_NAME: PROJECT_ID-MEDIA_BUCKET_SUFFIX
    

    Rails 应用现已设置为在部署到 Cloud Run 时使用 Cloud SQL 和 Cloud Storage。

授予 Cloud Build 对 Cloud SQL 的访问权限

为了让 Cloud Build 应用数据库迁移,您需要授予 Cloud Build 访问 Cloud SQL 的权限。

控制台

  1. 在 Cloud Console 中,转到 Identity and Access Management 页面。

    转到 Identity and Access Management 页面

  2. 如需修改 PROJECTNUM@cloudbuild.gserviceaccount.com 成员的条目,请点击 修改成员

  3. 点击添加其他角色

  4. 选择角色对话框中,选择 Cloud SQL 客户

  5. 点击保存

gcloud

  • 授予 Cloud Build 访问 Cloud SQL 的权限:

    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member serviceAccount:PROJECTNUM@cloudbuild.gserviceaccount.com \
        --role roles/cloudsql.client
    

将应用部署到 Cloud Run

设置后备服务后,您现在可以将应用部署为 Cloud Run 服务了。

  1. 使用提供的 cloudbuild.yaml,使用 Cloud Build 构建映像、运行数据库迁移并填充静态资源:

    gcloud builds submit --config cloudbuild.yaml \
        --substitutions _SERVICE_NAME=SERVICE_NAME,_INSTANCE_NAME=INSTANCE_NAME,_REGION=REGION,_SECRET_NAME=RAILS_SECRET_NAME
    

    SERVICE_NAME 替换为您的服务名称。第一次构建需要几分钟时间才能完成。如果构建超时,请将 --timeout=2000s 插入上述构建命令,以增加超时时长。

  2. 构建成功后,首次部署 Cloud Run 服务,设置服务区域、基础映像和连接的 Cloud SQL 实例:

    gcloud run deploy SERVICE_NAME \
         --platform managed \
         --region REGION \
         --image gcr.io/PROJECT_ID/SERVICE_NAME \
         --add-cloudsql-instances PROJECT_ID:REGION:INSTANCE_NAME \
         --allow-unauthenticated
    

    您应该会看到显示部署成功的输出,其中包含服务网址:

    Service [SERVICE_NAME] revision [SERVICE_NAME-00001-tug] has been deployed
     and is serving 100 percent of traffic at https://SERVICE_NAME-HASH-uc.a.run.app

  3. 如需查看已部署的服务,请转到服务网址。

    猫影集的应用着陆页的屏幕截图。
    如果服务网址显示猫咪影集,则您位于应用的首页。

  4. 请尝试上传新照片。如果照片上传成功,则表示 Rails 应用已成功部署。

    包含照片的猫影集的应用着陆页的屏幕截图。

更新应用

虽然初始配置和部署步骤较为复杂,但更新流程更为简单:

  1. 运行 Cloud Build 构建和迁移脚本:

    gcloud builds submit --config cloudbuild.yaml \
         --substitutions _SERVICE_NAME=SERVICE_NAME,_INSTANCE_NAME=INSTANCE_NAME,_REGION=REGION,_SECRET_NAME=RAILS_SECRET_NAME
    
  2. 部署服务,仅指定区域和映像:

    gcloud run deploy SERVICE_NAME \
         --platform managed \
         --region REGION \
         --image gcr.io/PROJECT_ID/SERVICE_NAME
    

了解代码

Rails 示例应用使用标准 Rails 命令创建。以下命令将创建 cat_album 应用并使用 scaffold 命令为相册资源生成模型、控制器和视图:

rails new cat_album
rails generate scaffold Photo caption:text

数据库连接

config/database.yml 文件包含在不同环境中(开发、测试、生产)访问数据库所需的配置。例如,生产数据库配置为在 Cloud SQL for PostgreSQL 中运行。数据库名称和用户名通过 .env 文件中的环境变量设置,而数据库密码存储在 config/credentials.yml.enc 文件中,这需要 RAILS_MASTER_KEY 进行解密。

当应用在 Cloud Run(全代管式)上运行时,它使用 Cloud Run 环境提供的套接字连接到 PostgreSQL 实例。 当应用在本地机器上运行时,它会使用 Cloud SQL Auth 代理连接到 PostgreSQL 实例。

production:
  <<: *default
  database: <%= ENV["PRODUCTION_DB_NAME"] %>
  username: <%= ENV["PRODUCTION_DB_USERNAME"] %>
  password: <%= Rails.application.credentials.gcp[:db_password] %>
  host: "<%= ENV.fetch("DB_SOCKET_DIR") { '/cloudsql' } %>/<%= ENV["CLOUD_SQL_CONNECTION_NAME"] %>"

云存储用户上传的媒体

Rails 使用 Active Storage 将文件上传到云存储服务商。config/storage.ymlconfig/environments/production.rb 文件将 Cloud Storage 指定为生产环境中的服务提供商。

google:
  service: GCS
  project: <%= ENV["GOOGLE_PROJECT_ID"] %>
  bucket: <%= ENV["STORAGE_BUCKET_NAME"] %>
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :google

使用 Cloud Build 实现自动化

cloudbuild.yaml 文件不仅执行典型的映像构建步骤(创建容器映像并将其推送到 Container Registry),还执行 Rails 数据库迁移。这些数据库需要访问数据库,可使用 Cloud SQL 身份验证代理的帮助程序 app-engine-exec-wrapper 执行。

steps:
  - id: "build image"
    name: "gcr.io/cloud-builders/docker"
    entrypoint: 'bash'
    args: ["-c", "docker build --build-arg MASTER_KEY=$$RAILS_KEY -t gcr.io/${PROJECT_ID}/${_SERVICE_NAME} . "]
    secretEnv: ["RAILS_KEY"]

  - id: "push image"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"]

  - id: "apply migrations"
    name: "gcr.io/google-appengine/exec-wrapper"
    entrypoint: "bash"
    args:
      [
        "-c",
        "/buildstep/execute.sh -i gcr.io/${PROJECT_ID}/${_SERVICE_NAME} -s ${PROJECT_ID}:${_REGION}:${_INSTANCE_NAME} -e RAILS_MASTER_KEY=$$RAILS_KEY -- bundle exec rails db:migrate"
      ]
    secretEnv: ["RAILS_KEY"]

substitutions:
  _REGION: us-central1
  _SERVICE_NAME: rails-cat-album
  _INSTANCE_NAME: cat-album
  _SECRET_NAME: rails-master-key

availableSecrets:
  secretManager:
  - versionName: projects/${PROJECT_ID}/secrets/${_SECRET_NAME}/versions/latest
    env: RAILS_KEY

images:
  - "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"

此配置中使用替代变量。直接更改该文件中的值意味着可以在迁移时删除 --substitutions 标志。

在此配置中,仅应用 db/migrate 目录中的现有迁移。如需创建迁移文件,请参阅 Active Record 迁移

如需构建映像并应用迁移,Cloud Build 配置需要从 Secret Manager 访问 RAILS_MASTER_KEY Secret。availableSecrets 字段设置用于 Secret 的 Secret 版本和环境变量。主密钥 Secret 作为构建映像步骤中的参数传入,然后在构建映像时设置为 Dockerfile 中的 RAILS_MASTER_KEY

ARG MASTER_KEY
ENV RAILS_MASTER_KEY=${MASTER_KEY}

要扩展 Cloud Build 配置以将部署包含在一个配置中,而无需运行两个命令,请参阅使用 Cloud Build 从 Git 进行持续部署。如上所述,这需要 IAM 更改。

支持 Ruby 2.7

虽然本教程使用 Ruby 3.0,但它也支持 Ruby 2.7。如需使用 Ruby 2.7,请将 Dockerfile 中的 Ruby 基础映像更改为 2.7

# Pinning the OS to buster because the nodejs install script is buster-specific.
# Be sure to update the nodejs install command if the base image OS is updated.
FROM ruby:3.0-buster

清理

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。