結論だけ書くと新しい名前で追加して、rereadしてupdateすればよい。
Supervisor
Supervisorという便利プロセス管理ツールがある。プロセス監視以外に、STDOUTとSTDERRをログにつないでくれたり、複数プロセス起動してくれたりして便利だ。Supervisor 3.0からEvent Listenerという機能が追加されて、これを使うとSupervisor上で発生したイベントにフックしていろんな処理ができるようになった。
EventListener
EventListenerはSupervisorが管理する他のプロセスと同様に、Supervisorの子プロセスとして起動され、標準入力/出力を経由してSupervisorと通信する。Supervisorからはイベントとその情報が渡されてくるので、それを元にメールを送信したり、外部の通知用HTTP APIを叩いたりできる。図にするとこんな感じ。
取得できるイベントには次の種類がある。
Supervisor側はプログラムの時とほぼ同じで、どのイベントを送るかを指定すれば良い。
[eventlistener:hello]
command=hello_listener.py
events=PROCESS_STATE_EXITED
あとはrereadしてupdateすれば反映される。
プロトコル
イベントはヘッダとペイロードからできている。ヘッダとペイロードは改行で区切られており、ヘッダにはイベント共通の属性が付与されている。ペイロードはさらにヘッダ行を持っていて、例えば、PROCESS_STATE_EXITED
イベントの場合次のようなかんじだ。
ver:3.0 server:supervisor serial:1 pool:listener poolserial:1 eventname:PROCESS_STATE_EXITED len:74
processname:hello groupname:hello from_state:RUNNING expected:0 pid:8888
Supervisorにはchildutils
というモジュールが付属していて、これを使うと簡単にEventListenerのスクリプトを書ける。だいたいこんな感じ。
import sys
from supervisor import childutils
def main():
while 1:
header, payload = childutils.listener.wait()
payload_header, data = childutils.eventdata(payload)
# debug print
sys.stderr.write("event: %s\n".format(header['eventname']))
sys.stderr.write("processname: %s\n".format(payload_header['processname']))
sys.stderr.write("expected: %d".format(payload_header['expected'])
sys.stderr.flush()
if __name__ == '__main__':
main()
まあこんな感じで便利に使える。で、PagerDutyに通知したりするわけだけど、Event Listenerのスクリプトを修正したとき、Event Listenerだけ再起動したくなる。
restartコマンドで再起動するとイベントが送られてこなくなる
ということで、supervisorctl
からリスタートを叩く。
$ sudo supervisorctl status
hello_listener RUNNING pid 8888, uptime 3 days, 00:00:00
program RUNNING pid 8889, uptime 3 days, 00:00:00
$ sudo supervisorctl restart hello
$ sudo supervisorctl status
hello_listener RUNNING pid 8888, uptime 00:00:10
program RUNNING pid 8889, uptime 3 days, 00:00:00
一見これでよさそうだが、実際にはイベントが受け取れなくなってしまう。
rereadしてupdateする
で、どうするか考えた訳だけど、rereadして追加することはできたので、rereadしてupdateすることで別のEvent Listenerとして登録することにした。
supervisord.confを書き換えて別の名前にする。
...
[include]
files = /etc/supervisor/conf.d/*.conf
-[eventlistener:hello_listener]
+[eventlistener:hello_listener2]
command=hello_listener.py
events=PROCESS_STATE_EXITED
で、rereadしてupdateする。
$ sudo supervisorctl reread
hello_listener: disappeared
hello_listener2: available
$ sudo supervisorctl update
hello_listener: stopped
hello_listener: removed process group
hello_listener2: added process group
$ sudo supervisorctl status
program RUNNING pid 8889, uptime 3 days, 00:00:00
hello_listener2 RUNNING pid 8888, uptime 00:00:10
めでたし。