Vagrant & VirtualBox上のUbuntuでRuby + Rails5 + Puma + Nginx

Rails5からデフォルトRackサーバがPumaになり、Unicornの代わりに利用される機会が増えそうなので、環境構築手順を自分用メモとして書いておきます。

# 投稿時のバージョン

* Ubuntu 15.10
* Ruby 2.3.0(rbenvでインストール)
* Rails 5.0.0beta3
* Puma 3.4.0
* Nginx 1.9.3


# ヘルパコマンドを用意

Pumaの推奨はJRubyかRubiniusですが、Rubyでも[クラスターモード](https://github.com/puma/puma#clustered-mode)で実行させればそこそこパフォーマンスが出せるはずなので[^1]、起動時にworkersにCPUコア数を指定します。

[^1]: Apache Benchで何もしないControllerのURLを指定した場合、Unicornとほぼ同等のスコアが出ました、詳細な検証ではないので正確性に欠けると思います。

workersへの指定は起動引数-wでも可能ですので、CPUコア数を取得するヘルパコマンドを用意し、workersにCPUコア数が自動設定されるようにします[^2]。

[^2]: 後述で使用

```sh
$ mkdir -p ~/bin
$ echo 'cat /proc/cpuinfo | grep processor | wc -l' > ~/bin/ncpu; chmod +x ~/bin/ncpu
$ echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile
```

動作確認

```sh
$ ncpu
4
```

# Rails5プロジェクトを用意

## プロジェクト作成

```sh
$ rails -v
Rails 5.0.0.beta3
$ rails new hello_rails5
$ cd hello_rails5
$ rails g scaffold person name age:integer
$ rails db:migrate
$ rails db:migrate RAILS_ENV=production
```

デフォルトDBはSQLite3になりますが、今回はそのままにします。


## Pumaの設定

デフォルトで作成される設定ファイルへ、Nginx用の設定を追加します。

```rb:config/puma.rb
_proj_path = "#{File.expand_path("../..", __FILE__)}"
_proj_name = File.basename(_proj_path)
_home = ENV.fetch("HOME") { "/home/vagrant" }

pidfile "#{_home}/run/#{_proj_name}.pid"
bind "unix://#{_home}/run/#{_proj_name}.sock"
directory _proj_path

# --- ここまで追加 ---

# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum, this matches the default thread size of Active Record.
#
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count

# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked webserver processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory. If you use this option
# you need to make sure to reconnect any threads in the `on_worker_boot`
# block.
#
# preload_app!

# The code in the `on_worker_boot` will be called if you are using
# clustered mode by specifying a number of `workers`. After each worker
# process is booted this block will be run, if you are using `preload_app!`
# option you will want to use this block to reconnect to any threads
# or connections that may have been created at application boot, Ruby
# cannot share connections between processes.
#
# on_worker_boot do
#   ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
# end

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
```

# Nginxの設定

```conf:/etc/nginx/conf.d/hello_rails5.conf
upstream hello_rails5 {
    # Path to Puma SOCK file, as defined previously
    server unix:/home/vagrant/run/hello_rails5.sock fail_timeout=0;
}

server {
    listen 80;
    server_name 192.168.33.10; # Vagrantfileに書かれているデフォルトIP

    root /path/to/hello_rails5/public; # /path/toを変更

    try_files $uri/index.html $uri @hello_rails5;

    location / {
        proxy_pass http://hello_rails5;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}
```

設定追加後、Nginxを再起動

```sh
$ sudo service nginx restart
```

最小の設定で済むように、conf.dに今回のRailsアプリ用のバーチャルサーバ設定を追加します、初期インストールのファイルは触りません。


# Rails5アプリを起動

development環境の場合は

```sh
$ puma -w $(ncpu)
```

production環境の場合は

```sh
$ rails assets:precompile RAILS_ENV=production 
# ※RAILS_ENV=productionはpumaの前
$ SECRET_KEY_BASE=$(rake secret) RAILS_SERVE_STATIC_FILES=true RAILS_ENV=production puma -w $(ncpu)
```

コマンドを叩くのが面倒な時は、起動用のaliasを用意して

```sh
$ echo "alias devpuma='puma -w \$(ncpu)'" >> ~/.bash_profile
$ echo "alias prodpuma='rails assets:precompile RAILS_ENV=production; SECRET_KEY_BASE=\$(rake secret) RAILS_SERVE_STATIC_FILES=true RAILS_ENV=production puma -w \$(ncpu)'" >> ~/.bash_profile
$ source ~/.bash_profile
```

aliasから起動

```sh
# developmentで起動
$ devpuma
# productionで起動
$ prodpuma
```

あとはブラウザからURLへアクセスして表示されれば環境構築は成功。


# 参考

* [Githubのpumaリポジトリ](https://github.com/puma/puma)
* [How To Deploy a Rails App with Puma and Nginx on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-deploy-a-rails-app-with-puma-and-nginx-on-ubuntu-14-04)