RubyOnRailsとPHPを各複数バージョンで同時テストが可能な開発環境をAWSで作る(その2：RubyOnRails編)

# はじめに
## 動機
・RubyもPHPもバージョン差多すぎめんどくさい
・WebサーバはApacheばかり使ってたけどnginx使いたい。RubyもRaptor試してみたい
・設定する度に忘れてるから、毎度リセットしてる気がする。というかしてる、どうかしてる
## 目的
・RubyやPHP周りのバージョン差とかの検証をすぐにできる開発環境を整備する(とりあえずEC2で)
・サーバの初期設定やら何やらを記録しておく
## 完成形
EC2のmicro.t2に以下が乗った状態
・RHEL7
・nginx：1.6.2。PassengerやPHP-FPMへの振り分け
・Ruby：1.9.3-p551/2.0.0-p598/2.1.5をrbenvで切り替え
・Rails：3系/4系をアプリケーションごとに切り替え
・Passenger：5.0.0.beta1(Raptor)で、アプリケーションごとにRuby1.9系/2.0系/2.1系を切り替え
・PHP：5.3.29/5.4.35/5.5.19/5.6.3をphpenvで切り替え
・PHP-FPM：上記を同一ソースでドメインごとに切り替え
・MariaDB：5.5.40
## 想定読者
・EC2などでPHPやRubyOnRailsの開発用サーバを用意したい方
・LinuxサーバにSSHで繋いでコマンド叩いたりくらいはできる方
・物忘れの激しいWeb系の技術者(主に私)
## 記事内容
・ググったり推測したりで試してうまくいったら記録
・一応初期状態から上記の構成が完成するまでのほぼ全記録になるはず
・特別目新しいことがあるわけではない。たぶん
## お願い
・一応0から一式試したけど、バージョンなどによってはこの通りに行かないのでそのときはぐぐってください
・筆者はLinuxにあまり慣れているわけではありませんので、設定など、こうした方がいいというものがあったらご指摘ください
・~~コマンド打ってバージョン切り替えればいいじゃない~~まあそんなこといわずに
・~~Dockerでやれ~~そのため公開に迷ったのですが、導入コストが低いしせっかく書いたので載っけることにしました

この記事はその2：RubyOnRails編です。
その他の部分は、その1：基本設定編、その3：PHP編をご覧ください。

# MariaDBのインストール
RHEL7ではMariaDBが標準サポートになったので、試しにインストール。軽く動かしただけだとMySQLとの差異は感じられない。
サービス名はmariadbなのにコマンド名はmysql。解せぬ

```
# yum -y install mariadb-server mariadb-devel
# mysql -V
mysql  Ver 15.1 Distrib 5.5.40-MariaDB, for Linux (x86_64) using readline 5.1
# systemctl start mariadb
```

特に問題なかったので自動起動に。
設定は後回し(Railsのインストール時に行う)。

```
# systemctl enable mariadb
```

# Rubyのインストール～Railsアプリケーションの稼働確認

## rbenv、ruby-build経由でRubyをインストール
ここは特に目立って引っかからなかったので、コマンドとファイルの編集内容だけ羅列。
インストール場所は迷ったが、[/usr/local とは何なのか](http://hateda.hatenadiary.jp/entry/2014/02/02/191807)によると、サブディレクトリで分割するなら/optにしておけという話らしいので従っておく。あんま厳密にする必要なさそうだけど一応。
なお、openssl-develはRubyインストール時、nodejsはWEBrick起動時に必要になる。

```
# yum -y install openssl-devel nodejs
# git clone https://github.com/sstephenson/rbenv.git /opt/rbenv
# vi /etc/profile.d/env.sh
```

以下を記載：

```
#rbenv
export RBENV_ROOT="/opt/rbenv"
PATH="$RBENV_ROOT/bin:$PATH"

export PATH
eval "$(rbenv init -)"
```

```
# . /etc/profile.d/env.sh
# rbenv -v
rbenv 0.4.0-129-g7e0e85b
# cd /opt/rbenv
# mkdir plugins
# cd plugins
# git clone https://github.com/sstephenson/ruby-build.git ./ruby-build
```

あとついでに[rbenv使うならこれもいれとけプラグイン4選](http://blog.code-life.net/blog/2014/05/07/recommended-rbenv-plugin/)にある他のプラグインも追加しておく。
プラグインの詳細はリンク先をご覧ください。

```
# git clone https://github.com/sstephenson/rbenv-default-gems.git ./rbenv-default-gems
# git clone https://github.com/sstephenson/rbenv-gem-rehash.git ./rbenv-gem-rehash
# git clone https://github.com/rkh/rbenv-update.git ./rbenv-update
# vi /opt/rbenv/default-gems
```

各Ruby環境にインストールするgemはこの時点で記載しておく。
Raptorもプロジェクトごとに用意する必要がないため、ここで各Ruby環境に入れておく。記事執筆時点ではまだbetaなので、--preを入れておこう。
ただ、どうやらRaptor自体はRubyのバージョンには依存しないため、gemはそれぞれでインストールするものの参照するファイルは1つでいいようである。
以下を記述：

```
bundler
pry
rack
passenger --pre
```

さて本体入れますか。

```
# rbenv install -l
# rbenv install 2.1.5
(けっこう時間かかる)
# rbenv global 2.1.5
# ruby -v
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]
```

インストール完了。ついでにバージョン切り替えも試す。

```
# rbenv install 1.9.3-p551
(けっこう時間(
# rbenv install 2.0.0-p598
(けっこ(
# rbenv versions
  1.9.3-p551
  2.0.0-p598
* 2.1.5 (set by /opt/rbenv/version)
# rbenv global 1.9.3-p551
# ruby -v
ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-linux]
# rbenv global 2.1.5
# ruby -v
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]
```

おっけーっぽい。

## Railsのインストール
Railsのインストールについては、[Rails開発環境の構築（rbenvでRuby導入からBundler、Rails導入まで）](http://qiita.com/emadurandal/items/a60886152a4c99ce1017)を大体そのまま使った。
Railsやプロジェクトごとに使うgemはプロジェクトごとに用意したいので、プロジェクト単位でbundlerを使ってgemをインストールする構成にしている。
わざわざ2回インストールする理由が最初はよくわからなかったけど、
・1回目：railsのgem自体はプロジェクト外に配置される→このままだとプロジェクト内で完結しないので管理が煩雑になる
・2回目：gemをプロジェクト内に配置し直すことでプロジェクト内で完結させる
ということのようだ。なるほど。

Passengerはnginxベースで動かすことにするので、ユーザ関係も一緒に調整しておく。
グループwwwを作って、そこにnginxを入れておく。

```
# groupadd www
# useradd nginx -g www
# passwd nginx
# mkdir /var/www
# cd /var/www
# mkdir rails
# chown nginx:www rails
# cd rails
# su nginx
$ bundle init
$ vi Gemfile
gem "rails"
```

準備ができたのでインストール。DBはmysql指定で問題なさそう。
オプションの指定をし忘れるとRuby環境にgemが入ってしまうので、忘れないように。
1回目のgemインストールに使ったファイル群はその場で削除して、2回目を行っている。

```
$ bundle install --path vendor/bundle
$ bundle exec rails new testapp1 -d mysql --skip-bundle
$ rm -rf Gemfile
$ rm -rf Gemfile.lock
$ rm -rf vendor
$ cd testapp1
$ bundle install --path vendor/bundle
```

ユーザを変えてインストールしていると、インストール中に、

```
rbenv: cannot rehash: /opt/rbenv/shims isn't writable

```

というメッセージが何度か出る。
このディレクトリは[rbenv rehashは何をやっているのか？](http://dqn.sakusakutto.jp/2014/02/rbenv_rehash_what_it_does.html)によると、どうやらコマンドの解釈に用いられているようだ。
しかしrails関連のコマンドはbundle exec経由で打つことになるので、特に問題はないと思われる。
(他の問題点についてご存じの方、ご教授ください)

Rubyのバージョンを切り替えながら、別のアプリケーション(testapp2:ruby2.0.0-p598, testapp3:ruby-1.9.3-p551)でも同様にgemを入れる。コマンドは上記の bundle init 以降とほぼ同様なので省略するが、1.9.3系でインストールするときだけ、Gemfileを

```
gem "rails", "~> 3.0"
```

としておいて、Rails3系をインストールしておく。

DBにmysql(mariadb)を指定したときは、DBを事前に用意する必要があるので、先に作っておく。
もちろんそれぞれで実行するのを忘れないように。

```
$ vi config/database.yml
$ bundle exec rake db:create:all
```

さてそんなわけで起動してみる。

```
$ bundle exec rails server
[2014-11-28 17:42:40] INFO  WEBrick 1.3.1
[2014-11-28 17:42:40] INFO  ruby 2.1.5 (2014-11-13) [x86_64-linux]
[2014-11-28 17:42:40] INFO  WEBrick::HTTPServer#start: pid=22131 port=3000
```

ということで、まずは起動ができることだけ確認した。

## Raptorをnginxに繋ぎ込み
[プロキシを使わずにRuby-1.9/2.1混在環境も作れる、Apache2+Passenger4+rbenvを用いた混在環境の作り方](http://y-ken.hatenablog.com/entry/how-to-use-multiple-ruby-version-in-one-apache-passenger)を参考にしつつアレンジしてみた。
記事はApacheだけど、nginxでもいけるはずということで。

インストール前にswap関係をいろいろ操作してるのは、EC2のmicro.t2などのように、メモリとスワップの合計値が不足する場合に途中で言われるようなので(micro.t2はデフォルトではスワップがない)、言われたらおとなしくやっておく。

```
$ su
# dd if=/dev/zero of=/swap bs=1M count=1024
# mkswap /swap
# swapon /swap
# cd /opt/rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/passenger-5.0.0.beta1/
# ./bin/passanger-install-nginx-module
```

アンケートなどいろいろ尋ねられる。
途中の質問は、サードパーティーのものは特に入れていないので1としよう。
終わったら他のバージョンでも同様の処理を行う。
Passengerインストール時にはRubyをそれぞれのバージョンにセットしておく必要があるので注意。

```
# rbenv global 2.0.0-p598
# cd /opt/rbenv/versions/2.0.0-p598/lib/ruby/gems/2.0.0/gems/passenger-5.0.0.beta1/
# ./bin/passanger-install-nginx-module
# rbenv global 1.9.3-p551
# cd /opt/rbenv/versions/1.9.3-p551/lib/ruby/gems/1.9.1/gems/passenger-5.0.0.beta1/
# ./bin/passanger-install-nginx-module
```

複数バージョンだろうが気にせずインストールコマンドを叩いてしまって問題ない(時間はかかるけど……)。
そうするとインストール完了時に、

```
  http {
      ...
      passenger_root /opt/rbenv/versions/1.9.3-p551/lib/ruby/gems/1.9.1/gems/passenger-5.0.0.beta1;
      passenger_ruby /opt/rbenv/versions/2.1.5/bin/ruby;
      ...
  }
```

のように、nginx.confにpassengerのパスとRubyのパスを指定するように出てくる。
passenget_rootはそのまま使って、passenger_rubyはサブドメインごとに切り替えるため消しておく。

ではnginx.confでもいじりますか。

```
# vi /opt/nginx/conf/nginx.conf
```

対応行を変更：

```
pid /var/run/nginx.pid;
user nginx;
gzip on;
```

で肝心の切り替え部分はこんな感じ。passenger_rubyのパスはどうにもならなくて羅列になってしまった。
分岐などができればもう少し収まりがよくなるのだが、[IfIsEvil](http://wiki.nginx.org/IfIsEvil)らしいしif内にpassenger_rubyは書けないみたいだしなので、気にしないことにする。
サブディレクトリで複数のアプリの切り分けが可能なようにすることも考えたけど、サブディレクトリで本運用はしないと思ったのでそのままにした。ここを複数書けばいい話なので。
nginxの設定については[nginx連載4回目: nginxの設定、その2 - バーチャルサーバの設定](http://heartbeats.jp/hbblog/2012/04/nginx04.html)とその周辺のページがわかりやすい。
今度じっくり読む。

```
    server {
        listen       80;
        server_name  testapp1.yourdomain;
        root /var/www/rails/testapp1/public;
        access_log /var/log/testapp1_access.log;
        error_log /var/log/testapp1_error.log info;
        passenger_ruby /opt/rbenv/versions/2.1.5/bin/ruby;
        passenger_enabled on;
        rails_env development;
        location = /50x.html {
            root   html;
        }
    }
    server {
        listen       80;
        server_name  testapp2.yourdomain;
        root /var/www/rails/testapp2/public;
        access_log /var/log/testapp2_access.log;
        error_log /var/log/testapp2_error.log info;
        passenger_ruby /opt/rbenv/versions/2.0.0-p598/bin/ruby;
        passenger_enabled on;
        rails_env development;
        location = /50x.html {
            root   html;
        }
    }
    server {
        listen       80;
        server_name  testapp3.yourdomain;
        root /var/www/rails/testapp3/public;
        access_log /var/log/testapp3_access.log;
        error_log /var/log/testapp3_error.log info;
        passenger_ruby /opt/rbenv/versions/1.9.3-p551/bin/ruby;
        passenger_enabled on;
        rails_env development;
        location = /50x.html {
            root   html;
        }
    }
```

まだパス通ってなかったのでパスを追加しつつconfigtestする。

```
# vi /etc/profile.d/env.sh
```

rbenvで書いた分を含めるとこんな感じ。

```
#rbenv
export RBENV_ROOT="/opt/rbenv"
PATH="$RBENV_ROOT/bin:$PATH"

#nginx
PATH="$PATH:/opt/nginx/sbin"

export PATH
eval "$(rbenv init -)"
```

ではconfigtest。

```
# . /etc/profile.d/env.sh
# nginx -t
nginx: the configuration file /opt/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /opt/nginx/conf/nginx.conf test is successful
```

通ったらさっそく起動と行きたいところだけど、サービスに登録してないからsystemctlが使えない。
直接叩いてもよいのだが、このままでは自動起動などが面倒になるため、[FedoraのServiceFile](http://wiki.nginx.org/FedoraSystemdServiceFile)を元にServiceFileを作成してしまおう。
この部分は、[「Systemd」を理解する ーシステム管理編ー ](http://equj65.net/tech/systemd-manage/)と[Systemd入門(1) - Unitの概念を理解する](http://d.hatena.ne.jp/enakai00/20130914/1379146157)のあたりを読むと理解が進む。以下でやっていること自体は単純である。

```
# vi /usr/lib/systemd/system/nginx.service
```

以下を記載。

```
[Unit]
Description=The nginx HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/opt/nginx/sbin/nginx -t
ExecStart=/opt/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target
```

これで準備完了。nginxを起動しよう。

```
# systemctl start nginx
```

エラーなどが出なければ起動は完了。気になったらps auxしてみてもよいだろう。
起動できることを確認したので、nginxは自動起動にしておく。

```
# systemctl enable nginx
```

これで各ドメインにアクセスすれば、それぞれのバージョンのRubyで各アプリケーションが動くことを確認できる。

本当は同じアプリケーションを複数のバージョンで動かしてみたかったのだが、どうも初回起動時にキャッシュとは別に実行バージョンの情報を記録したものを保持しているようである。
毎回再起動を入れるように、httpセクションにpassenger_stat_throttle_rate 0;を指定した上でtmp/always_restart.txtを設置したところ、確かに毎回再起動がかかっているようには見えたのだが、アプリケーションのバージョン情報には特に変化が見られなかった。

こちらの方法について、もしご存じの方がいらっしゃいましたらご教授いただければと思います。

次はPHP編。