ローカルで開発したRailsアプリをCapistrano3でEC2にデプロイする

f:id:watass:20150826201128p:plain

最近RailsのScaffoldから色々開発するのにハマっていて、WEBRickで動作確認しながら、ちょこちょこ開発していました。
そろそろ公開環境へのデプロイ自動化を考えないとなーとなって、せっかくなので今までやっていた単純なAnsibleを使用した形ではなく、デプロイ支援ツールであるCapistranoを使ってローカルマシンからデプロイする手順を自動化してみました。
ちなみに、今までまとめてきたAnsibleを使った形式は以下の記事にあります。

やりたいこと

Mac OS Xのローカルマシンでrails newして生成したプロジェクトを、ローカルからcap production deployするだけで、AWS上のEC2インスタンスにデプロイします。
AWS上の環境はNginxをプロキシとして使って、Unicornアプリケーションサーバとして採用する王道構成です。NginxとUnicornは同一サーバ内で共存させます。DBはRDSのPostgreSQLを採用します。ローカルの環境では、アプリケーションサーバにはWEBrickを、DBにはSQLiteを使います。デフォルトのままです。
つまり、ローカルではデフォルト設定のまま作業できるけど、コマンド一発で本番環境でも動かすというわけです。構成図にするとこんな感じ。

f:id:watass:20150826234933p:plain

Capistranoとは

f:id:watass:20150826201128p:plain
A remote server automation and deployment tool written in Ruby.

Ruby製のデプロイツールです。なんだかんだいってまだ日本語の文献が少ない気がします。
以前やっていたAnsibleのデプロイと違って、Capistranoを使うと何が嬉しいのかと言うと、ロールバックが容易にできるとか、DBのマイグレーションなども一連のデプロイ作業の中に組み込めるとか、タスクを自作すれば、サーバの再起動からなんでもできます。
Ansibleでも近いことはできるんですが、もともとAnsibleはプロビジョニングツールですので、Capistranoのほうがデプロイ目的での拡張には優しい印象です。gemでインストールできるので、Railsだとプロジェクト毎に管理しやすいのも嬉しいです。

バージョン情報

使用する諸々のバージョンは以下の通り。

名前 バージョン
Ruby on Rails 4.2.3
rbenv 0.4.0
Ruby 2.2.0
Capistrano 3.1.0
Amazon Linux AMI 2015.03
Nginx 1.6.2
Unicorn 4.9.0

リモート環境の準備

まずはAWS側の環境の整備を進めます。インスタンスは起動済みの前提です。

Nginxの設定

まずNginxをインストールします。

$ sudo yum install nginx
$ nginx -v
nginx version: nginx/1.6.2

インストールできたら、NginxをUnicornのプロキシとして動作させるためにnginx.confを編集します。設定内容は主に

  • httpのserverでproxy_passのURLをFQDNで指定する。jsなどがある場合も設定しておく。
  • Unix Domain SocketでUnicornとの接続をするため、httpのupstreamでUnicornで設定するunicorn.sockを指定する。

という感じです。
実際の設定ファイルは以下の記事を参考にさせてもらいました。

rbenvのインストール

次にRubyのバージョン切り替えを簡単に行うためにrbenvをインストールします。
まず、rbenvをインストールするために諸々必要なものをインストールします。

$ sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel git

準備ができたらgitでrbenvをプロジェクトからcloneしてきます。

$ cd /usr/local
$ sudo git clone git://github.com/sstephenson/rbenv.git rbenv
$ sudo mkdir /usr/local/rbenv/shims /usr/local/rbenv/versions

インストールできたら、rbenvにPATHを通します。

$ echo 'export RBENV_ROOT=/usr/local/rbenv' >> ~/.bash_profile
$ echo 'export PATH=$RBENV_ROOT/bin:$PATH' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

また、visudoを使ってスーパーユーザ権限の設定をします。

Defaults    env_keep += "RBENV_ROOT"
Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin #これはデフォルト
Defaults    secure_path = /usr/local/rbenv/shims:/usr/local/rbenv/bin:/sbin:/bin:/usr/sbin:/usr/bin

最後に、rbenvでインストールしたRubyをビルドするためにruby-buildをcloneしてきます。また、ビルドに必要な諸々もインストールしておきます。

$ sudo mkdir /usr/local/rbenv/plugins
$ cd /usr/local/rbenv/plugins
$ sudo git clone git://github.com/sstephenson/ruby-build.git
$ sudo yum install -y libffi-devel openssl-devel

これでrbenvのバージョンを確認して、インストールできていることを確認しましょう。

$ rbenv -v
rbenv 0.4.0-153-g3b6faa8

インストールできていますね。
早速rbenvを使ってRuby 2.2.0をインストールしましょう。

$ sudo rbenv install 2.2.0
$ sudo rbenv rehash
$ sudo rbenv global 2.2.0
$ which ruby
/usr/local/rbenv/shims/ruby

Rubyのインストール先がrbenvのインストール時に設定したディレクトリならば成功です。
この辺りの設定は諸々、以下の記事を参考にしました。

bundlerのインストール

後はCapistranoでデプロイするときにリモート側でもbundlerを実行するので、bundlerをインストールしておきます。

$ sudo gem install bundler --no-rdoc --no-ri
$ sudo rbenv rehash
$ bundle init
$ bundle install --path vendor/bundle

postgresqlのインストール

次にリモート環境のRDSの設定と、EC2インスタンス側でコネクタのインストールをします。
RDSの作成に関しては過去に記事を書いているので、見てみてください。

RDSの設定ができたら、EC2のPostgreSQLの諸々をインストールします。

$ sudo yum install postgresql-devel
$ sudo yum install postgresql-server
$ sudo gem install pg -v '0.18.2'

SECRET_KEY_BASEの設定

最後に、環境に必要なSECRET_KEY_BASEの環境変数を設定しておきます。
exportで設定すると、ログアウト時に消えるので、/etc/environmentで記載しておきます。

/etc/environment

export SECRET_KEY_BASE="[長い複雑な文字列]"

この「長い複雑な文字列」はbundleをインストールしていた環境ならばbundle exec rake secretで参考になる文字列を取得できます。

ローカル環境の準備

リモート環境の準備は以上で完了なので、ローカルマシンの準備をします。
既にRailsのインストールは完了しており、WEBrickSQLiteで動作確認できるアプリが動いている前提で進めます。

Capistranoのインストール

デプロイするプロジェクトの配下に移動し、Gemfileを編集してbundle installします。このGemfileを元にリモート環境でもgemがインストールされるので、Unicornのインストールも記述しておきましょう。

Gemfile

gem 'capistrano', '3.1.0'
gem 'capistrano-rails', '~> 1.1.0'
gem 'capistrano-bundler'
gem 'capistrano-rbenv', '~> 2.0'
gem 'unicorn', '4.9.0'

bundle install後には、cap installを実行してCapistranoの基本設定ファイルを生成します。

$ bundle install
$ bundle exec cap install

以上でCapistranoのインストールは完了です。

Capistrano設定ファイルの作成

Capistranoでは、読み込む諸々の設定を記述したCapfileやデプロイ先、参照リポジトリ、デプロイタスクなどのデプロイ全般の設定を記述したdeploy.rb、環境別のホスト設定などを記述したproduction.rbなどをそれぞれ設定します。
また、Unicornに対して行うべきタスクを記述したcapファイルや、Unicornのsocketの設定を記述したunicorn.rbなどをあわせて作成します。capファイルは、capistrano-unicornというgemを利用すること自分で定義する必要がなくなるとかなくならないとか・・・
この辺りの設定はNginxの設定ファイルでもお世話になったこちらの記事の設定をほぼいただきました。ありがとうございます(再掲)

ただ、こちらの記事のままデプロイすると、Unicornの再起動がUSR2シグナルを送るだけ(いわゆるgraceful restart)になり、新しいソースコードをすぐに反映させることができません。USR2シグナルを送るだけでは、既存のmasterから新masterをforkするだけで、実際のアクセスは旧masterに生えているworkerに割り振られるためです。

コンテンツを切り替えるためには、旧masterからをworkerを生やさないようにして、旧masterそのものにQUITシグナルを送る必要があります。が、とりあえず(ダウンタイムゼロにするとか)難しいことを考えずに、停止→起動をcapファイルでタスクとして定義すればいいんじゃないかなと思います。Blue-Green Deploymentみたいなインスタンスごと置き換えるデプロイ方式を採用するならば、1つのインスタンス内でダウンタイムが発生しないことはそこまで重要ではないと思います。

DBとJavaScriptランタイムの設定

最後に、リモート環境ではPostgreSQLを、ローカル環境ではSQLiteを使うので、Gemfileを編集してproduction環境ではpgを、development環境ではsqliteを使用するようにします。

Gemfile

gem 'pg'
gem 'therubyracer'
# 略
group :development do
  gem 'sqlite3'
  # 略
end

途中で設定しているtherubyracerというgemはリモート環境でJavaScriptランタイムの環境が無くてエラーになることを防ぐために指定しておきます。以上を記載してbundle install

$ bundle install

後は本番環境のPostgreSQLの接続情報をdatabase.ymlに記述します。

config/database.yml

production:
  adapter: postgresql
  host: [RDSのエンドポイント]
  encoding: utf8
  database: [DB名]
  username: [ユーザ名]
  password: [ユーザ名に対するパスワード]
  pool: 5
  timeout: 5000

こんな感じでRDS作成時の情報を本番環境時だけ参照するようにしておきます。
GitHubなどで公開することを考えていて、設定ファイルにパスワードをベタ書きすることに抵抗があるならば、環境変数にパスワードを設定しておいて、それをdatabase.ymlで参照するようにしておきましょう。

デプロイしてみる

以上で設定が完了しました。
プロジェクト配下に移動して以下のコマンドでデプロイを実行してみましょう。

$ bundle exec cap production deploy

うまく動作すると、ごちゃごちゃとログが表示されます。DBのマイグレーションアプリケーションサーバの再起動なども行われます。たまに失敗するログが出ますが、ファイルやディレクトリの存在確認で死んでいることが多いです。デプロイ自体には問題なさそうでした。できれば消したいですね。
なお、途中で停止する、デプロイが失敗する、といった場合にはEC2やRDSのSecurityGroupを見直すなどしておくといいでしょう。意外とプロパイダのIPから接続しているとSSHの許可IPが変わってたりしてログインに失敗するケースがあります。

なお、コンテンツはあくまでもリポジトリの中身を参照するので、修正したコードは必ずプッシュしておいてください。デプロイ後に修正した最新のコンテンツが反映されていればデプロイ成功です!

まとめ

Capistrano導入コストは確かに高い

よくCapistranoのようなデプロイ自動化ツールは導入コストが高いと聞きますが、確かに高かったです。個人的にはAnsibleより難しかった印象。
特に、アプリケーションサーバを使用する開発を今回はじめて触ったので、その分Unicornの設定に苦労しました。これはAnsibleで同じことやろうとしたらこれ以上に難しくなるのかもしれませんが・・・

まぁRailsのデプロイ要件は明らかにFuelPHPより複雑なので、早めの自動化を実現しておくことで、その後どんどん複雑になるであろうデプロイ要件を許容しやすくなるのかなーとは思います。構成管理ではChefもコスト高いとか言いますが、似たようなものかもしれません。いずれChefも触ってみたい。

参考

Rails4 - Rails 初心者が Capistrano3 で AWS EC2 にデプロイするためにがんばったこと。 - Qiita
# NginxやUnicorn周りのインフラ設定を大変参考にさせていただきました。
nginx + unicorn + Railsの設定方法 - Qiita
# 同じく、Unicornなどの設定環境で参考にさせていただきました。
Railsのデプロイでハマった点まとめ - そのねこが学ぶとき
# SECRET_KEY_BASEやPostgreSQLの設定の話が参考になりました。
AmazonLinuxにrbenvでrubyを入れてみる · mechamogera/MyTips Wiki · GitHub
# EC2へのrbenvのインストールが結構辛かったので、非常に救われました。
[*Rails*] 開発環境用DBをSQLite3からPostgreSQLに変更する - Qiita
# 開発環境と本番環境のDB切り替えについて参考にさせていただきました。
capistrano3 の設定例と躓いた箇所のまとめ - Qiita
# Capistranoでのデプロイにコケまくったときに大変助かりました。
k-yamada 技術メモ
# Unicornの再起動後に最新コンテンツが反映されなかったときに色々探って巡り会えました。
Unicornのgraceful restartで少しハマった件 - hack in 3 minites
# 同じく、graceful restartで反映できない件について参考になりました。