Supervisorにおける死活監視通知とEvent Listenerプロセスを再起動したい


結論だけ書くと新しい名前で追加して、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のスクリプトを書ける。だいたいこんな感じ。

hello_listener.py
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を書き換えて別の名前にする。

/etc/supervisor/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

めでたし。