とあるプロジェクトで Nginx + Passenger という組合せで動かしていたのだが、リクエストの同期処理動作で具合が悪かったので Puma に切り替えた話。

環境など

  • Nginx や Passenger は設定済みで動作していた
  • Capistrano でデプロイしている
  • システム全体に rbenv で ruby をインストールしている
  • デプロイユーザーが別にいる (今回の場合 deployer)

sudo の許可

deployer で puma の再起動ができるようにするため sudo 権限を与える。全コマンドの sudo は危険なので特定コマンドのみに制限する。

$ visudo
deployer ALL=(ALL) ALL
deployer ALL=(ALL) NOPASSWD: /sbin/service puma restart

rbenv PATH の設定

1
2
3
export RBENV_ROOT="/usr/local/rbenv"
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)"

起動スクリプトを作成

所有者は root で chmod 755

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/bin/bash
#
# puma-myproject

# chkconfig: 2345 82 55
# processname: puma-myproject
# description: Runs puma-myproject for nginx integration.

# Include RedHat function library
. /etc/rc.d/init.d/functions

# The name of the service
NAME=puma

# The username and path to the myapp source
USER=deployer
APP_PATH=/your-app-path/current

# The PID and LOCK files used by puma and sidekiq
UPID=$APP_PATH/tmp/pids/puma.pid
ULOCK=/var/lock/subsys/$NAME

# The options to use when running puma
#OPTS="-C $APP_PATH/config/puma.rb -e production"
#OPTS="-F $APP_PATH/config/puma.rb"
OPTS="-F /your-app-path/shared/puma.rb"

# Ruby related path update
RUBY_PATH_PATCH="PATH=$PATH:/usr/local/bin:/usr/local/lib && export PATH && "
BUNDLE_CMD=bundle
PUMA_CMD=pumactl
. /etc/profile.d/rbenv.sh

start() {
  cd $APP_PATH
  # Start puma
  echo -n $"Starting $NAME: "
  daemon --pidfile=$UPID --user=$USER $BUNDLE_CMD exec $PUMA_CMD $OPTS start
  puma=$?
  [ $puma -eq 0 ] && touch $ULOCK
  echo

  return $puma
}

stop() {
  cd $APP_PATH

  # Stop puma
  echo -n $"Stopping $NAME: "
  killproc -p $UPID
  puma=$?
  [ $puma -eq 0 ] && rm -f $ULOCK
  echo

  return $puma
}

restart() {
  stop
  start
}

get_status() {
  status -p $UPID $NAME
}

query_status() {
  get_status >/dev/null 2>&1
}

case "$1" in
  start)
    query_status && exit 0
    start
    ;;
  stop)
    query_status || exit 0
    stop
    ;;
  restart)
    restart
    ;;
  status)
    get_status
    exit $?
    ;;
  *)
    N=/etc/init.d/$NAME
    echo "Usage: $N {start|stop|restart|status}" >&2
    exit 1
    ;;
esac

exit 0

puma 設定ファイルを作成

所有者は deployer で chmod 644

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env puma

directory '/your-app-path/current'
rackup "/your-app-path/current/config.ru"
environment 'production'
daemonize true

pidfile "/your-app-path/shared/tmp/pids/puma.pid"
state_path "/your-app-path/shared/tmp/state/puma.state"
stdout_redirect '/your-app-path/current/log/puma.access.log', '/your-app-path/current/log/puma.error.log', true

threads 0,16
workers 0

bind 'unix:///your-app-path/shared/tmp/sockets/puma.sock'

preload_app!

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "/your-app-path/current/Gemfile"
end


on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end

nginx 設定ファイル

バックアップを作っておく

1
2
3
4
5
6
7
8
3c3
< worker_processes  auto;
---
> worker_processes  1;
17a18,20
>     passenger_root /usr/local/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/passenger-5.0.23;
>     passenger_ruby /usr/local/rbenv/versions/2.2.2/bin/ruby;
>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1,4d0
< upstream app {
<     server unix:///your-app-path/current/tmp/sockets/puma.sock;
< }
<
20,27c16,17
<         gzip_static on;
<         proxy_read_timeout 60;
<         proxy_connect_timeout 60;
<         proxy_redirect off;
<         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
<         proxy_set_header Host $http_host;
<         proxy_set_header X-Forwarded-Proto $scheme;
<         proxy_set_header X-Real-IP $remote_addr;
---
>         passenger_enabled on;
>         rails_env sandbox;
30,34d19
<             break;
<         }
<
<         if (!-f $request_filename) {
<             proxy_pass http://app;

起動確認と自動起動

$ service nginx configtest
$ chkconfig puma on

その他動かなかったので修正など

secret キーが必要だったので再生成する。 .env ファイルを作成する。所有者は deployer とする。

1
2
SECRET_KEY_BASE="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
RAILS_ENV="production"

secret キーの生成は以下のコマンドで作れる。

$ bundle exec rake secret