Rails + Hypernova なアプリを Heroku にデプロイする

個人メモ

の続き。

Rails + Hypernova で作った趣味アプリをデプロイすることに。

最近は Firebase を使うことも増えてきたのだけど、Express や Rails で作った API やウェブアプリのデプロイには、Heroku や Now などの Docker に対応している PaaS を使っている。

Docker 対応の PaaS で Rails + Hypernova のアプリをデプロイする際に、 1コンテナ 1サービス のお作法に従うと、Rails と Node でそれぞれ PaaS のアプリケーションを用意して、デプロイしないといけない。 このようにアプリケーションを分割すると、Rails と Node で同じコンポーネントのファイルを使用する事情から、正しく動かすには Rails と Node のデプロイ完了のタイミングを(可能な限り)揃える必要がある。

各ロールでさらに複数のアプリケーションを作っておいて、Blue-Green Deployment のようなことを実現するか、コンポーネントをHypernova に登録する際の名前にバージョンを含めるようにして、過去のバージョンも扱えるようにすればできそうだが、前者はデプロイタスクが煩雑になるし、後者はコードの管理が複雑になるので、趣味アプリでそこまでやる必要性は感じない。 また、Heroku や Now では、アプリケーション同士は内部ネットワークでやりとりできず、外部ネットワークを通す形になるのでレイテンシーも気になる(計測はしていない)。

上記の理由と Wantedly でも Rails のコンテナに Hypernova を同居させてうまくいっているようなので( ref: Rearchitecting Wantedly's Frontend | Wantedly Engineer Blog )、1コンテナで Rails と Hypernova 両方のサービスを動かすことにした。

Rails の対応

Hypernova に限らず Heroku にデプロイするために必要な対応。

Heroku では静的ファイルの配信をアプリケーションサーバーがおこなう必要があるので、config/environments/production.rb に以下を書く。

config.public_file_server.enabled = true

また、Heroku の制約でログを標準出力に書き出す必要があるので、RAILS_LOG_TO_STDOUT 環境変数を設定する。これは設定ファイルにデフォルトで以下のような記述がある。

  if ENV["RAILS_LOG_TO_STDOUT"].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
  end

昔は、rails_12factor gem を入れて上記と同じ対応していたのだけど、最近は、そういった gem を入れる必要がなくなったぽい。

Docker の対応

複数のサービスを動かすために Run multiple services in a container | Docker Documentation を参考に supervisor を使うことにした。

以下のように ruby:2.5.1 のイメージをベースに apt-get で supervisor を入れ、必要なディレクトリを掘り、設定ファイルをコピーして、supervisord を起動する。

FROM ruby2.5.1

#...

RUN apt-get update && apt-get install -y supervisor

# ...

RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# ...

CMD ["/usr/bin/supervisord"]

supervisord の設定は以下のようなかんじ。Rails や Hypernova のプロセスの標準入出力を supervisord にリダイレクトするために Dockerで子プロセスからのstdoutをsupervisordにリダイレクトする方法 – 踊る犬.netブログ (旧) を参考にした。

[supervisord]
nodaemon=true
directory=/usr/src/app

[program:rails]
command=/bin/bash -c "rails server -b 0.0.0.0"
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:hypernova]
command=/bin/bash -c "node hypernova.js"
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0