最近把 s.yanghao.org 从 php + python 转型到 Rails 框架, 第一次使用 Rails 遇到的麻烦真不少,最麻烦的还真得算部署, 如果不计较安全问题,统统使用 root 帐号操作,这些问题就不是什么问题, 为了安全,我们做以下限定:
- 不能使用 root 帐号运行 unicorn 进程
- 运行 unicorn 的帐号不能有 sudo 权限
- 为了使服务器能运行不同 ruby 版本,应使用 rvm 用户模式安装 ruby
这些为我们带来的问题:
- 开机需要自动运行 unicorn
- 重新部署之后需要重启 unicorn
开机自动运行 unicorn 可以使用 init.d 脚本,unicorn 官方给的也有例子
#!/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=/home/x/my_app/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="/usr/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
old_pid="$PID.oldbin"
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $old_pid && kill -$1 `cat $old_pid`
}
case $action in
start)
sig 0 && echo >&2 "Already running" && exit 0
$CMD
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig HUP && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
upgrade)
if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
then
n=$TIMEOUT
while test -s $old_pid && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $old_pid
then
echo >&2 "$old_pid still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
$CMD
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac
上面的自动启动脚本很好,但是他只在使用系统 ruby 或者使用 rvm root 模式安装时候才能很好的运行, 我们使用 rvm 用户模式安装的 ruby,上面的脚本中 unicorn_rails 根本找不到,即便是指定了 HOME 中 的 unicorn_rails 全路径,那么还是找不到 ruby,这个问题可以使用 rvm 的 wrapper 功能来解决,假如 现在我们使用的是 ruby 1.9.3,执行下面命令来获得 wrapper 过的 unicorn_rails:
rvm wrapper 1.9.3 ruby-1.9.3 unicorn_rails
执行过之后会在 $HOME/.rvm/bin 目录下生成 ruby-1.9.3_unicorn_rails 文件,经过修改很的 init.d
#!/bin/sh
set -e
set -u
TIMEOUT=${TIMEOUT-60}
UNICORN=/home/x/.rvm/bin/ruby-1.9.3_unicorn_rails
APP_ROOT=/home/x/my_app/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="$UNICORN -D -c $APP_ROOT/config/unicorn.rb -E production"
action="$1"
old_pid="$PID.oldbin"
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $old_pid && kill -$1 `cat $old_pid`
}
case $action in
start)
sig 0 && echo >&2 "Already running" && exit 0
$CMD
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig HUP && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
upgrade)
if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
then
n=$TIMEOUT
while test -s $old_pid && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $old_pid
then
echo >&2 "$old_pid still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
$CMD
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac
unicorn 配置文件官方也给的有例子,官方地址:unicorn.conf.rb
官方给的这个例子也有个问题,就是得指定 working_directory 地址,看看我们刚才在 init.d 的脚本中已经指定过 我们程序的地址了,就是那个 APP_ROOT,把项目地址写的到处都是可不是什么好主意,unicorn 配置文件我们增加下面代码
require 'pathname'
path = Pathname.new(__FILE__).realpath
path = path.sub('/config/unicorn.rb', '')
APP_PATH = path.to_s
另外就是别忘了指定用户
user "unprivileged_user", "unprivileged_group"
这样我们的 unicorn 只有 master 是 root 用户在,work 进程就是 unprivileged_user 在运行了
第二个问题部署之后 su 之后,对 unicorn 进程发送 USR2 信号即可,但是配合 capistrano 部署 还没找到最好的办法,具体问题是由于使用了 rvm,那么就得使用 rvm-capistrano 配合, 但是 rvm-capistrano 默认使用 $HOME 下面的 rvm-shell 来执行命令,即便是做到了自动 su 到 root, 那么在rvm-shell中运行 service unicorn upgrade 也是有问题的,这个问题还不知道怎么解决, 目前我只能手工去 service unicorn upgrade