はじめに
ちょっとした興味からGCPの勉強を初めて3日目にしてやっと Google App Engine(以降GAE)に当初のゴールだった Railsアプリとsidekiqのワーカー両方をデプロイし起動するところまで出来ました。ググってみるとRails アプリ単体をGAEにデプロイする記事はたくさんあったのですが、sidekiq も合わせてGAEにデプロイする手順までまとめた記事は見かけなかったのでここにまとめたいと思います。
全体図
- GAEで別サービスで App(Rails), Worker(Sidekiq) の両方をデプロイし起動させる
- DBにはCloud SQLのPostgres を利用する
- Cloud MemorystoreのRedisを利用してSidekiqのジョブ管理を行う
前提
- 簡単なRailsアプリ、Sidekiqのワーカーは実装済みとする
- GCPでプロジェクをは作成済みとする。今回のプロジェクトIDは (仮)
rails-app-sample
とする。 - 環境
- Rails 6.1.0
- Sidekiq 6.1.2
説明しないこと
- sidekiq の初期設定
サンプルアプリケーション
- id, name, created_at, updated_at カラムを保持する items テーブル
- RailsのAPIモード上で scaffold を利用しAPIを実装
GET /items
POST /items
db/schema.rb
create_table "items", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
config/routes.rb
Rails.application.routes.draw do
resources :items
end
app/controllers/items_controller.rb
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :update, :destroy]
# GET /items
def index
@items = Item.all
render json: @items
end
# POST /items
def create
ItemCreatorWorker.perform_in(3.seconds, item_params.to_h)
render json: { message: 'success' }, status: :accepted
end
private
# Use callbacks to share common setup or constraints between actions.
def set_item
@item = Item.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def item_params
params.fetch(:item, {}).permit(:name)
end
end
app/workers/item_creator_worker.rb
class ItemCreatorWorker
include Sidekiq::Worker
def perform(options = {})
@item = Item.new(options)
@item.save
end
end
手順
Cloud SDK をインストールする
GAE のデプロイは gcloud コマンドを利用して行うため Cloud SDKが必要なのでインストールをします。詳しくは Google Cloud SDK のインストール | Cloud SDK のドキュメント を参考にしてください。
gcloud コマンドの認証
# Google アカウントの選択とアクセスの許可を行う認証画面がブラウザ上で開かれるので許可をする
$ gcloud auth login
# プロジェクト一覧を確認する
$ gcloud projects list
# gclould コマンドで操作したいプロジェクトのプロジェクトIDを指定する
$ gcloud config set project [PROJECT ID]
appengine gem のインストール
appengine gemはDBマイグレーションを App Engine から実行するために必要なので追加してください。
gem "appengine"
これで以下のようなコマンドが実行できるようになります。
$ bundle exec rake appengine:exec -- bundle exec rake db:migrate
GAE環境の用意
gcloud app create
を実行し、リージョンを選択することでApp Engineアプリケーションが作成できます。これでいつでもデプロイは可能な状態となりました。
$ gcloud app create
You are creating an app for project [my-project2-300306].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.
Please choose the region where you want your App Engine application
located:
[1] asia-east2
[2] asia-northeast1
[3] asia-northeast2
[4] asia-northeast3
[5] asia-south1
[6] asia-southeast2
[7] australia-southeast1
[8] europe-west
[9] europe-west2
[10] europe-west3
[11] europe-west6
[12] northamerica-northeast1
[13] southamerica-east1
[14] us-central
[15] us-east1
[16] us-east4
[17] us-west2
[18] us-west3
[19] us-west4
[20] cancel
Please enter your numeric choice: 2
Creating App Engine application in project [my-project2-300306] and region [asia-northeast1]....done.
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.
参考: ロケーション - リージョンとゾーン | Google Cloud
Cloud SQL で PostgreSQL を用意
- グローバルメニューより「SQL」を選択し、「インスタンスを作成」を選択する
- 「PostgreSQL」を選択する
- インスタンスID, パスワードを入力、ロケーション、データベースバージョンを選択。今回はパブリックIPより接続するように選択します。マシンタイプとストレージ以降はお好みで。
- 「作成」ボタンを押せば、PostgreSQL が約10分後に作成されます。
DBユーザー作成
- SQL -> ユーザーメニューを選択後、「ユーザーアカウントを追加」ボタンを押す
- インスタンスに固有のユーザーを作成するため、ユーザー名・パスワードを入力後、「追加」ボタンを押す
- これでDB接続ユーザーが作成されました
データベース作成
- SQL -> データベースを選択後、「データベースの作成」ボタンを押す
- データベース名を入力後、「作成」ボタンを押せば終わり
Cloud Memorystore で Redis を用意
-
$ gcloud services enable redis.googleapis.com
を実行してGoogle Cloud Memorystore for Redis API
を有効にする - Memorystore -> Redis から「インスタンスを作成」ボタンを押す
- インスタンスID, 表示名を入力、リージョン・ゾーン・バージョンなどを選択し、「作成」ボタンを押します。これで Redis が作成されます
アプリケーション直下に以下の Dockerfile を用意する
FROM ruby:2.6.6
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential
RUN gem install bundler
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app
EXPOSE 8080
# アプリとWorker両方でDockerfileを兼用するため起動スクリプトを別途実行するようにする
CMD ["/bin/sh", "bootstrap.sh"]
app.yaml: web側サービスの構成ファイルを用意する
# 独自のDockerイメージを利用するため custom を入力
runtime: custom
# フレキシブル環境を利用するため flex を入力
env: flex
# env_variables で環境変数の設定ができる。このサービス側は web として認識し、bootstrap.sh 内で実行するコマンドを切り替えるため、 `SERVICE_TYPE: 'web'` としました。
# 他にも良い方法があれば知りたいです。
env_variables:
SERVICE_TYPE: 'web'
# 今回は検証環境として最低限のスペックで構築します。
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
# 【重要】 GAE から Cloud SQLのDBに接続できるようにするため、 cloud_sql_instances に CLOUD SQL で作成した該当DBの接続名(例: bamboo-striker-300213:asia-northeast1:sample-app-db )を入力します。
beta_settings:
cloud_sql_instances: [YOUR_INSTANCE_CONNECTION_NAME]
# DBパスワードなどの秘匿情報を
includes:
- secret.yaml
worker.yaml: 非同期処理側サービスの構成ファイルを用意する
runtime: custom
env: flex
# 用途がわかるようにサービス名をつけておきます
service: worker
env_variables:
# bootstrap.shで実行コマンドを切り替えるため、明示的に以下を定義します。
SERVICE_TYPE: 'worker'
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
beta_settings:
cloud_sql_instances: [YOUR_INSTANCE_CONNECTION_NAME]
includes:
- secret.yaml
secret.yaml: 秘匿情報のみを記載した環境変数を定義するファイルを用意する
アプリのサービス構成ファイルでは includes ディレクティブ を使用することで、別ファイルを読み込むことが出来ます。今回は秘匿情報を定義した構成ファイルを用意し、以下のように定義ます。
env_variables:
SECRET_KEY_BASE: [SECRET_KEY_BASE]
DATABASE_USER: 'sample-app-user'
DATABASE_PASSWORD: 'sample-app-user-password'
DATABASE_NAME: 'sample-app-db'
# /cloudsql/[YOUR_INSTANCE_CONNECTION_NAME] の形式で定義することにより、GAE から Cloud SQL へ UNIX ドメインソケットで接続できるようになります。
DATABASE_HOST: '/cloudsql/[YOUR_INSTANCE_CONNECTION_NAME]'
RAILS_ENV: 'production'
# web/workerのサービスから Reis に接続できるようにするため以下も定義します。
REDIS_URL: 'redis://[REDIS_IP_ADDRESS]:6379'
参考: App Engine から Cloud SQL への接続 | Google Cloud
bootstrap.sh: web, worker用の起動スクリプトを用意する
#!/bin/bash
if [ "$SERVICE_TYPE" = "web" ]; then
bundle exec rackup -p 8080 -o '0.0.0.0'
elif [ "$SERVICE_TYPE" = "worker" ]; then
bundle exec sidekiq -C config/sidekiq.yml
fi
ここで一旦web側をデプロイする
この後データベースの作成、マイグレーションを実施するためweb側をデプロイしておきます。
$ gcloud app deploy
マイグレーション準備
Cloud SQL Admin APIの有効
以下を実行してCloud SQL Admin APIを有効にします。DBの作成・マイグレーションなどが可能になります。
$ gcloud services enable sqladmin.googleapis.com
Cloud Build に Cloud SQL クライアントロールの追加
Cloud Build から Cloud SQL Admin API 経由でアクセスできるように Cloud Build のサービスアカウントに Cloud SQL クライアント のロールを追加します。これでアクセスできるようになります。
DBの接続状態を確認
$ bundle exec rake appengine:exec -- bundle exec rake db:migrate:status
特にCloud Buildや認証関連のエラーが発生しなければOK!
---------- CONNECT CLOUDSQL ----------
cloud_sql_proxy is running.
Connections: my-project2-300306:asia-northeast1:sample-app-db.
---------- EXECUTE COMMAND ----------
bundle exec rake db:migrate:status
Schema migrations table does not exist yet.
ERROR
ERROR: build step 0 "gcr.io/google-appengine/exec-wrapper:latest" failed: step exited with non-zero status: 1
--------------------------------------------------------------------------------------------------------------
マイグレーションを実行!!
$ bundle exec rake appengine:exec -- bundle exec rake db:migrate
これで必要なテーブルも無事に作成されました
最後にweb/worker両方を再度デプロイして終わり
時間はかかりますが待っていればこれでweb, worker構成の完了です! うおおおお。
$ gcloud app deploy app.yaml worker.yaml
動作検証
Itemデータの登録処理はワーカー側で非同期で登録するようにしており、数秒待ってから登録されるようになりました!
$ curl -s https://my-project2-300306.an.r.appspot.com/items | jq .
[]
$ curl -X POST -H "Content-Type: application/json" -d '{"name":"ageage"}' https://my-project2-300306.an.r.appspot.com/items
{"message":"success"}⏎
$ curl -s https://my-project2-300306.an.r.appspot.com/items | jq .
[
{
"id": 1,
"name": "ageage",
"created_at": "2020-12-31T09:58:19.770Z",
"updated_at": "2020-12-31T09:58:19.770Z"
}
]
なんやかんやでここまで来るのに丸3日かかりました...
トラブルシューティング
ERROR: (gcloud.app.deploy) NOT_FOUND: Unable to retrieve P4SA: [service-XXXX@gcp-gae-service.iam.gserviceaccount.com] from GAIA. Could be GAIA propagation delay or request from deleted apps.
初回時に gcloud app deploy
をした際にこのエラーが表示されることがあります。ググっても GAIA が何者か分からず原因不明なのですが、もう一度コマンドを再実行すると発生しなくなります。謎...。
ERROR: (gcloud.app.deploy) Error Response: [8] The region asia-northeast1 does not have enough resources available to fulfill the request. Please try again later.
これは結構な頻度で発生します。内容としては「該当のリージョンで該当のリクエストを満たす十分なリソースが無い。後でやり直してください」とのこと。文字通り時間をおいてからやり直すとうまくいきます
おわりに
年末年始の自由研究としてちょっとした興味がてら GAE 上でのRailsアプリケーション環境の構築方法を調査して構築してみました。ざっと触れることでGAE フレキシブルを利用した環境構築方法はざっと把握することができました。GCPから日本語の Rails 環境構築方法、Cloud SQLでのPostgreSQLへの接続方法などの資料が丁寧に提供されておりとても助かりました。一方構築し始めて気づいたことは、意外と非同期処理環境の構築方法は日本語・英語共にあまりなく、本記事は珍しい方の内容になるかと思います。あくまで自分用のメモとして列挙した内容であり読みづらい点は申し訳ないです。
GAEには、その他タスクキュー、cronジョブ、ファイアーウォールルール、Memcacheなどのなどの機能もあるので、全容把握のためざっくりでも良いので触れてみたいです。
ちなみにここまでかかったコストは¥910 でした。(無料枠無し)
参考
- GoogleCloudPlatform/appengine-ruby: Optional integration library for the Ruby runtime for Google App Engine
- 今回利用したサンプルRailsアプリケーション hypermkt/rails-playground
- Rails 5 での Cloud SQL for PostgreSQL の使用 | Ruby | Google Cloud
- app.yaml によるアプリの構成 | App Engine フレキシブル環境用カスタム ランタイム | Google Cloud
- App Engine から Cloud SQL への接続 | Google Cloud