🐥

【CentOS 7】Nginx + Unicorn で Rails アプリケーションを本番環境で立ち上げる方法

2021/02/21に公開

はじめに

Rails で作ったアプリケーションを本番環境で動かすときには Phusion Passenger か Unicorn を使うのが一般的なようです。

これらの特徴は Apache や Nginx などのWebサーバとうまく連携して処理を行うというものです。

ぼくの借りているサーバではしばらく Apache が動いていましたが、Rails のアプリケーションを動かすのを機に Nginx に乗り換えました。

Nginx と連携して Rails のアプリケーションを動かすには Unicorn が良いというのを聞いたので Nginx + Unicorn で動かそうとしました。ところが実際にやってみたらかなりはまったので、他のサイトで紹介されている「はまる間違い」に注意しながら説明していきたいと思います。

環境

CentOS 7.1
Nginx 1.10.1
Unicorn 5.1.0
Rails 4.2.6
Ruby 2.0.0p353

Nginx のインストール

はじめにNginxをダウンロードします。Ubuntuでは $ sudo apt install nginx だけでインストールできると思いますが、CentOS は少し特殊な設定をしなければなりません。

詳しくは別の記事で書いたのでこちらを参照してください。
CentOS 7 (5, 6) で安定版 (最新版) の Nginx をインストールする方法

Rails アプリケーション側の設定

まずはじめに Rails 側での設定を行います。

Unicorn のインストール

Gem を使って Unicorn をインストールします。まずは Gemfile を編集します。おそらく Rails 4 系を使っている人は、30行目あたりに

Gemfile
# Use Unicorn as the app server
# gem 'unicorn'

という箇所があるはずなので、ここの # gem 'unicorn' のコメント外してください。
この1行がなければ gem 'unicorn' をどこかに追加してください。

編集したら保存して Unicorn をインストールします。

$ bundle install

これで Unicorn がインストールされます。

Unicorn の設定ファイルを作成

Rails アプリケーションがあるディレクトリの config ディレクトリに unicorn.rb というファイルを新規作成します。

そのファイルに以下を追加します。

config/unicorn.rb
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true

listen '/home/vagrant/myapp/tmp/unicorn.sock'
pid    '/home/vagrant/myapp/tmp/unicorn.pid'

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

⚠️ listenpid のパスは /home/{ユーザ名}/{Railsアプリケーション名}/tmp/unicorn.sock(.pid) としてください。言い換えると、{Railsアプリケーションのあるディレクトリ}/tmp/unicorn.sock(pid) です。

Unicorn の起動・停止スクリプトを作成する

これは作らなくてもできますが、一度作っておけば後々の操作がかなり楽になるのでここで紹介します。なお、スクリプトは「Rakefileにunicorn起動・停止のコマンドを追加する」を参考にしました。

まず以下のコマンドを実行してファイルを生成します。
$ rails g task unicorn

すると lib/tasks ディレクトリに unicorn.rake というファイルが生成されます。このファイルを開いて以下を追加します。

lib/tasks/unicorn.rake
namespace :unicorn do

  # Tasks
  desc "Start unicorn"
  task(:start) {
    config = Rails.root.join('config', 'unicorn.rb')
    sh "unicorn -c #{config} -E development -D"
  }

  desc "Stop unicorn"
  task(:stop) {
    unicorn_signal :QUIT
  }

  desc "Restart unicorn with USR2"
  task(:restart) {
    unicorn_signal :USR2
  }

  desc "Increment number of worker processes"
  task(:increment) {
    unicorn_signal :TTIN
  }

  desc "Decrement number of worker processes"
  task(:decrement) {
    unicorn_signal :TTOU
  }

  desc "Unicorn pstree (depends on pstree command)"
  task(:pstree) do
    sh "pstree '#{unicorn_pid}'"
  end

  # Helpers
  def unicorn_signal signal
    Process.kill signal, unicorn_pid
  end

  def unicorn_pid
    begin
      File.read("/home/vagrant/myapp/tmp/unicorn.pid").to_i
    rescue Errno::ENOENT
      raise "Unicorn does not seem to be running"
    end
  end

end

‼️ ちなみに上記は本番環境ではなく開発環境で起動するためのスクリプトです。本番環境での起動方法を知っている方は development の箇所を production にしてください。ただし、いきなり本番環境で起動しようとすると別のエラーが発生する可能性があるので、問題を切り分けるために、まずは開発環境で起動してみて、一通りうまくいったら本番環境で起動する、という流れで説明していきます。

⚠️ def unicorn_pidFile.read の引数のパスは /home/{ユーザ名}/{Railsアプリケーション名}/tmp/unicorn.pid としてください。

これで便利な Unicorn の起動・停止スクリプトが完成しました。

Unicorn を起動する

では Unicorn を起動してみましょう。以下のコマンドを実行します。
$ rake unicorn:start

unicorn -c /home/{ユーザ名}/{Railsアプリケーション名}/config/unicorn.rb -E production -D の1行が表示され、ほかにエラーが表示されなければ起動成功です。

Unicorn が起動しているかどうかを確認するには以下のコマンドを実行します。
$ ps -ef | grep unicorn | grep -v grep

起動していれば、上記の設定であればおそらく4つほどプロセスが稼働しているのが確認できると思います。停止している場合はコマンドを実行しても何も表示されません。

ちなみに Unicorn を停止するには以下のコマンドを実行します。
$ rake unicorn:stop

停止に成功すれば特に何も表示されません。先ほどの ps コマンドを実行して何も表示されなければ停止したことがわかります。

停止した場合はもう一度起動しておいてください。
$ rake unicorn:start

起動できなかった場合

起動しようとしたらエラーが発生した場合は、/tmp 以下に unicorn.sock というファイルがないかチェックしてください。ここでいう /tmp は Rails アプリケーション内の tmp ではなく、Unix システムのルート / 以下の tmp であることに注意してください。

もし /tmp/unicorn.sock があった場合は、このファイルを先ほどの設定ファイル内で指定したパスに移動させてください。

$ sudo mv /tmp/unicorn.sock /home/{ユーザ名}/{Railsアプリケーション}/tmp

⚠️ {} は入力しません。

そしてもう一度 Unicorn 起動コマンドを実行してください。
$ rake unicorn:start

これで起動できるはずです。

🌀 ここがまさにはまるポイントで、いくつかのサイトでは、unicorn.sock を置く場所として /tmp/unicorn.sock を指定していますが、これが大きな落とし穴です。いつからかはわかりませんが、/tmp 内のファイルは Nginx と Unicorn 間で共有できないようになったらしいです。

昔はできたっぽいので、ちょっと古い記事だと /tmp 内にソケットファイル(.sock)を置くようにしていますが、現在はここだとうまくいかないので、/home/{ユーザ名}/{Railsアプリケーション名}/tmp に置くようにしましょう。厳密には、Unicorn 自体は起動できますが、後に Nginx を起動して動作確認しても 502 Bad Gateway もしくは Rails のサーバエラーのページが表示されてしまいます。

Nginx 側の設定

次に Nginx の設定を行います。

Nginx の設定ファイルを作成

Nginx の設定ファイルは、/etc/nginx/conf.d/default.conf です。そのままこのファイルを編集してもいいですが、通常これらの設定ファイルはバックアップを取るのが一般的なので、今回は、このファイルは直接いじらずに rails.conf というファイルを作って編集することにしましょう。

‼️ default.conf にはじめから設定されていたものとバッティングする可能性があるので、Rails のアプリケーションがうまく動くまでは、default.conf 内に書かれている内容はすべてコメントアウトしてから行ったほうがいいかもしれません。

/etc/nginx/conf.d 以下に rails.conf を新規作成して、以下を追加します。vi 等のエディタを使う場合は sudo が必要になるかもしれません。

/etc/nginx/conf.d/rails.conf
upstream unicorn {
    server  unix:/home/vagrant/myapp/tmp/unicorn.sock;
}

server {
    listen       80;
    server_name  192.168.33.12;

    access_log  /var/log/nginx/access.log;
    error_log   /var/log/nginx/error.log;

    root /home/vagrant/myapp/public;

    client_max_body_size 100m;
    error_page  404              /404.html;
    error_page  500 502 503 504  /500.html;
    try_files   $uri/index.html $uri @unicorn;

    location @unicorn {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://unicorn;
    }
}

⚠️ upstream 内の server のパスは /home/{ユーザ名}/{Railsアプリケーション名}/tmp/unicorn.sock としてください。同様に、server 内の root/home/{ユーザ名}/{Railsアプリケーション名}/public としてください。

⚠️ server 内の server_name は使用するサーバのドメイン名もしくはIPアドレスを指定してください。

Nginx の起動

設定ファイルを編集したら Nginx を起動します。なお、すでに起動していた場合でも設定ファイルを編集した場合は再起動が必要になりますので再起動してください。

Nginx が起動しているか停止しているかを確認するには以下のコマンドを実行します。
$ systemctl status nginx

Active: の箇所が active (running) となっていれば起動していて、inactive (dead) となっていれば停止しています。

停止している場合は起動します。
$ sudo systemctl start nginx

起動している場合は再起動します。
$ sudo systemctl restart nginx

これでエラーがでなければOKです。

確認する

設定が間違っていなければとりあえず開発環境では起動できているはずです。Nginx の設定ファイルの server_name で指定したドメイン名もしくはIPアドレス(今回の説明では 192.168.33.12 の部分)にアクセスして Rails アプリケーションが表示されればOKです!

ほかのサイトを参考にしている場合は、ほとんどの人が unicorn.sock の置き場所でつまづくと思うので、そこさえ気をつけていれば特に問題はないと思います。もし、どこかでエラーが発生した場合はもう一度設定を確認してください。特にここの記事のコードをそのままコピペするとパスの部分が違っているというミスが起こりそうなので、パスをよく確認してみてください。また、今回は CentOS での環境で説明しているので、Ubuntu の場合は Nginx の設定ファイルの場所が少し違うので注意してください。

本番環境で起動する

ここまで来たら、後は開発環境から本番環境へ移行します。
本番環境に移行する前に、開発環境で起動している Unicorn を停止します。
$ rake unicorn:stop

最初に、Unicorn で Rails アプリケーションが本番環境で起動されるようにスクリプトを編集します。さきほど Rails 側の設定で編集した、lib/tasks/unicorn.rake を開き、7行目の development と書かれている部分を production に変更します。

lib/tasks/unicorn.rake
-    sh "unicorn -c #{config} -E development -D"
+    sh "unicorn -c #{config} -E production -D"

マイグレーションを実行します。
$ rake db:migrate RAILS_ENV=production

SCSS ファイルや CoffeeScript ファイルをコンパイルします。
$ rake assets:precompile RAILS_ENV=production

次に、config/secret.yml を開いて以下の行があるか確認してください。

config/secret.yml
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

これは本番環境で起動するためのキーを設定するものです。secret_key_base: の値が <%= ENV["SECRET_KEY_BASE"] %> となっていて、ここに環境変数で設定したランダムな文字列が設定されます。このファイルは特に編集する必要はないので、確認だけでOKです。もし <%= ENV["SECRET_KEY_BASE"] %> ではなくランダムな文字列が設定されていたら <%= ENV["SECRET_KEY_BASE"] %> に書き換えてください。

では、環境変数に SECRET_KEY_BASE を設定しましょう。まずはランダムなキーを生成します。Railsのプロジェクトディレクトリで以下のコマンドを実行します。
$ rake secret

実行して出てきたランダムな文字列をコピーします。

次に .bash_profile ( .bashrc ) に以下の1行を追加します。

.bash_profile
export SECRET_KEY_BASE=ランダムなキー

ランダムなキーの箇所に $ rake secret で生成した文字列をペーストしてください。

そして .bash_profile を再読み込みして反映させます。
$ source ~/.bash_profile

これで環境変数が設定できました。ちゃんと設定できているかを確認するには以下のコマンドを実行します。
$ env | grep SECRET_KEY_BASE

SECRET_KEY_BASE=先ほど設定したランダムなキー が表示されればOKです。

最後に Unicorn をもう一度起動します。
$ rake unicorn:start

ブラウザで開発環境のときと同様のドメイン名もしくはIPアドレスにアクセスしてページが表示されればOKです。お疲れさまでした!

2回目以降の起動方法

ここまでがちゃんと動いていたら、2回目以降、コードを編集したりしてUnicornを再起動する際に行うコマンドは以下の通りです。

$ rake db:migrate RAILS_ENV=production
$ rake assets:precompile RAILS_ENV=production
$ rake unicorn:stop && rake unicorn:start

マイグレーションと、SCSSやCoffeeScriptのコンパイルは、それぞれマイグレーションをしていない場合、CSSやJavaScriptを編集していない場合は実行する必要がありませんが、実行しても特に問題はないです。勘違いを防ぐためにとりあえず実行しておくのでも良いでしょう。

また、作成したunicorn.rakeのスクリプトには、restartというコマンドが用意されていますが、これは前回起動していたプロセスをキルしないので、不要なUnicornプロセスがたまってしまい良くないので、stopしてからstartする、というコマンドにしました。

参考サイト

Unicorn や Nginx の設定ファイルの書き方

Unicorn の起動・停止スクリプト

/tmp にソケットファイルを置くと失敗するときの解決策

本番環境での起動方法

気になったこと

GitHubで編集を提案

Discussion