unicorn 部署 Ruby on Rails 开机自启动

最近把 s.yanghao.org 从 php + python 转型到 Rails 框架, 第一次使用 Rails 遇到的麻烦真不少,最麻烦的还真得算部署, 如果不计较安全问题,统统使用 root 帐号操作,这些问题就不是什么问题, 为了安全,我们做以下限定:

  1. 不能使用 root 帐号运行 unicorn 进程
  2. 运行 unicorn 的帐号不能有 sudo 权限
  3. 为了使服务器能运行不同 ruby 版本,应使用 rvm 用户模式安装 ruby

这些为我们带来的问题:

  1. 开机需要自动运行 unicorn
  2. 重新部署之后需要重启 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

This entry was posted in ruby on rails and tagged . Bookmark the permalink.

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>