45
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Rails 5.1 on Google App Engine

はじめに

「Railsを動かすならHeroku」の方が圧倒的に多いかと思いますが、Google App Engine(以下:GAE)でもFlexible Environmentにはなりますが、動かせます。

Docker化しているアプリケーションであれば、後々はk8s等の恩恵も受けたいはずで、それを考慮に入れてくると、GCP上に載せておくことは良いことかもしれません。
また、BigQueryやStackdriverなど、魅力的なプロダクトに繋げやすいのもGCPの利点です。

あまり、RailsをGoogle App Engineに載せている記事を見かけなかったので、簡単にまとめてみました。

Herokuデプロイ時に比べ、いくつかハマるポイントがありました……。
ここでは、Rails v5.1以上を対象に記載します。

GAEのRuby Base ImageのDockerfileを覗いてみる

まずは、GAEが使用するRuby Base ImageのDockerfileを覗いてみましょう。
https://github.com/GoogleCloudPlatform/ruby-docker/blob/master/appengine/image_files/Dockerfile (リンク切れ)
(2017-10-06 修正)
https://github.com/GoogleCloudPlatform/ruby-docker/blob/master/ruby-base/Dockerfile

大事な部分だけ、切り出します。

Dockerfile(BaseImage)
FROM gcr.io/google-appengine/debian8

---(snip)---

# (1)
RUN mkdir /nodejs && curl -s https://nodejs.org/dist/v6.11.1/node-v6.11.1-linux-x64.tar.gz | tar xvzf - -C /nodejs --strip-components=1

---(snip)---

# (2)
ENV DEFAULT_RUBY_VERSION 2.3.4
ENV BUNDLER_VERSION 1.15.1

---(snip)---

# (3)
ENV RACK_ENV=production \
    RAILS_ENV=production \
    APP_ENV=production \
    RAILS_SERVE_STATIC_FILES=true \
    RAILS_LOG_TO_STDOUT=true

# (4)
WORKDIR /app
EXPOSE 8080
ENV PORT=8080
ENTRYPOINT []
CMD []

(1)(2) デフォルトのRubyバージョンは、2.3.3、Nodeは6.11.1です。(このあたりはよく更新されます。)

(3) Heroku同様、RAILS_ENVや、RAILS_SERVE_STATIC_FILESなど必要な環境変数は、コンテナビルド時に既にセットされます。

(4) ポイントとしては、8080番ポートをリッスンしているところでしょうか。
GAEは、言語に限らず8080番しかエントリーポイントとして許可しないのが習慣です。Railsサーバの起動は8080番で行いましょう。

gcloud beta app gen-config で作られるDockerfileを覗いてみる

先ほどのBase Imageを使って、デプロイ用のコンテナがビルドされます。
app.yamlruntime: rubyを指定した場合のDockerfileは、以下のコマンドで確認することができます。

$ gcloud beta app gen-config --custom

This looks like a Ruby application. Please confirm the command to run
as the entrypoint: [bundle exec rackup -p $PORT]:
Writing [app.yaml] to [/Users/takamario/qiita-appengine-rails-5.1].
Writing [Dockerfile] to [/Users/takamario/qiita-appengine-rails-5.1].
Writing [.dockerignore] to [/Users/takamario/qiita-appengine-rails-5.1].
Dockerfile(GAEデプロイ用)
FROM gcr.io/google-appengine/ruby:latest

# (1)
ARG REQUESTED_RUBY_VERSION=""

RUN if test -n "$REQUESTED_RUBY_VERSION" -a \
        ! -x /rbenv/versions/$REQUESTED_RUBY_VERSION/bin/ruby; then \
      (apt-get update -y \
        && apt-get install -y -q gcp-ruby-$REQUESTED_RUBY_VERSION) \
      || (cd /rbenv/plugins/ruby-build \
        && git pull \
        && rbenv install -s $REQUESTED_RUBY_VERSION) \
      && rbenv global $REQUESTED_RUBY_VERSION \
      && gem install -q --no-rdoc --no-ri bundler --version $BUNDLER_VERSION \
      && apt-get clean \
      && rm -f /var/lib/apt/lists/*_*; \
    fi
ENV RBENV_VERSION=${REQUESTED_RUBY_VERSION:-$RBENV_VERSION}

COPY . /app/

RUN if test -f Gemfile.lock; then \
      bundle install --deployment --without="development test" \
      && rbenv rehash; \
    fi

# (2)
ENV RACK_ENV=production \
    RAILS_ENV=production \
    RAILS_SERVE_STATIC_FILES=true

RUN if test -d app/assets -a -f config/application.rb; then \
      bundle exec rake assets:precompile || true; \
    fi

# (3) BUG: Reset entrypoint to override base image.
ENTRYPOINT []

# (4)
CMD bundle exec rackup -p $PORT

(1) ここで、使用するRubyのバージョンを設定できます。
(2) 念のため、ここでも環境変数はセットされています。
(3) BUGと書いてますね……。Base Imageでセットされてはいないですが、明示的に書いて置く必要がありました。
(4) bundle exec rails s -p $PORT でも問題ないです。

Rails 5.1以上用のDockerfile例

上で見てきたDockerfileは、あくまでもRubyアプリ全般、Railsに関しては、v5.1未満のものでした。
v5.1から導入されたyarnなどを使用するとなると、もうひと工夫必要になります。
(もちろん、そのうち修正されるとは思います。)

(2017-10-06 追記)
2017-09-20のコミットで、yarnのインストールが加わっていました。
https://github.com/GoogleCloudPlatform/ruby-docker/commit/8032c26cd3028176d0760b4f435c79463e8e1e30#diff-91e4c715bfb701f20104580d2fcdc94eR29

以下は、yarnのインストールを含めたDockerfile例です。

Dockerfile(yarnのインストールを含む)
FROM gcr.io/google-appengine/ruby:latest

# (1)
ARG REQUESTED_RUBY_VERSION=""

RUN if test -n "$REQUESTED_RUBY_VERSION" -a \
        ! -x /rbenv/versions/$REQUESTED_RUBY_VERSION/bin/ruby; then \
      (apt-get update -y \
        && apt-get install -y -q gcp-ruby-$REQUESTED_RUBY_VERSION) \
      || (cd /rbenv/plugins/ruby-build \
        && git pull \
        && rbenv install -s $REQUESTED_RUBY_VERSION) \
      && rbenv global $REQUESTED_RUBY_VERSION \
      && gem install -q --no-rdoc --no-ri bundler --version $BUNDLER_VERSION \
      && apt-get clean \
      && rm -f /var/lib/apt/lists/*_*; \
    fi
ENV RBENV_VERSION=${REQUESTED_RUBY_VERSION:-$RBENV_VERSION}

### 追加ここから
RUN apt-get update -qq && apt-get install -y apt-transport-https && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update -qq && apt-get install -y yarn
COPY . /app/
### 追加ここまで

RUN if test -f Gemfile.lock; then \
      bundle install --deployment --without="development test" \
      && rbenv rehash; \
    fi

ENV RACK_ENV=production \
    RAILS_ENV=production \
    RAILS_SERVE_STATIC_FILES=true

RUN if test -d app/assets -a -f config/application.rb; then \
      bundle exec rake assets:precompile || true; \
    fi

ENTRYPOINT []

CMD bundle exec rackup -p $PORT

app.yaml

最後にapp.yamlですが、動かすだけであれば、それほど複雑な記載は必要ありません。
GAEと一緒にCloudSQLを使うことが多いかと思いますので、その形で記載します。

また、Heroku同様、production環境ではDATABASE_URLの環境変数で、DBの接続情報を渡すものとします。

({}の中身は、適宜置き換えて下さい。)

app.yaml
runtime: custom
env: flex

env_variables:
  DATABASE_URL: mysql2://{USERNAME}:{PASSWORD}@localhost/{DATABASE}?socket=/cloudsql/{INSTANCE_CONNECTION_NAME}

beta_settings:
  cloud_sql_instances: {INSTANCE_CONNECTION_NAME}

ハマるポイント

deviseを使っている場合、assets:precompileで落ちる

deviseに限らず発生する可能性はある事象ですが、GAEの設定ファイルであるapp.yamlで環境変数は設定できますが、それはコンテナ起動時にのみセットされ、コンテナビルド時には設定されません。

回避策としては、以下のように適当にセットしておくか、

config/initializers/devise.rb
config.secret_key = ENV.fetch('SECRET_KEY_BASE', 'hogehoge')

Rails v5.1以上であれば、Encrypted Secretsを使って、ソースコードに含めてしまうのが良いかと思います。

config/initializers/devise.rb
config.secret_key = Rails.application.secrets.secret_key_base

Stackdriverでのログが見づらい

Base ImageでRAILS_LOG_TO_STDOUT=trueの環境変数をセットしていますので、Heroku同様、エラー等は標準出力され、GCPではStackdriverで確認可能なのですが、Railsのデフォルトのログフォーマットだと、見づらくなってしまいます。

以下のgemを入れるだけで、フォーマットをStackdriver用に整えてくれます。
google-cloud-ruby/stackdriver

また、RailsのSQLログは、色付き(ANSI Color)で出力されますが、Stackdriverは対応していないので、以下を設定しておくと良いかもしれません。

config/environments/production.rb
config.colorize_logging = false

Healthcheckのログがうざい、service-inされない

上のgemをインストールすると、healthcheckのログファイルを分けてくれるようになります。
ただ、事前にhealthcheckを返すアクションを作成しておく必要があります。

app/controllers/gae_healthcheck_controller.rb
class GaeHealthcheckController < ApplicationController
  def ok
    render plain: 'ok'
  end
end
config/routes.rb
if Rails.env.production?
  get '/_ah/start',  to: 'gae_healthcheck#ok'   
  get '/_ah/stop',   to: 'gae_healthcheck#ok'
  get '/_ah/health', to: 'gae_healthcheck#ok'
end

デプロイに時間がかかる

ログを見てもらうとわかりますが、デプロイ用のコンテナを作るまではそこまで時間がかからないのですが、GAE内部でserviceやversionの設定をしている部分に時間がかかっております。。

これからに期待ですね。。

CloudSQLへのDBマイグレーションは?

ローカルから、cloud_sql_proxyを用いて、マイグレーションを行うことも可能ですが、以下のgemで可能です。(上記の stackdriver も依存gemとして含まれます。)
GoogleCloudPlatform/appengine-ruby

$ bundle exec rake appengine:exec -- bundle exec rake db:migrate

特にコンテナの中身にこだわりがなければ

2017-10-06現在、デフォルトのRuby用のapp.yamlを使用しても、yarnが既にインストールされているイメージが使われるので問題ないです。

app.yaml
entrypoint: bundle exec rackup --port $PORT
env: flex
runtime: ruby

beta_settings:
  cloud_sql_instances: [YOUR_INSTANCE_CONNECTION_NAME]

この際、注意すべきところは、cloud_sql_proxy用にプロジェクトのサービスアカウントにEditorのロールを付与してあげることです。

ロール付与
$ gcloud projects add-iam-policy-binding [YOUR-PROJECT-ID] \
  --member=serviceAccount:[PROJECT_NUMBER]@cloudbuild.gserviceaccount.com \
  --role=roles/editor

さいごに

Herokuと違って、準備しておかなければいけない部分は多少ありますが、Google Cloud Platformの他プロダクトの恩恵を受けやすいGoogle App Engineはとても魅力的だと思います。

コンテナ管理が流行っていくことも考慮に入れると、GAEは良い選択肢ではないでしょうか。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
45
Help us understand the problem. What are the problem?