開発中にプログラムが固まった時に kill プロセスID
って打ったことがある方は多いかと思います
僕もそうで、 kill コマンドはプロセスを強制終了するためのコマンドだと思っていました
ですが puma のログローテートを設定している時に kill -HUP
って出てきてなんだこれ?ってなりました
あれ?ログファイルを握り直すのに kill
使うの??プロセスを止めたいなんて思ってないよ???みたいな
なのでちゃんと調べてみました
TL;DR
- kill コマンドはプロセスに対して「シグナル」を送信するためのコマンド
- シグナルはプロセスがプロセス外でのイベントに対して対応するための機構
- シグナルには定義があってそれごとにデフォルト動作がある
- シグナルの動作は上書きすることも可能
今回のシチュエーション
logrotate で puma のログファイルをローテーションしたい、という状況でした
なので、いろいろとググって下記の設定をしました
lastaction
puma_pid=/tmp/pids/puma.pid
test -s $puma_pid && /usr/bin/kill -HUP "$(cat $puma_pid)"
endscript
これは logrotate がログファイルを切り替えた後に行う処理です
これをやらないと puma がログを吐き出すことができなくなってしまいます
今回のキモは kill -HUP
ですね
puma を止めたいわけじゃない(ログを切り替えたい)のに kill
してます
しかも謎の -HUP
指定もしてます
どういうことよ
kill コマンドの機能について
kill コマンドはプロセスを強制終了するためのコマンドだと思っていましたが、違う雰囲気を感じます
なのでちゃんと kill コマンドの man を見ました
kill コマンドは、指定したシグナルを指定したプロセスまたはプロセスグループへ送る。シグナルが指定されない場合、TERMシグナルを送る。
https://linuxjm.osdn.jp/html/util-linux/man1/kill.1.html
プロセスの強制終了なんて一言も書いてません
~~ なんて紛らわしいコマンド名でしょうか ~~
kill コマンドはシグナルをプロセスに送るコマンドです
「シグナル」と「プロセス」というキーワードが2つ出てきていますが
プロセスはあるプログラムを指すと理解しておきます
シグナルってなんぞや
シグナルとは
なんらかのイベントが起こったことをプロセスに通知するための機構です
イベントを受け取ったプロセスは、イベントの種類によってなんらかの処理をします
受け取る側のプロセスは自身の実行とは別で受け取ることができます
イメージは Javascript の addEventListener
が近いと思います
なんらかのイベントが発生したらそれに対応する関数が実行されます
例えば下の場合です
var select = document.querySelector('select') // L1
select.addEventListener('click', function() { // L2
console.log('clicked!') // L3
})
select.addEventListener('change', function() { // L5
console.log('changed!') // L6
})
なんらかのイベントが起こったこと (L2) をプロセス (L1) に通知するための機構です
イベントを受け取ったプロセスは、イベントの種類 (L2, L5) によってなんらかの処理 ( L3, L6 ) をします
シグナルが登場する場面
普段の開発でシグナルの送信を意識していない方も多いかと思います
ですが、以外とシグナルを送信している機会は意外とあります
例にあげた kill
もその1つです
オプションに -KILL
や -9
をつけて実行すると対象のプロセスに対して SIGKILL
というシグナルを送信しています
kill
(オプションなし) の場合は SIGTERM
というシグナルを送信しています
また terminal でよく使う Ctrl-c
もプロセスに対してシグナルを送信しています
Ctrl-c
を入力するとプロセスに対して SIGINT
というシグナルが送信されます
同じく Ctrl-z
は SIGTSTP
を送信しています
これは javascript だとブラウザ上で何かのアクションが起きた時(クリックとかスクロールとか)をイメージしてもらうと近いかと思います
その場合イベントハンドラ console.log('clicked!')
とかするところはどうなるのでしょうか
シグナル送信後の挙動
デフォルトの挙動
シグナルを受け取ったプロセスは、そのシグナルの種類によってなにかしらの処理をします
基本的にはシグナルごとに定義されているデフォルトの処理が行われます
一部を紹介するとこうなっています
シグナルの種類 | デフォルトの動作 | 備考 |
---|---|---|
SIGINT | 終了 | キーボードからの割り込み (Interrupt) |
SIGTSTP | 停止 | terminal から入力される時停止 |
SIGKILL | 終了 | kill シグナル |
SIGTERM | 終了 | termination 終了シグナル |
SIGHUP | 終了 | terminal のハングアップや制御しているプロセスの死 |
http://linuxjm.osdn.jp/html/LDP_man-pages/man7/signal.7.html
プログラムを停止したい時に Ctrl-c
を押せばいい、というのはこういうことですね
つまり Ctrl-c
を押すと SIGINT
シグナルがプロセスに送信され、デフォルトの挙動である「終了」処理を行うからです
デフォルト挙動の上書き
SIGINT
を受け取ったプロセスは終了処理を行いますが、これはあくまでもデフォルトで定義された挙動でしかなく別の処理をさせる(キャッチ)こともできます
例えば less
コマンドのの場合、 less
を起動してから Ctrl-c
を押下してもプロセスは終了しません
これはデフォルトの終了という挙動を上書きしているからです
つまり、あるシグナルに対してなにをするのかはそのプログラムの実装による、ということです
逆に、プログラムはシグナルを受け取ることで外部の任意のタイミングで規定の動作を受け付けることができる、とも言えます
javascript でいえば、 form の submit をフックして submit 前にバリデーションを挟む、的な挙動をさせるのに似てますね
var form = document.querySelector('form')
form.addEventListener('submit', function(evt) {
if (do_validations() == false) {
evt.preventDefault();
}
})
補足ですが、シグナルはキャッチする以外にブロックしたり無視することができます
ただし SIGKILL
と SIGSTOP
はキャッチ、ブロック、無視することはできません
kill -HUP
の解読
さて、話を戻して今回のシチュエーション kill -HUP
がなんなのかという件です
具体的には puma のログファイルに対する logrotate の中で、 puma にログファイルを握り直してもらうために kill -HUP {puma のプロセス}
をします
まず kill -HUP
です
kill
コマンドを使って puma に対して SIGHUP
のシグナルを送信しています
SIGHUP
は前述の通り terminal のハングアップや制御しているプロセスの死を表し、デフォルトではプロセスは終了します
ですが実際には puma のプロセスは終了しないので意味不明です
なので落ち着いて puma の wiki を参照しましょう
Puma cluster responds to these signals:
HUP
reopen log files defined in stdout_redirect configuration parameter.
puma は HUP
を受け取るとログファイルをリオープンします
とあります
つまり puma は SIGHUP
をキャッチして、プロセスの終了をする代わりにログファイルを握り直してくれます
ということで、これで理解できましたね
「logrotate の中で puma に対して SIGHUP
シグナルを送信することで、
puma が SIGHUP
シグナルに対して独自に定義した『ログファイルをリオープンする』という動作をさせる」
という設定でした
補足
シグナルの値について
シグナルには名前とともに値が割り振られています
シグナルの種類 | 値 | デフォルトの動作 | 備考 |
---|---|---|---|
SIGHUP | 1 | 終了 | terminal のハングアップや制御しているプロセスの死 |
SIGINT | 2 | 終了 | キーボードからの割り込み (Interrupt) |
SIGKILL | 9 | 終了 | kill シグナル |
SIGTERM | 15 | 終了 | termination 終了シグナル |
kill
コマンドを使ってプロセスを停止させるときに kill -KILL
としたり kill -9
としたりすると思います
kill
コマンドはオプションでシグナル名かシグナル値を受け取ります
なので -KILL
と -9
はどちらも SIGKILL
を送信するので同じ挙動になります
シグナルをキャッチする
C 言語でシグナルをキャッチするためには sigaction
関数を使います
https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigaction.2.html
signal
関数もあるんですが、歴史的背景から非推奨なようです
ruby の場合は Signal.#trap
でキャッチできます
https://docs.ruby-lang.org/ja/latest/method/Signal/m/trap.html
puma だとこんな感じ
https://github.com/puma/puma/search?q=signal+trap&unscoped_q=signal+trap
nginx の logrotate の場合
puma で logrotate をするように nginx でも logrotate していたのでついでに調べてみました
ec2 で nginx をインストールすると logrotate が自動で設定されていました
postrotate
/etc/init.d/nginx reopen_logs
endscript
これだとわかりにくいので公式サイトを参照すると
NGINX will re-open its logs in response to the USR1 signal.
$ kill -USR1 `cat master.nginx.pid`
https://www.nginx.com/resources/wiki/start/topics/examples/logrotation/
もうわかりますね
SIGUSR1
シグナルを送信しています
SIGUSR1
は初出ですが、デフォルトの動作は「終了」でこれは「ユーザー定義シグナル1」というシグナルです
好きに使っていいよ、って感じですかね
nginx でログローテートする場合は puma より素直でに好きに使っていいよシグナルを介してログファイルを握り直してくれるみたいです
unicorn の logrotate の場合
puma と nginx を見てきたのでついでに unicorn も見てみます
公式サイトのシグナルハンドリングの項に書いてありました
USR1 - reopen all logs owned by the master and all workers See Unicorn::Util.reopen_logs for what is considered a log.
https://bogomips.org/unicorn/SIGNALS.html
ということで nginx と同じく USR1
シグナルを送信すれば良さそうですね
まとめ
シグナルと kill
コマンドについて調べてみました
出会う機会は少なかったりなんか難しそうだったのでなんとなくで流してしまっていましたが、ちゃんと調べてみればなんてことはなかったです