2012-06-19
_ tDiary を unicorn で動かすまでのもろもろ
サーバーの引っ越し作業を機に tDiary を Unicorn で動かすようにしたので,このサーバーでの設定について記録しておきます.サーバーの環境は以下の通り.
- さくらの VPS 1GB
- Ubuntu 12.04 Server (64bit)
- Ruby 1.9.3-p194 (shared install of rbenv)
文中でのコマンドなどは必要に応じて sudo などで実行しています.
最新の tDiary を入手する
最新の安定版は 3.1.3 ですが,リリース後に特に Rack 環境まわりの変更が充実しているので,最新版を github から clone します.ちなみにこのサーバーでは tDiary を/opt/tdiary 以下で管理し,所有者を www-data ユーザーとしています.環境構築時は作業ユーザーの権限にしておいて,最後に必要に応じて権限を変更するほうがいろいろ楽かもしれないです.
$ mkdir /opt/tdiary
$ cd /opt/tdiary
$ git clone git://github.com/tdiary/tdiary-core.git
必要なライブラリのインストール
Rack 環境で動作させるために必要なライブラリについては,Gemfile で定義されているので,bundler を利用してインストールします.特にまっさらな環境の場合,coffeescript から execjs を介して JavaScript ランタイムを実行するので,nodejs などなんらかの JavaScript ランタイムがインストールされている必要があります.ここでは Gemfile に therubyracer を追加して対応することにしました.現在は Gemfile に記述されているので,追加する必要はありません.
diff --git a/Gemfile b/Gemfile
index 9ff550f..ee45113 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,7 @@ source :rubygems
gem 'rake'
gem 'coffee-script'
+gem 'therubyracer'
# Use rack environment
gem 'rack'
Gemfile へ therubyracer を追加したら,bundler をインストールし,その後,必要なライブラリをインストールします.インストール先は tdiary-core/.bundle 以下とし,test,development,production グループのライブラリは除外しています.production グループは主に Heroku 向けのライブラリが定義されていて,今のところ,DB も memcached も使わないので除外しています.
$ cd /opt/tdiary
$ rbevn local 1.9.3-p194
$ gem install bundler
$ rbenv rehash
$ cd tdiary-core
$ bundle install --path .bundle --without test development production
tdiary.conf を作成して rackup してみる
tDiary の設定ファイル tdiary.conf を作成します.tdiary.conf.sample をコピーして必要な部分を修正すれば基本的に問題ありません.日記データの保存先を指定する @data_path に適切なパスを指定します.書き込み先は,最終的に unicorn を実行するユーザーが書き込み権限のある場所を指定します.
$ cp tdiary.conf.sample tdiary.conf
ここまでこればひとまず rackup コマンドでアプリケーションを実行して動作の確認ができます.
$ bundle exec rackup
ブラウザでアクセスしてみて表示されればとりあえず問題なし.tdiary.conf で @index が未設定の場合は,リダイレクト先の URL がデフォルト値として"./"が指定されてアクセスできない場合がありますが,"."を消せばアクセスできます.バグのようだったので修正しました.この現象が起きるのは初回アクセス時のみのよう.
更新ページに認証をかける
認証関係は CGI の時は Web サーバーで行っていたけど,Rack 環境では OmniAuth を利用した Twitter や Github のアカウントでの認証もできるようになっています.このサーバーでは従来と同じような .htpasswd を利用した認証にしています.apache2-utils パッケージなどがインストールされていると,htpasswd コマンドを実行して,,htpasswd ファイルの作成もできますが,tDiary では .htpasswd を作成するための rake コマンドが用意されているので,わざわざインストールしてなくてもよいので便利です.
$ RACK_ENV=production bundle exec rake auth:password:create
ユーザー名とパスワードの入力をすると.htpasswd ファイルが出力されます.
Unicorn で動かす
Unicorn で実行するために,設定ファイルとして unicorn.rb を作成します.中身は,https://github.com/blog/517-unicorn を参考にしています.PID ファイルやログの出力先などは,Ubuntu で標準的に出力される場所に合わせています.また,unicorn のプロセスは master プロセスを root ユーザーで実行し,fork したプロセスは www-data ユーザーで実行するようになっています.
worker_processes 2
app_dir = File.expand_path( File.dirname( __FILE__ ) )
working_directory app_dir
preload_app true
timeout 30
listen "/tmp/tdiary.sock", :backlog => 64
listen 3000, :tcp_nopush => true
pid "/var/run/tdiary.pid"
stderr_path "/var/log/tdiary/stderr.log"
stdout_path "/var/log/tdiary/stdout.log"
before_fork do |server, worker|
old_pid = "/var/run/tdiary.pid.oldbin"
if File.exists?( old_pid ) && server.pid != old_pid
begin
Process.kill( "QUIT", File.read( old_pid ).to_i )
rescue Errno::ENOENT, Errno::ESRCH
end
end
end
after_fork do |server, worker|
begin
uid, gid = Process.euid, Process.egid
user, group = "www-data", "www-data"
target_uid = Etc.getpwnam( user ).uid
target_gid = Etc.getpwnam( group ).gid
worker.tmp.chown( target_uid, target_gid )
if uid != target_uid || gid != target_gid
Process.initgroups( user, target_gid )
Process::GID.change_privilege( target_gid )
Process::UID.change_privilege( target_uid )
end
rescue => e
STDERR.puts "couldn't change user, oh well"
raise e
end
end
unicorn をインストールし,unicorn.rb で指定したログ出力先のディレクトリを作成します.
$ gem install unicorn
$ rbenv rehash
$ sudo mkdir /var/log/tdiary
これで tDiary を unicorn で実行することが可能になります.実行ユーザーは root 権限でないと,上記の設定では PID ファイルの作成で権限がなくエラーとなるので,以下のように実行すると確認できます.
$ sudo /usr/local/rbenv/versions/1.9.3-p194/bin/unicorn -c /opt/tdiary/tdiary-core/unicorn.rb
ここから先は主に運用する際に必要な設定手順について.
tDiary をサービスとして登録する
upstart を利用して tDiary をサーバー起動時に立ち上がるように設定します./etc/init 以下に tdiary.conf ファイルを作成し,以下のような設定にしました.
#!/bin/bash
description "tDairy"
start on runlevel [2345]
stop on runlevel [016]
script
export LANG=en_US.UTF-8
exec /usr/local/rbenv/versions/1.9.3-p194/bin/unicorn -c /opt/tdiary/tdiary-core/unicorn.rb
end script
respawn
これにより service コマンドで tDiary の stop/start が行えるようになります.
$ sudo service tdiary start
ログをローテーションする
しばらく運用していくとログファイルが大きくなってくるのが気になってきたので,logrotate を利用してログのローテーションを行うようにしました./etc/logrotate.d 以下に設定ファイルを配置してローテーションさせるようにします.最初に起動したときに出力されたログファイルの所有者が root の場合,reopen に失敗するので出力するログファイルの所有者は www-data とするように指定します.また,Unicorn は USR1を送るとログファイルを reopen してくれるのでローテーション後に USR1を送るように処理を含めている.
/var/log/tdiary/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ ! -f /var/run/tdiary.pid ] || kill -USR1 `cat /var/run/tdiary.pid`
endscript
}
肥大化した Unicorn プロセスを止める
運用して気になるのは fork したプロセスが肥大化している点.一定数のリクエストをさばくか,既定のメモリ使用量を超えるとプロセスを Kill する便利な unicon_killer を config.ru に追加してみました.
diff --git a/config.ru b/config.ru
index 1a169c7..299e968 100644
--- a/config.ru
+++ b/config.ru
@@ -17,6 +17,12 @@ use OmniAuth::Builder do
# provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
# provider :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET']
end
+
+# Unicorn Killer
+require 'unicorn_killer'
+use UnicornKiller::Oom, 60 * 1024
+use UnicornKiller::MaxRequests, 1000
+
map "#{base_dir}/auth" do
run TDiary::CallbackHandler.new
end
というわけで,このサーバーで tDiary が動くまでの手順でした.Unicorn で実行してしばらくたちますが,特に問題もなく安定して動作しています.レスポンスもなかなかよいので,新しく環境を作るタイミングなどで移行してみてもよいのではないでしょうか.