bunlderを使ったWebアプリをプロダクション環境で動かすときに、アプリの起動をどうやって実現しているだろうか。
Apache Passengerを使う場合には、Apacheの起動がアプリの起動につながるので、 アプリの起動はあまり気にしなかったかもしれない。
しかし、例えばNginx × Unicorn/thinの構成などの場合はUnicornやthinの起動もしなければいけなくなってくる。
(あるいはこのようなケースがあるかは謎だが、Unicornやthinを単体で動かそうとしている場合など)
Unicornやthin(例ではthinを扱うが本質は同じ)の自動起動を実現する際の勘所、注意事項をまとめた。
0. 前提
- CentOS6.5上でRubyでのWebアプリケーションを作っている。
- アプリケーションサーバはthinを利用している。
- また、gemパッケージ管理にbundlerを利用している。
1. 開発環境でよくするアプリの起動
開発環境では、アプリケーションのログの閲覧性なども兼ねて以下のようにアプリを起動していた。
$ bundle exec rackup
$ bundle exec thin start
でも、これではいつまでたってもプロダクション環境での利用はできません。
2. 上記方法ではプロダクション環境で利用できない理由
当然のことながら、プロダクション環境ではいちいち手動でコマンドを実行しアプリケーションを立ち上げるわけにはいかない。
例えば、なんらかの理由でサーバが再起動してしまった場合には、
このままではアプリケーションが自動的に立ち上がらないため、サービスの停止につながってしまう。
ではどうするのか?
以下の状態であることがプロダクション環境では理想なのではないだろうか?
- オリジナルアプリケーションもserviceコマンドで起動・停止ができる
- 他のサービスと同様の操作方法が可能なのでわかりやすい
- サーバ立ち上げ時にサービスが自動で起動される
3. 起動スクリプトを作ろう
上記の状態にもっていくためには、起動スクリプトを作らなければならない。
起動スクリプトを作る…!?
「作ったことないし、すぐには作れないよ〜」って思うかもしれないが、
サンプルはたくさんあるし、よく見てみるとそれほど難しくはない。
thinを使ったサンプルを探そうと思うと数は少ないが、Unicornも同じ仕組なので、 "unicorn init script"なんて検索をかけてもいろいろでてくるのでおすすめ。
参考ししたもの
https://gist.github.com/sbeam/3454488
上を参考にしながら、こんな起動スクリプトを作ってみた。(未完成版)
これを/etc/init.d
以下へ配置する。
#!/bin/bash
### BEGIN CHKCONFIG INFO
# chkconfig: 2345 55 25
# description: sample-app
### END CHKCONFIG INFO
SCRIPT_NAME=/etc/init.d/sample-app
CONFIG_PATH=/path/to/config
BUNDLE_CMD=/usr/local/bin/bundle
bundle_exec_thin ()
{
for CONFIG_FILE in "$CONFIG_PATH/*.yml"; do
SITE_DIR=`awk '/^chdir:/ { print $2; }' $CONFIG_FILE`
cd $SITE_DIR
$BUNDLE_CMD exec thin $1 -C $CONFIG_FILE
done
}
case "$1" in
start)
bundle_exec_thin start
;;
stop)
bundle_exec_thin stop
;;
restart)
bundle_exec_thin restart
;;
*)
echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
exit 3
;;
esac
:
起動スクリプトも完成したし、実際にserviceコマンドで実行してみる。
$ sudo service sample-app start
/usr/bin/env: ruby: No such file or directory
んん。。。起動せず、撃沈…
起動スクリプトを作る上での注意
起動しなかった原因に移る前に、起動スクリプトを作る上での注意点を1つ。
chkconfigで認識させるためには冒頭のCHKCONFIG INFO部分(コメントアウト部分)も重要になってくる。
CHKCONFIG INFO部分を書かないままchkconfigでaddしようとすると以下のように怒られます。
$ sudo chkconfig --add sample-app
service sample-app does not support chkconfig
4. serviceコマンド実行時のPATHのはなし
なぜ、serviceコマンドでthinを起動できなかったのか。
調べていくと意外なことがわかった。
serviceコマンドを実行すると中で環境変数のPATHが上書きされてしまう。
【参照】
デーモンの起動・終了にはserviceコマンドを利用しよう - インフラエンジニアway - Powered by HEARTBEATS
試しに、起動スクリプト内にPATHの出力を仕込んで確かめてみる。
起動スクリプトにecho $PATH
を仕込んだ。
$ sudo service sample-app start
/sbin:/usr/sbin:/bin:/usr/bin
起動スクリプト内でbundleやrubyがインストールされているディレクトリに
PATHを通すことで、解決することにした。
(もっと美しい方法があれば教えて下さい。。。)
5. 起動スクリプト修正(完成版)
上記の通り起動スクリプトを修正したものが以下。
#!/bin/bash
### BEGIN CHKCONFIG INFO
# chkconfig: 2345 55 25
# description: sample-app
### END CHKCONFIG INFO
# 以下を追加
export PATH=/usr/local/bin:$PATH
SCRIPT_NAME=/etc/init.d/sample-app
CONFIG_PATH=/path/to/config
BUNDLE_CMD=/usr/local/bin/bundle
bundle_exec_thin ()
{
for CONFIG_FILE in "$CONFIG_PATH/*.yml"; do
SITE_DIR=`awk '/^chdir:/ { print $2; }' $CONFIG_FILE`
cd $SITE_DIR
$BUNDLE_CMD exec thin $1 -C $CONFIG_FILE
done
}
case "$1" in
start)
bundle_exec_thin start
;;
stop)
bundle_exec_thin stop
;;
restart)
bundle_exec_thin restart
;;
*)
echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
exit 3
;;
esac
:
(PATHを通したのでbundleコマンドはフルパスでなくても大丈夫ですよ...)
最後に起動スクリプトを/etc/init.d
以下に配置し、
忘れずにchkconfigに登録しましょう。
$ sudo chkconfig --add sample-app
【おまけ】sudo だとrubyやgem、bundleが使えない?
rootユーザではrubyやgem, bundleが使えるけど、sudoで実行すると使えない…
という悩みの人も多いのではないだろうか。
$ sudo gem install xxxxx
sudo: gem: command not found
sudoでの実行はrootユーザで実行することなのになぜ実行できないか。
これはsudoを使うときに/usr/local/binが許可されていないからだ。
visudoでsecure_pathの設定を見直すとよい。
sudoersのsecure_pathについて -- ぺけみさお