Lambdaカクテル

京都在住Webエンジニアの日記です

ECS/DockerでWorkerManagerを使おうとしてハマったメモ(PID=1問題)

PerlにはWorkerManagerという,外部のジョブキューを使ってバッチ処理できる便利なcpanモジュールがあるのですが,これをECS/Dockerで使おうとしてハマったのでメモ。

PID=1問題

普通にDockerでWorkerManagerを使おうとすると,次のようなメッセージが表示されて起動しません。

my dad may be killed. at /cpan/lib/perl5/WorkerManager/TheSchwartz.pm line 73.
2019-09-19T05:51:37 WorkerManager 61420 exited with PID 28977 and exit code: 2
2019-09-19T05:51:37 WorkerManager 61421 started with PID 28978
my dad may be killed. at /cpan/lib/perl5/WorkerManager/TheSchwartz.pm line 73.
2019-09-19T05:51:38 WorkerManager 61421 exited with PID 28978 and exit code: 2
2019-09-19T05:51:38 WorkerManager 61422 started with PID 28979

このmy dad may be killedというのは,ワーカプロセスが,その親プロセスがkillされたことを検知したために,安全のために終了していることを示しています。

この「親プロセスがkillされた」ことを検知するロジックがここでのキモとなります。

孤児(orphan)プロセス

いったん前提知識として孤児プロセスについて説明します。

通常,*nix系のOSではinitプロセスがPID=1としてOSにより起動され,他のプロセスはinitにより起動されるという形をとるため,他のプロセスはすべてinitの子孫となります。

ここで,ある親プロセスが子プロセスを待たずしてkillされたとき,そのままでは子プロセスの親が居なくなってしまうため,自動的に子プロセスの親はinit(PID=1)になります。 このとき,子プロセスのことを孤児プロセスと呼びます。

ワーカの仕組み

ワーカについても触れます。

WorkerManagerのようなバッチ処理のためのプログラムでは,実際に処理するワーカと,それらを指揮するコントローラとにプロセスが分かれているのが一般的です。コントローラプロセスは自分自身をforkし,ワーカに変身させます。 ワーカはコントローラから受け取った仕事を終えると終了します。コントローラの仕事は,ワーカの異常終了や,ワーカの並列実行数を制御することです。

このため, ワーカよりも先にコントローラプロセスが終了してしまうと,正常な状態を保ったままバッチ処理を行うことができなくなります。したがってワーカは,コントローラプロセスが終了したのを検知すると,進んで仕事を中止して終了するように設計されています。

孤児プロセス検知

さて,ワーカはコントローラプロセスが終了したことをどうやって知るのでしょうか。ここで孤児プロセスの概念が登場します。なんらかの事情で「親プロセスのPID」(つまりPPID)が1になっているとき,ワーカは「コントローラプロセスが終了した」と判断できます。 WorkerManagerもこのロジックでコントローラプロセスの終了を検知しています。

このロジックは,「まずPID=1のinitプロセスがあり,他のプロセスはすべてその子である」という前提が成り立つ場合にはうまく動作します。

ECS/Docker

ところが,Docker,またそれを基盤技術としている AWS ECSでは,この前提が成り立ちません。プロセス空間が隔離されるため,DockerのCMDとして記述されたプロセスは突然PID=1として起動されます。

windymelt% docker run -it --rm debian:jessie sh -c 'echo $$'
1

このため,initプロセスとしてPID=1を見込んでおり,孤児プロセスの検知にそれを用いているソフトウェアを起動すると,様々な不都合が生じます。 冒頭のWorkerManagerの誤動作も,これによるものです。

対策

詳しくは参考文献に詳述されています。

  • docker
    • docker run--initオプションをつける
  • ECS
    • タスク定義JSONに以下のように記述する
// "essential"などと並列に
"linuxParameters": {
  "InitProcessEnabled": true
}

参考文献

christina04.hatenablog.com

ngzm.hateblo.jp

Working With Unix Processes (English Edition)

Working With Unix Processes (English Edition)