nginx + unicorn + Rails on Mac

とりあえず動かしてみたのでメモ。

unicornを動かす

まずはgemをインストール。

$ gem install unicorn

unicornの処理を設定する

$ cd <RAILS_ROOT>
$ vi config/unicorn.rb

<RAILS_ROOT>/config/unicorn.rbはこんな感じ(nginx + unicorn を試してみたからほぼそのまま拝借):

# ワーカーの数
worker_processes 2

# ソケット経由で通信する
listen File.expand_path('tmp/sockets/unicorn.sock', ENV['RAILS_ROOT'])

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

# ダウンタイムなくす
preload_app true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      # SIGTTOU だと worker_processes が多いときおかしい気がする
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

そして起動。

$ unicorn_rails -c config/unicorn.rb -E production -p 5000 -D

unicorn_railsRailsアプリケーションをunicornで起動するためのコマンド。最近のRailsはRackに乗っかっているからunicornコマンドだけでも十分だけど、unicorn_railsならではのメリットもあるらしい(さくらの VPS に登録してみた(4) nginx + unicorn参照)。

-cオプションで設定ファイルを指定、-EでRAILS_ENVを指定(デフォルトはdevelopment)、-pでポートを指定(デフォルトは8080)、-Dでデーモンとして動作。それ以外については-hオプションで一覧が表示される。

nginxを動かす

まずHomebrewでインストールして起動。

$ brew install nginx
$ cp /usr/local/Cellar/nginx/1.0.4/org.nginx.nginx.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/org.nginx.nginx.plist

この時点で http://localhost:8080 にアクセスするとnginxの初期画面が表示されるはず。

apacheでいうところのconf的なものは/usr/local/etc/nginx以下に入れられる。 さて、設定。

$ cd /usr/local/etc/nginx
$ mkdir -p sites-available sites-enabled
$ vi nginx.conf
$ vi sites-available/default
$ ln -s /usr/local/etc/nginx/sites-available/default sites-enabled/default

まずは初期画面に関する設定をnginx.confからsites-available/defaultに書き写す。

nginx.confの差分:

@@ -32,87 +32,6 @@ http {
 
     #gzip  on;
 
-    server {
-        listen       8080;
-        server_name  localhost;
-
-        #charset koi8-r;
-
-        #access_log  logs/host.access.log  main;
-
-        location / {
-            root   html;
-            index  index.html index.htm;
-        }
-
-        #error_page  404              /404.html;
-
-        # redirect server error pages to the static page /50x.html
-        #
-        error_page   500 502 503 504  /50x.html;
-        location = /50x.html {
-            root   html;
-        }
-
-        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
-        #
-        #location ~ \.php$ {
-        #    proxy_pass   http://127.0.0.1;
-        #}
-
-        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
-        #
-        #location ~ \.php$ {
-        #    root           html;
-        #    fastcgi_pass   127.0.0.1:9000;
-        #    fastcgi_index  index.php;
-        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
-        #    include        fastcgi_params;
-        #}
-        # deny access to .htaccess files, if Apache's document root
-        # concurs with nginx's one
-        #
-        #location ~ /\.ht {
-        #    deny  all;
-        #}
-    }
-
-
-    # another virtual host using mix of IP-, name-, and port-based configuration
-    #
-    #server {
-    #    listen       8000;
-    #    listen       somename:8080;
-    #    server_name  somename  alias  another.alias;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
-
-
-    # HTTPS server
-    #
-    #server {
-    #    listen       443;
-    #    server_name  localhost;
-
-    #    ssl                  on;
-    #    ssl_certificate      cert.pem;
-    #    ssl_certificate_key  cert.key;
-
-    #    ssl_session_timeout  5m;
-
-    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
-    #    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
-    #    ssl_prefer_server_ciphers   on;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
-
+    include /usr/local/etc/nginx/sites-enabled/*;
 }

末尾の追加項目は、nginx.confにべた書きするのではなく、sites-enabled/以下に分割されたファイルを読み込むように設定するための記述である。 sites-available/defaultは次のようにした。

+server {
+     listen       8081;
+     server_name  localhost;
+     
+     location / {
+         root   html;
+         index  index.html index.htm;
+     }
+}
+

もともとは8080をlistenしていたが、Railsアプリのほうに8080を使いたかったのでここではとりあえず8081にした。 root html;はドキュメントルートをhtmlディレクトリ(ここでは/usr/local/Cellar/nginx/<VERSION>/htmlになる)に設定している。

ここまでやって文法にミスが無ければnginxを再起動して、 http://localhost:8081 にアクセスしてみる。

$ /usr/local/sbin/nginx -t # 文法チェック
$ launchctl unload ~/Library/LaunchAgents/org.nginx.nginx.plist
$ launchctl load ~/Library/LaunchAgents/org.nginx.nginx.plist

nginxとunicornをつなぐ

次にunicornとつないでRailsアプリを表示してみる。

$ vi sites-available/<APPNAME>-unicorn
$ ln -s /usr/local/etc/nginx/sites-available/<APPNAME>-unicorn sites-enabled/<APPNAME>-unicorn
upstream <APPNAME>-unicorn {
  server unix:/path/to/RAILS_ROOT/tmp/sockets/unicorn.sock;
}

server {
  listen 8080;
  server_name localhost;

  root /path/to/RAILS_ROOT/public;
  access_log /path/to/RAILS_ROOT/log/access.log;
  error_log /path/to/RAILS_ROOT/log/error.log;

  location / {
    if (-f $request_filename) { break; }
    # ファイルが存在しなければunicornにproxyする
    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;
  }

  # この記述がないと静的ファイルが表示されなかった
  location ~* \.(ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$ {
    expires 1y;
  }
}

ここまでやって文法にミスが無ければnginxを再起動して、 http://localhost:8080 にアクセスしてみる。

$ /usr/local/sbin/nginx -t # 文法チェック
$ launchctl unload ~/Library/LaunchAgents/org.nginx.nginx.plist
$ launchctl load ~/Library/LaunchAgents/org.nginx.nginx.plist

最後に

とりあえず動いた。

以下、気になる点:

  • ApacheとNginx、PassengerとUnicorn、どっちがどういいんだろう?
  • 再起動するためにlaunchctlを呼ぶのは面倒くさい、なにか適当なコマンドはないのかな?
  • passengerのnginxモジュールを入れるためにはnginxを再コンパイルする必要があるみたいで、そうなるとRVMでgemsetを自由に切り替えられる環境とは相性が悪いのでは(本番ではRVMを使わないから関係ないだろうけど)
  • nginxの設定ファイルのセミコロンが面倒くさい

ちなみに今回なぜ80を使わずに808xを使ったかというと、powが80を奪ってしまっているから。Apacheに関してはIPv6で動かして回避するという方法があるらしいけど、nginxはどうなんだろう?