Ubuntu 20.04 VM 上の Rails 5.2 で Puma ワーカーが起動しない
概要
私は Capistrano を使用し、アプリサーバーとして Puma を使用し、それに応じて Nginx Web サーバーを使用してデモ Rail アプリをデプロイする方法を学んでいました。必要な puma 設定をファイル _stage.rb に設定し、その後 puma を SysVinit サービスとして /etc/init.d/puma_myarticles_staging として設定しました。実行可能ファイル puma_init.sh.erb は、後に次のような puma_init.sh としてリモート サーバーに書き込まれました。
#!/usr/bin/env bash
PATH=/usr/local/bin:/usr/local/sbin/:/sbin:/usr/sbin:/bin:/usr/bin
DESC="Puma rack web server"
NAME=puma_<%=fetch(:full_app_name)%>
SCRIPT_NAME=/etc/init.d/${NAME}
APP_ROOT=<%=current_path%>
PIDFILE=<%= fetch(:puma_pid) %>
STATE_FILE=<%= fetch(:puma_state) %>
log_daemon_msg() { echo "$@"; }
log_end_msg() { [ $1 -eq 0 ] && RES=OK; logger ${RES:=FAIL}; }
run_pumactl(){
[ $# -lt 1 ] && echo "$# params were given, Expected 1" && exit 1
cd ${APP_ROOT} && <%= fetch(:rbenv_prefix) %> bundle exec pumactl -F <%=fetch(:puma_conf)%> $1
}
# Function that starts the puma
#
start_task() {
if [ -e ${PIDFILE} ]; then
PID=`cat ${PIDFILE}`
# If the puma isn't running, run it, otherwise restart it.
if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then
do_start_task
else
restart_task
fi
else
do_start_task
fi
}
do_start_task() {
log_daemon_msg "--> Woke up puma ${APP_ROOT}"
run_pumactl start
}
# Function that stops the daemon/service
#
stop_task() {
log_daemon_msg "--> Stopping puma in path: ${APP_ROOT} ..."
if [ -e ${PIDFILE} ]; then
PID=`cat ${PIDFILE}`
if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then
log_daemon_msg "--> Puma isn't running in path: ${APP_ROOT}."
else
log_daemon_msg "--> About to kill puma with PID: `cat $PIDFILE` ..."
if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then
log_daemon_msg "--> Puma isn't running in path: ${APP_ROOT}."
return 0
else
run_pumactl stop
log_daemon_msg "--> Waiting for status ..."
sleep 5
if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then
log_daemon_msg "--> Puma with pid ${PID} stopped successfully."
rm -f ${PIDFILE} ${STATE_FILE}
else
log_daemon_msg "--> Unable to stop puma with pid ${PID}."
fi
fi
fi
else
log_daemon_msg "--> Puma isn't running in path: ${APP_ROOT}."
fi
return 0
}
# Function that sends a SIGUSR2 to the daemon/service
#
restart_task() {
if [ -e ${PIDFILE} ]; then
log_daemon_msg "--> About to restart puma in path: ${APP_ROOT} ..."
run_pumactl restart
else
log_daemon_msg "--> Your puma was never playing... Let's get it out there first ..."
start_task
fi
return 0
}
# Function that sends a SIGUSR2 to the daemon/service
#
status_task() {
if [ -e ${PIDFILE} ]; then
log_daemon_msg "--> About to status puma ${APP_ROOT} ..."
run_pumactl status
else
log_daemon_msg "---> Puma isn't running in path: ${APP_ROOT}."
fi
return 0
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting ${DESC}" "${NAME} ..."
start_task
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping ${DESC}" "${NAME} ..."
stop_task
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
log_daemon_msg "Status ${DESC}" "${NAME} ..."
status_task
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
restart)
log_daemon_msg "Restarting ${DESC}" "${NAME} ..."
restart_task
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
*)
echo "Usage:" >&2
echo " ${SCRIPT_NAME} {start|stop|status|restart}" >&2
exit 3
;;
esac
:
puma.rb は、
#!/usr/bin/env puma
directory '/app/myarticles_staging/current'
environment 'staging'
pidfile '/app/myarticles_staging/shared/tmp/pids/puma.pid'
state_path '/app/myarticles_staging/shared/tmp/states/puma.state'
stdout_redirect '/app/myarticles_staging/shared/log/puma_access.log', '/app/myarticles_staging/shared/log/puma_error.log', true
daemonize
threads 4, 8
bind 'unix:///app/myarticles_staging/shared/tmp/sockets/puma.myarticles_staging.sock'
activate_control_app 'unix:///app/myarticles_staging/shared/tmp/sockets/pumactl.myarticles_staging.sock'
workers '4'
preload_app!
on_worker_boot do
require "active_record"
ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
ActiveRecord::Base.establish_connection(YAML.load_file('/app/myarticles_staging/shared/config/database.yml')['staging'])
end
# Allow puma to be restarted by the `rails restart` command.
plugin :tmp_restart
ここでは、次のような _stage.rb という名前のファイルからすべての puma 設定を取得しています。
set :stage, :staging
set :branch, :staging
set :server_port, 80
set :full_app_name, "#{fetch(:application)}_#{fetch(:stage)}"
set :rails_env, :staging
set :deploy_to, "/app/#{fetch(:full_app_name)}"
set :puma_user, fetch(:deploy_user)
set :puma_state, "#{shared_path}/tmp/states/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_rackup, -> { File.join(current_path, 'config.ru')}
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.#{fetch(:full_app_name)}.sock"
set :puma_default_control_app, "unix://#{shared_path}/tmp/sockets/pumactl.#{fetch(:full_app_name)}.sock"
set :puma_conf, "#{shared_path}/config/puma.rb"
set :puma_workers, 4
set :puma_threads, [4, 8]
set :puma_role, :app
set :puma_env, :staging
set :puma_preload_app, true
set :puma_enable_socket_service, true
set :puma_access_log, "#{shared_path}/log/puma_access.log"
set :puma_error_log, "#{shared_path}/log/puma_error.log"
set :nginx_access_log, "#{shared_path}/log/nginx_access.log"
set :nginx_error_log, "#{shared_path}/log/nginx_error.log"
/etc/init.d/puma_myarticles_staging start として puma サービスを開始すると、次のように出力されます。
Starting Puma rack web server puma_myarticles_staging ...
--> Woke up puma /app/myarticles_staging/current
[3955] Puma starting in cluster mode...
[3955] * Version 4.3.12 (ruby 2.7.0-p0), codename: Mysterious Traveller
[3955] * Min threads: 4, max threads: 8
[3955] * Environment: staging
[3955] * Process workers: 1
[3955] * Preloading application
[3955] * Listening on unix:///app/myarticles_staging/shared/tmp/sockets/puma.myarticles_staging.sock
[3955] ! WARNING: Detected 1 Thread(s) started in app boot:
[3955] ! #<Thread:0x000055c692868bf8 /app/myarticles_staging/shared/bundle/ruby/2.7.0/gems/activerecord-6.1.7.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:323 sleep> - /app/myarticles_staging/shared/bundle/ruby/2.7.0/gems/activerecord-6.1.7.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:329:in `sleep'
[3955] * Daemonizing...
新しい puma pid や状態ファイルは残されません。最終的に、puma.pid と puma.state を確認したところ、ファイルが書き込まれていなかったので、puma サービス ワーカーは起動しませんでした。 rbenv exec Bundle exec Rails を実行して current_path で手動でテストしたところ、うまくいきました。
ps ax | を使用して悪魔化された puma プロセスを確認しました。 grep puma を実行しましたが、実際の puma ワーカーが見つかりませんでした。
1516 pts/0 S+ 0:00 grep --color=auto puma
私が間違っている可能性があることについて何か提案はありますか?前もって感謝します。
解決策
おそらく問題は、puma の起動方法にあります。ここには Puma のバグはないと確信しています。 systemctl の設定に関する詳細は投稿されていませんが、ベストプラクティスはユーザーサービスファイルを使用することです。そうすることで、puma は常に起動時に開始され、パスワードは必要ありません
私が使用する標準的な puma 構成は次のようになります
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
# Change to match your CPU core count
workers 0
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port ENV.fetch("PORT") { 3000 }
# Specifies the `environment` that Puma will run in.
#
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env
app_dir = File.expand_path("../..", __FILE__)
#shared_dir = "#{app_dir}/shared"
shared_dir = "/home/project/apps/comtech/shared" # Use your projects path
# Specifies the `pidfile` that Puma will use.
#pidfile ENV.fetch("PIDFILE") { "pids/server.pid" }
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app
# Set up socket location
bind "unix://#{shared_dir}/sockets/comtech_puma.sock"
on_worker_boot do
require "active_record"
ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end
# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
明らかに、サーバー上のステージングに ENVIRONMENT 変数を設定する必要があります。
tmp を使用するように pids フォルダーを調整します。
システム サービスではなくユーザー サービスを使用するには、次を参照してください。
cd ~/.config/systemd/user フォルダーが存在しない場合はフォルダーを作成します
nano name_of_your_puma.service を入力し、パスを適宜調整して次の内容を貼り付けます。
[Unit]
Description=Puma Rack Server
# Puma supports systemd's `Type=notify` and watchdog service
# monitoring, if the [sd_notify](https://github.com/agis/ruby-sdnotify) gem is installed,
# as of Puma 5.1 or later.
# On earlier versions of Puma or JRuby, change this to `Type=simple` and remove
# the `WatchdogSec` line.
[Service]
Type=simple
# If your Puma process locks up, systemd's watchdog will restart it within seconds.
#WatchdogSec=10
RestartSec=10
WorkingDirectory=/home/comtech/apps/comtech/current
#PIDFile=/home/comtech/apps/comtech/shared/pids/puma.pid
#User=comtech
ExecStart=/home/comtech/.rvm/bin/rvm 3.1.3@comtech_cms_app do bundle exec puma -C /home/comtech/apps/comtech/current/config/puma_production.rb
ExecStop=/home/comtech/.rvm/bin/rvm 3.1.3@comtech_cms_app do bundle exec pumactl -S /home/comtech/apps/comtech/shared/pids/puma.state stop
ExecReload=/home/comtech/.rvm/bin/rvm [email protected]@comtech_cms_app do bundle exec pumactl -S /home/comtech/apps/comtech/shared/pids/puma.state restart
Restart=always
[Install]
WantedBy=multi-user.target
それから実行します
$ systemctl --user enable name_of_your_puma.service
サービスの確認へ
$ systemctl --user status
サービスを停止するには
$ systemctl --user stop
サービスを開始するには
$ systemctl --user start
サービスは起動時に自動的に開始され、puma が失敗した場合は puma を再起動します。 また、RVM を使用しているため、それに応じて起動コマンドを調整する必要があることにも注意してください。