こんにちは、鈴木です。
Ruby on Rails は開発に必要なものが一通り提供されているフルスタックのフレームワークです。一通りのものが提供されているとはいえ、「こんなものが欲しい」「あんなものが欲しい」と思うこともあります。
何度も実行するような処理であれば、独自の Rake タスクとして定義しておくと便利です。
独自の Rake タスクの作成
独自の Rake タスクの作成は非常に簡単に行うことができます。lib/tasks/ 以下に .rake という拡張子のファイルを作成します。例として「Hello」を出力するタスクを定義してみます。lib/tasks/hello.rake を作成し、以下の内容を記述します。
1 2 3 4 |
desc 'Print "Hello".' task :hello do puts 'Hello' end |
作成できたら、「rake hello」で実行することができます。
1 2 |
$ rake hello Hello |
「rake -T」でタスク一覧を確認することができるのですが、その中に自分で定義したタスクも表示されるようになると、なんだか楽しい気持ちになります。
Unicorn を操作する Rake タスクの例
最近アプリケーションサーバの Unicorn を使い始めました。
Unicorn の操作はプロセスにシグナルを送ることが基本となります。例えば、サーバの再起動や設定ファイルの再読み込み、ワーカープロセス数の変更などを行うには、Unicorn のプロセスにシグナルを送る必要があります。
はじめのうちはプロセス ID を調べて、kill コマンドでシグナルを送る、ということを何度も繰り返していましたが、次第に面倒になり、独自の Rake タスクを作成しました。
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# coding: utf-8 namespace :unicorn do # 設定ファイルのパス. UNICORN_CONFIG_FILE_PATH = Rails.root.join('config', 'unicorn.rb') # PID ファイルのパス. UNICORN_PID_FILE_PATH = Rails.root.join('tmp', 'pids', 'unicorn.pid') # # マスタプロセスの PID 及びコマンド文字列を求める. # # ==== 戻り値 # `ps -o pid,command` 形式の文字列. # マスタプロセスが存在しない場合は nil. # def unicorn_master_pid_string pid_file_path = UNICORN_PID_FILE_PATH return nil unless File.exists?(pid_file_path) pid_string = `ps h -o pid,command #{File.read(pid_file_path).to_i}`.strip return nil unless pid_string.present? pid_string end # # マスタプロセスの PID を求める. # # ==== 戻り値 # マスタプロセスの PID. # マスタプロセスが存在しない場合は nil. # def unicorn_master_pid pid_string = unicorn_master_pid_string return nil unless pid_string pid_string.to_i end # # ワーカープロセスの PID 及びコマンド文字列を求める. # # ==== 戻り値 # `ps -o pid,command` 形式の文字列の配列. # マスタプロセスが存在しない場合は nil. # ワーカープロセスが存在しない場合は空の配列. # def unicorn_worker_pid_strings pid = unicorn_master_pid return nil unless pid `ps h -o pid,command --ppid #{pid}`.split(/\n/).map(&:strip) end # # ワーカープロセスの PID を求める. # # ==== 戻り値 # ワーカープロセスの PID の配列. # マスタプロセスが存在しない場合は nil. # ワーカープロセスが存在しない場合は空の配列. # def unicorn_worker_pids pid_strings = unicorn_worker_pid_strings return nil unless pid_strings pid_strings.map(&:to_i) end # # マスタプロセス, 及びワーカープロセスの PID 及びコマンド文字列を求める. # # ==== 戻り値 # `ps -o pid,command` 形式の文字列の配列 (先頭要素がマスタプロセス). # マスタプロセスが存在しない場合は nil. # def unicorn_pid_strings pid_strings = [unicorn_master_pid_string, unicorn_worker_pid_strings].flatten.compact return nil unless pid_strings.present? pid_strings end # # マスタプロセス, 及びワーカープロセスの PID を求める. # # ==== 戻り値 # マスタプロセス, 及びワーカープロセスの PID の配列. # マスタプロセスが存在しない場合は nil. # def unicorn_pids pid_strings = unicorn_pid_strings return nil unless pid_strings pid_strings.map(&:to_i) end # # マスタプロセスにシグナルを送信する. # # ==== 引数 # signal:: シグナル番号, もしくはシグナル名を文字列またはシンボルで指定する. # failed_message:: マスタプロセスが存在しない場合に表示するメッセージ. # # ==== 戻り値 # マスタプロセスの PID. # マスタプロセスが存在しない場合は nil. # def send_signal_to_unicorn_master(signal, failed_message='Not running.') if pid = unicorn_master_pid Process.kill(signal, pid) else puts failed_message if failed_message.present? end end desc 'Show unicorn process-pids.' task(:pids) { puts (unicorn_pid_strings || 'Not running.') } desc 'Start unicorn server.' task(:start) { system("unicorn -c '#{UNICORN_CONFIG_FILE_PATH}' -E #{Rails.env} -D") } desc 'Reloads config file and gracefully restart all workers (Send signal HUP to master process).' task(:graceful) { send_signal_to_unicorn_master(:HUP) } desc 'Quick shutdown, kills all workers immediately (Send signal TERM to master process).' task(:kill) { send_signal_to_unicorn_master(:TERM) } desc 'Graceful shutdown (Send signal QUIT to master process).' task(:stop) { send_signal_to_unicorn_master(:QUIT) } desc 'Reopen all logs owned by the master and all workers (Send signal USR1 to master process).' task(:reopen_logs) { send_signal_to_unicorn_master(:USR1) } desc 'Reexecute the running binary (Send signal USR2 to master process).' task(:restart) { send_signal_to_unicorn_master(:USR2) } desc 'Gracefully stops workers but keep the master running (Send signal WINCH to master process).' task('workers:stop') { send_signal_to_unicorn_master(:WINCH) } desc 'Increment the number of worker processes by one (Send signal TTIN to master process).' task('workers:increment') { send_signal_to_unicorn_master(:TTIN) } desc 'Decrement the number of worker processes by one (Send signal TTOU to master process).' task('workers:decrement') { send_signal_to_unicorn_master(:TTOU) } # # ワーカープロセスの [番号, PID, 名前] を求める. # # ==== 戻り値 # ワーカープロセスの [番号, PID, 名前] を求める. # マスタプロセス, もしくはワーカープロセスが存在しない場合は空の配列. # def worker_number_pid_name_triples number_pid_name_triples = (unicorn_worker_pid_strings || []).flatten.map do |line| pid, name = *line.split(' ').values_at(0, 2) number = name.gsub(/\D/, '') [number.to_i, pid.to_i, name] end number_pid_name_triples.sort end worker_number_pid_name_triples.each do |number, pid, name| desc "Quick shutdown, immediately exit (Send signal TERM to #{name} process)." task("worker#{number}:kill") { Process.kill(:TERM, pid) } desc "Gracefully exit after finishing the current request (Send signal QUIT to #{name} process)." task("worker#{number}:stop") { Process.kill(:QUIT, pid) } desc "Reopen all logs owned by the worker process (Send signal USR1 to #{name} process)." task("worker#{number}:reopen_logs") { Process.kill(:USR1, pid) } desc "Output thread dump (Send signal USR2 to #{name} process)." task("worker#{number}:thread_dump") { Process.kill(:USR2, pid) } end end if defined?(Unicorn) |
何度も行っている面倒な作業があれば、独自の Rake タスクを作ることを検討してみてください。