#293 Nginx & Unicorn pro
- Download:
- source codeProject Files in Zip (47.9 KB)
- mp4Full Size H.264 Video (57.9 MB)
- m4vSmaller H.264 Video (25.5 MB)
- webmFull Size VP8 Video (25.4 MB)
- ogvFull Size Theora Video (61.4 MB)
今回のエピソードでは、nginxとUnicornを使ってRailsアプリケーションを公開する方法を紹介します。このホスティングソリューションは、Githubや37 Signalsなどの大規模なサイトでRailsアプリケーションをホストするのに利用されており、今回はこれをLinuxサーバ上に設定するために必要なステップを順を追って見ていきます。
通常はもちろんこれを本番環境のサーバでおこないますが、もしこのような作業をおこなうのが初めての場合は、仮想マシンを使用して安全な環境でいろいろテストしてみるのがいいでしょう。今回はVagrantと前回のエピソードで作成した仮想マシンを使用します。この仮想マシン(VM)にはrbenvを使ってすでにRuby 1.9.2がインストール済で、/vagrant
には共有ディレクトリが設定されていてごく基本的なRailsアプリケーションが置かれています。アプリケーションはすでにサーバ上にあるので、Capistranoやデプロイメントについては特に触れません。
VM上にすでにRailsアプリケーションが起動しています。まず最初にアプリケーションがあるディレクトリからSSHで仮想マシンに入ります。
$ vagrant ssh
次にVM上で/vagrant
に移動し、デフォルトのRailsサーバを実行します。
vagrant@lucid32:~$ cd /vagrant vagrant@lucid32:/vagrant$ bundle exec rails s
ポート3000番でWEBrick配下でアプリケーションが起動し、このポートがホストマシン側と共有されているので、ブラウザからアプリケーションを見ることができます。開発作業のためにはこれでOKですが、本番環境ではこれとは違う方法で、nginxとUnicornの下でサーバを稼働させます。
Nginxのインストール
ではまずnginxをインストールします。最新バージョンをインストールしたい場合はソースからおこなうのがいいのですが、今回はapt-getを使うことにします。
vagrant@lucid32:/vagrant$ sudo apt-get install nginx
nginx
コマンドでサーバを起動できますが、インストーラがinit.d
ファイルもインストールするので、これを使ってnginxサーバを管理することもできます。
vagrant@lucid32:/vagrant$ /etc/init.d/nginx -h Usage: nginx {start|stop|restart|reload|force-reload|status|configtest}
このinit.d
コマンドを直接実行することもできますが、service
コマンドを介して操作することも可能なので今回はこの方法で起動することにします。
vagrant@lucid32:/vagrant$ sudo service nginx start Starting nginx: the configuration file /etc/nginx/nginx.conf syntax is ok configuration file /etc/nginx/nginx.conf test is successful nginx.
出力を見ると、nginxが起動に成功し設定ファイルの/etc/nginx/nginx.conf
が正常に解析されたことがわかります。
nginxがVM上のポート80番で起動したのですが、今はまだVagrantがポート80番上のトラフィックをフォワードしていないので、ホストマシン上のブラウザからサイトを見ることはできません。Vagrantfile
を修正して、このトラフックがホストの8080番ポートにフォワードされるようにします。
# Forward a port from the guest to the host, which allows for outside # computers to access the VM, whereas host only networking does not. config.vm.forward_port "http", 80, 8080 config.vm.forward_port "rails", 3000, 3000
この変更を有効化するにはVagrantをリロードする必要があるので、SSHセッションからexit
してから仮想マシンをリロードして、新しい設定を反映させます。
vagrant@lucid32:/vagrant$ exit logout Connection to 127.0.0.1 closed. $ vagrant reload
ポート80番のトラフィックはホストマシンのポート8080番にフォワードされるようになります。init.d
ファイルによってnginxも自動的に再起動されているはずです。http://localhost:8080にアクセスするとnginxのデフォルトページが表示されます。
nginxの設定
nginxが正常に動作していることがわかったので、設定方法を見ていきます。メインの設定ファイルが/etc/nginx/nginx.conf
にあり、デフォルトでは以下のようになっています。
user www-data; worker_processes 1; error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; events { worker_connections 1024; # multi_accept on; } http { include /etc/nginx/mime.types; access_log /var/log/nginx/access.log; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; tcp_nodelay on; gzip on; gzip_disable "MSIE [1-6]\.(?!.*SV1)"; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
この設定はデフォルトのままで大丈夫ですが、少なくとも何が書いてあるかくらいは理解しておくのがいいでしょう。設定を見ると、nginxをwww-data
というユーザで実行していて、一つのワーカープロセスが起動して1024までの接続を受け付けられることがわかります。nginx wikiはこれらの設定を理解するための優れた情報源で、Core Moduleページにはメインの設定項目についてのドキュメントがあります。
メインの設定ファイルには、その他のいくつかの設定ファイル(例えばMIMEタイプを設定するためのmime.types
)をインクルードする行が含まれています。ファイルの末尾の2行では、/etc/nginx/conf.d/
ディレクトリ以下のすべての.conf
ファイルと、 sites-enabled
ディレクトリ以下のすべてのファイルをインクルードしています。sites-enabled
ディレクトリにはサイト固有の設定を置きます。
sites-enabled
の中を見るとひとつだけdefault
というファイルがありますが、これはsites-available
ディレクトリ内のdefault
ファイルへのシンボリックリンクです。このファイルには先に見た「Welcome to nginx!」のページを含むサイトの設定情報が含まれています。アプリケーションの設定のためにこのファイルをテンプレートとして使用することもできるのですが、今回はゼロから作ることにします。
設定情報はすべてRailsアプリケーションの中に持つようにして、サーバの正しい場所にそのファイルのシンボリックリンクを作成するのがいいでしょう。もちろんサーバの構成によっては違うように設定する必要があるかもしれません。アプリケーションのconfigディレクトリにnginx.conf
というファイルを新規に作成し、まず始めにそこに必要最小限の設定情報を入力します。
server { listen 80 default; # server_name example.com; root /vagrant/public; }
設定ファイルに追加したのはserver
ブロックで、これはApacheのVirtualHostに似ています。サーバに対してポート80番でlisten
するよう指示してdefaultを指定し、一致するサーバ名が見つからない場合にデフォルトでこのサーバを使用するようnginxに指示します。アプリケーションにドメイン名があれば、server_name
オプションを渡せますが、localhostでサイトを見るだけなのでこれは使いません。最後にroot
オプションを使って、nginxに対してアプリケーションの静的ファイルの場所を指示します。
ブラウザでサイトを見てみる前に、sites-enabled
ディレクトリからデフォルトサーバを削除し、設定ファイルのシンボリックリンクを作成します。リンクの名称をtodo
とします。
vagrant@lucid32:/etc/nginx/sites-enabled$ sudo rm default vagrant@lucid32:/etc/nginx/sites-enabled$ sudo ln -s /vagrant/config/nginx.conf todo
これらの修正を反映するためにnginxを再起動します。
vagrant@lucid32:/etc/nginx/sites-enabled$ sudo service nginx restart
ホストマシンでlocalhost:8080
にアクセスすると、「Welcome aboard」のページが表示されるので、アプリケーションのindex.html
ページは正しく公開されています。しかし、Railsサーバが現在オフラインのため、Railsが動的に生成するものはまだ動作しません。asset pipelineが動作していないため画像が表示されないことに注意してください。また、environmentを見ようとすると404エラーが発生します。これは静的ファイルではないものを見ようとしたときにnginxが返すエラーです。
Railsアプリケーションにリクエストを送信する
設定ファイルを調整して、Railsアプリケーションにリクエストを送るようにする必要があります。ただしすべてのリクエストではなく、静的ファイルが存在しない場合のみです。このためにtry_files
を利用できます。
server { listen 80 default; # server_name example.com; root /vagrant/public; try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_pass http://localhost:3000; } }
URLでユーザから渡されたパスを含む$uri
変数を用いて、try_files
に試したい場所のリストを渡します。まず最初にindex.html
ファイルがその場所に存在するかをチェックし、なければ次にその場所自体が存在するかをチェックします。どちらもない場合、Railsアプリケーションにフォールバックします。これをnamed locationというしくみを介して処理し、名前を@unicorn
としました。locationコマンドを用いてnamed locationを作成し、このコマンドの中でproxy_pass
を呼び出してnginxに対してリクエストをUnicornサーバに渡すよう指示します。まだUnicornが設定できていないので、取りあえずリクエストをWEBrickに渡します。
nginxを再起動し、/vagrant
ディレクトリに戻ってWEBrickを起動します。
vagrant@lucid32:/etc/nginx/sites-enabled$ sudo service nginx restart vagrant@lucid32:/etc/nginx/sites-enabled$ cd /vagrant vagrant@lucid32:/vagrant$ bundle exec rails s
ページをリロードすると、動的なコンテンツがWEBrickによって公開されます。
WEBrick serverをkillしてページをリロードすると、nginxがRailsアプリケーションにアクセスできなくなったので動的コンテンツについては502エラーが表示されます。
このようなエラーが発生した場合はnginxのエラーではなくRailsの500エラーページを表示させるべきでしょう。そのためにはnginxの設定ファイルのserverセクションに次の行を追加するだけです。これはnginxに対して、多種にわたる500関連のエラーのうちのいずれかが発生した場合に、Railsアプリケーションの500.html
ページを表示するよう指示しています。
error_page 500 502 503 504 /500.html;
nginxの設定ファイルには他にもいくつか変更できる部分がありますが、今はそのままにしておいて、その代わりにUnicornを起動して動作させることに集中しましょう。
Unicornのインストール
Unicornについてよく知らないという場合は、Githubのブログ記事が一読の価値があります。 記事には、その利点についての多くの有用な情報と、設定方法についての詳細が含まれています。
インストールするためには、アプリケーションのGemfile
にunicorn
gemを追加します。
gem 'unicorn'
インストールを完了するために仮想マシン上でbundle
コマンドを実行します。Unicornがインストールできたら、アプリケーションをどう実行するかを指示するために設定ファイルを追加します。Unicornの設定ファイルはRubyで書くので、新規ファイルを/config/unicorn.rb
に作成し次のコードを記述します。
working_directory "/vagrant" pid "/vagrant/tmp/pids/unicorn.pid" stderr_path "/vagrant/unicorn/unicorn.log" stdout_path "/vagrant/unicorn/unicorn.log" listen "/tmp/unicorn.todo.sock" worker_processes 2 timeout 30
この設定ファイルでは、まずUnicornに対してRailsアプリケーションがどのディレクトリにあるかを指示し、pidファイルとログファイルへのパスを指定します。次にlisten
を呼び出してsocketへのパスを渡します。(あるいはその代わりにポート番号を渡します。)最後にworker_processes
を呼び出してUnicornが起動するRailsのインスタンス数を指定し、タイムアウトを30秒に指定します。この時間内にRailsアプリケーションがレスポンスを返さなかった場合、Unicornはそれを終了して再起動します。UnicornのウェブサイトのConfiguratorページにUnicornの設定オプションについてさらに情報があります。
Unicornに対して起動を試すために十分な情報を与えたので、bundle
を介してunicorn
コマンドを実行します。この時に-c
オプションで設定ファイルへのパスを渡し、-D
を指定してデーモンプロセスとして実行します。(unicorn_rails
コマンドも利用できますが、Rails 3はRackに完全対応しているのでそれを使う必要はありません。)
vagrant@lucid32:/vagrant$ bundle exec unicorn -c config/unicorn.rb -D
もしエラーが表示されなければ、Unicornが正しく起動したということです。しかしブラウザでページが見えるようにするには、nginxの設定ファイルを修正して動的ページをWEBrickではなくUnicornに渡さなければいけません。そのためにupstream
ブロックを介して名前を与えます。このブロックでserver
を用いてUnix socket(Unicornの設定ファイルで使用したもの)を指定します。ここではさらにfail_timeout
を0
に設定して、もしRailsアプリケーションの反応がなくなってUnicornがタイムアウトした場合も正しく処理されるようにします。ファイルの下の方でproxy_pass
をhttp://unicorn
に設定して、上のupstreamブロックを指定するようにします。
upstream unicorn { server unix:/tmp/unicorn.todo.sock fail_timeout=0; } server { listen 80 default; # server_name example.com; root /vagrant/public; try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_pass http://unicorn; } error_page 500 502 503 504 /500.html; }
nginxを再起動すると、ポート8080番のUnicornを介してページが見えるようになります。
nginxの詳細設定
nginxに渡した設定オプションは、サービスを起動させるために最低限必要なもののみでしたが、その他にも設定するべきオプションがあります。UnicornプロジェクトのGithubページには多くの種類のサンプル設定があり、その中にはnginx用のものも含まれています。このサンプル設定は非常にわかりやすくドキュメント化されているので、一度目を通してみて自分のアプリケーションの設定ファイルに必要となるセクションを抽出することをお勧めします。例えば、 location
ブロックに渡すproxyオプションがいくつかあり、いろいろなヘッダを設定したりリダイレクト処理をオフにすることができます。またclient_max_body_size
を設定してユーザが大きなファイルをアップロードできるように、かつkeepalive_timeout
でタイムアウトしないようにします。
location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://unicorn; } client_max_body_size 4G; keepalive_timeout 5; error_page 500 502 503 504 /500.html;
最後にLinuxシステムを使用している場合はlisten
コマンドにdeferred
オプションを追加して、TCP Defer Acceptオプションを使用するようにします。
server { listen 80 default; # Rest of block omitted. }
これらのオプションはすべてnginx wikiで詳しく解説されているので、さらに情報が必要な場合はチェックしてみてください。
init.dスクリプトでUnicornを自動起動する
Unicornの例を見ている合間に、Unicornプロセスの起動と管理で使用するinit.d
シェルスクリプトのサンプルであるinit shell scriptも見てみましょう。このスクリプトをunicorn_init.sh
という名前でサーバの/config
ディレクトリに追加します。これを利用できるようにするために、ファイルの最初にある変数を設定します。このうちのひとつがunicorn
実行ファイルへのパスですが、UnicornをBundlerでインストールした場合はこれをどう設定すればいいでしょうか?
Bundlerがこれへの解決策を持っています。bundle install --binstubs
を実行すると、アプリケーションに/bin
ディレクトリが作成され、そこにはBundlerが管理するすべてのgemの実行ファイルが含まれます。
vagrant@lucid32:/vagrant$ ls bin erubis rake ri scss tilt unicorn rackup rake2thor sass therubyracer tt unicorn_rails rails rdoc sass-convert thor turn
CMD
でこのunicorn
実行ファイルを指定できます。またAPP_ROOT
変数を変更して、Railsアプリケーションがある場所を指定します。
#!/bin/sh set -e # Example init script, this can be used with nginx, too, # since nginx and unicorn accept the same signals # Feel free to change any of the following variables for your app: TIMEOUT=${TIMEOUT-60} APP_ROOT=/vagrant PID=$APP_ROOT/tmp/pids/unicorn.pid CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb" #INIT_CONF=$APP_ROOT/config/init.conf action="$1" set -u #test -f "$INIT_CONF" && . $INIT_CONF
サンプルファイルにはINIT_CONF
オプションもあり、アプリケーション固有の設定ファイルを指定しています。このファイルはここでは我々のアプリケーションの設定ファイルになるので不要なため、これとこの変数を呼び出すtest
コマンドを削除します。
このスクリプトはrootユーザで実行されるので、UnicornとRailsアプリケーションもrootで実行されます。これをvagrantユーザで実行したいので、ファイルの$CMD
への呼び出しをすべてこのように置き換えます。
su -c "$CMD" - vagrant
このファイルを実行可能にするために次のコマンドを実行します。
vagrant@lucid32:/vagrant$ chmod +x config/unicorn_init.sh
次にinit.d
ディレクトリ内のこのファイルへのリンクを作成します。名前は単純にunicorn
としますが、もし複数のアプリケーションを実行している場合はより具体的な名前にするのがいいでしょう。
vagrant@lucid32:/vagrant$ sudo ln -s /vagrant/config/unicorn_init.sh /etc/init.d/unicorn
ファイルを次のコマンドで実行します。
vagrant@lucid32:/vagrant$ sudo service unicorn restart reloaded OK
これらの設定がすべて終わった状態でサーバを再起動すると、nginxとUnicornが自動的に起動するはずです。しかし、今回はVagrantで実行しているため問題があり、これはうまくいきません。VagrantはRailsアプリケーションを含む/vagrant
共有フォルダを仮想マシンの起動後(init.d
スクリプトの実行後)にマウントします。つまりinitスクリプトの実行時にはアプリケーションのconfig
ディレクトリ内のinit.sh
ファイルは利用できないため、サービスを手動で開始しなくてはいけないということです。
vagrant@lucid32:/vagrant$ sudo service nginx restart Restarting nginx: the configuration file /etc/nginx/nginx.conf syntax is ok configuration file /etc/nginx/nginx.conf test is successful nginx. vagrant@lucid32:/vagrant$ sudo service unicorn restart reloaded OK
実際のサーバを立てる場合は、マシンの起動時に設定スクリプトが利用可能な状態になるので、この点については心配する必要はありません。
localhost:8080
でアプリケーションにアクセスすると、稼動しています。しかしまだ開発モードで実行しています。本番稼動モードで実行するにはUnicornに-E
オプションを渡して環境を指定します。
CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
Rubyのローカルバージョンの扱い
先に設定したbinstubについてもう一点注意することがあります。binstubの内の一つを見てみると、それが/usr/bin/env/ruby
内で実行されるスクリプトだということがわかります。
#!/usr/bin/env ruby # # This file was generated by Bundler. # # The application 'unicorn' is installed as part of a gem, and # this file is here to facilitate running it. # require 'pathname' ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) require 'rubygems' require 'bundler/setup' load Gem.bin_path('unicorn', 'unicorn')
rbenvを設定するときにRuby 1.9.2のglobal versionを設定したのでこれは問題ありません。このプロジェクトで使用するRubyのlocal version(これはもちろんglobalに使用するものと同じバージョンです)をこのように指定すると、これによってアプリケーションで実行したいRubyのバージョンを指定する.rbenv-version
ファイルがプロジェクトに追加されます。
vagrant@lucid32:/vagrant$ rbenv local 1.9.2-p290
もしlocal versionがglobal versionと違う場合、bin
ディレクトリ内のコマンドを実行するときに問題が発生します。これを解決するためには、バイナリファイルを検索してruby
の呼び出しをすべてruby-global-exec
に置き換えます。これによって常にローカルのRubyのバージョンが実行されることが保証されます。
vagrant@lucid32:/vagrant$ sed -i 's/env ruby/env ruby-local-exec/' bin/*
nginxとUnicornに関する今回のエピソードは以上です。ここではすべてをカバーすることはできませんでした。今回触れられなかったものの中でも特に見ておくべきものの一つは、Unicornのpreload_appオプションです。これはRailsアプリケーションを、分割したプロセスにフォークされる前にロードします。このアプローチによって、大規模なアプリケーションを通常よりもずっと短時間でロードできます。
この他にも読んでおくべきブログ記事がいくつかあります。一つがTyler Birdによる「Everything You Need to Know About Unicorn(Unicornについて知っておくべきすべてのこと)」で、Unicornを理解するのに役立つ興味深い情報がたくさん含まれています。もう一つはRyan Tomaykoによる「I Like Unicorn because it’s Unix(私がUnicornを好きなのはそれがUnixだから)」です。これはUnicornの内部のしくみを説明したすばらしい記事です。