ゾンビプロセスの大量発生
シェルを使ってとある処理を実行している最中に大量のゾンビプロセスが発生していることに気づきました。
以下はその時のtopコマンドの出力で、S列が「Z」と表示されているものが全てゾンビプロセスです。
これまでゾンビプロセスの発生を全く考えたことが無かったのですが、コンテナ環境の場合だといわゆる「PID 1 問題」の1つとして認識されています。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 2288244 310936 19076 S 0.0 15.3 1:24.64 java
23 root 20 0 18480 2272 1688 S 0.0 0.1 0:00.32 bash
14538 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14542 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14544 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14549 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14554 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14557 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14561 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14565 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14568 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14582 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14587 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14595 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14596 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14599 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14629 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14630 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14636 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
14637 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 bash
~~以下略~~
PID 1 問題とは
Linuxで使用されているプロセスを一意に識別するためのプロセスID(PID)ですが、PID 0 と 1 は特別扱いされます。
コンテナではENTRYPOINTやCMDで指定したプロセスがPID=1に割り当てられ、それらのプロセスがLinuxのシグナルを正しくトラップしない問題が「PID 1 問題」とされます。
ゾンビプロセスの大量発生もこの問題に関連しています。
Wikipediaの「プロセス識別子」のページではPIDについて下記のように説明がされています。
Unix系OSでは、プロセス識別子 0 と 1 は特別なタスクを指している。プロセス識別子 0 は swapper または sched と呼ばれ、ページングを担当している。これは実はカーネルの一部であり、ユーザーモードのプロセスではない。プロセス識別子 1 は init プロセスで、主にシステムの立ち上げとシャットダウンを担当している。
Linuxのシグナルについてはmanページをご参照ください。
ゾンビプロセスについて知る
ゾンビプロセスとは
ゾンビプロセスとは
実行が終了したのにも関わらず、プロセステーブル上にエントリだけが残っているゴミプロセス
のことです。
プロセステーブル上に情報として残っているだけのため、実態として何らかの処理が存在しているわけではありません。
ゾンビプロセスは下記のような特徴を持ちます。
- CPUリソースは使用しない
- メモリリソースは消費しない
- PIDを消費する(PIDの数はOSで上限がある)
- killコマンドで消せない(親プロセスをkillする必要がある)
従って、PIDを消費する以外では基本的に害はありません。
ゾンビプロセスと孤児プロセス
「ゾンビプロセス」と似た意味で使われる用語として「孤児プロセス」があります。
これらは区別されないことも多いですが、下記のような違いがあります。
ゾンビプロセス | 孤児プロセス | |
---|---|---|
実行状態 | 終了済 | 実行中 |
プロセスの実体 | なし | あり |
発生は意図的か | 多くの場合意図的でない | 意図的であることも意図的でないこともある |
ゾンビプロセス発生の仕組み
ゾンビプロセスが生成される過程は以下のようになります。
LinuxのプロセスはPID=1のプロセスを起点としてツリー状になっています。
親であるプロセスが子プロセスの面倒を見なければなりません。
コンテナではないLinux環境であればPID=1のプロセスはinit
プロセスになっています。
下図の例では、bashを起動してそこからcurlプロセスを複数起動しています。
親であるbashプロセスがプロセステーブルにcurlプロセスの情報を登録します。
PPID
は親プロセスのPIDを表しています。
ここでcurlのプロセスが終了する前にbashプロセスが終了するとどうなるでしょうか。
この場合、子プロセスであったcurlは、PID=1であるinitプロセスの子になります。
curlプロセス終了後はinitプロセスが、プロセスの情報をプロセステーブルから削除します。
コンテナではないLinux環境では、PID=1のプロセスはinitであり子プロセスを適切に管理するため、通常ゾンビプロセスが発生することはありません。
(実際には終了直後のプロセスは全てゾンビ状態になるが、親プロセスにより直ちにプロセステーブルから削除されるためゾンビ状態を確認するのは難しい)
これがコンテナ環境では以下のようになります。
PID=1はinitではなく、コンテナで実行するメインのプロセス(例えばnginxやmysqlなど)になります。
これらのプロセスはほとんどの場合、ゾンビ状態となった子プロセスを掃除するという機能を持っていません。
そのためプロセステーブル上に実体のないゾンビプロセスが残り続けることになります。
コンテナ環境におけるゾンビプロセスの検証
ここでは実際にコンテナ環境でゾンビプロセスを発生させ、その対処方法についてみてみます。
ゾンビプロセスが発生(PID=1がinitではない適当なコマンド)
まずコンテナとして一般的によく使われる状態で検証してみます。
PID=1はinitではない別のプロセスで起動します。
ここではCentOSのイメージを使用します。
まずはコンテナを起動します。
$ docker run -itd --name zombie-test centos:7 sleep 3600
コンテナへログインしてプロセスを確認します。
ここではPID=1はsleepコマンドとしていますが、nginxやmysqlなどでも同様です。
$ docker exec -it zombie-test bash
[root@ccdc87dd344c /]# ps -eo pid,ppid,pgid,stat,command
PID PPID PGID STAT COMMAND
1 0 1 Ss+ sleep 3600
6 0 6 Ss bash
19 6 19 R+ ps -eo pid,ppid,pgid,stat,command
ここで、下記コマンドによりゾンビプロセスを発生させます。
コマンド実行後2・3秒後にCtrl+Cでプロセスを中断させるとゾンビプロセスが生成されます。
[root@ccdc87dd344c /]# seq 0 50 | xargs -n 1 -P 5 sleep 1
^C
[root@ccdc87dd344c /]#
再度プロセスを確認してみます。
STATの項目がZとなっているものがゾンビプロセスです。
親プロセスのPIDであるPPIDが1の状態で、ゾンビプロセスが残っていることを確認できます。
[root@ccdc87dd344c /]# ps -eo pid,ppid,pgid,stat,command
PID PPID PGID STAT COMMAND
1 0 1 Ss+ sleep 3600
6 0 6 Ss bash
25 1 20 Z [sleep] <defunct>
26 1 20 Z [sleep] <defunct>
27 1 20 Z [sleep] <defunct>
28 1 20 Z [sleep] <defunct>
29 1 20 Z [sleep] <defunct>
30 6 30 R+ ps -eo pid,ppid,pgid,stat,command
ゾンビプロセスが発生(PID=1がinit)
つづいてPID=1にinitを指定して確認してみます。
$ docker run -itd --name zombie-test centos:7 init
PID=1はinitプロセスになっています。
$ docker exec -it zombie-test bash
[root@db36af7005bf /]# ps -eo pid,ppid,pgid,stat,command
PID PPID PGID STAT COMMAND
1 0 1 Ss+ init
6 0 6 Ss bash
19 6 19 R+ ps -eo pid,ppid,pgid,stat,command
先ほどと同様にゾンビプロセスを発生させます。
[root@db36af7005bf /]# seq 0 50 | xargs -n 1 -P 5 sleep 1
^C
[root@db36af7005bf /]#
ゾンビプロセス発生後に確認すると、先ほど同様ゾンビプロセスは残っています。
ゾンビプロセスはinitプロセスが掃除しますが、Linuxに含まれているinit(/sbin/init)を指定してもゾンビプロセスは掃除してくれません。
そのため別のinitプロセスを使用する必要があります。
[root@db36af7005bf /]# ps -eo pid,ppid,pgid,stat,command
PID PPID PGID STAT COMMAND
1 0 1 Ss+ init
6 0 6 Ss bash
31 1 25 Z [sleep] <defunct>
32 1 25 Z [sleep] <defunct>
33 1 25 Z [sleep] <defunct>
34 1 25 Z [sleep] <defunct>
35 1 25 Z [sleep] <defunct>
36 6 36 R+ ps -eo pid,ppid,pgid,stat,command
ゾンビプロセスを発生させない対策
--initオプションを使ってコンテナを起動
Dockerではinitオプションを使用することでゾンビプロセスの発生を抑止できます。
--initオプションについて公式ドキュメントでは下記のように説明があります。
init プロセスの指定
--init プラグを使うと、コンテナ内で PID 1 として使われるべき init プロセスを指定できます。init プロセスの指定とは、ゾンビプロセスの回収のような処理を作成したコンテナ内で行い、init システムにおける通常の処理を確実に担います。デフォルトの init プロセスには、Docker デーモンプロセスのシステムパス上で、最初に実行可能な docker-init を使います。この docker-init バイナリは、デフォルトのインストールに含まれており、 tiny の支援を受けています。
実際に--init
オプションを使用してコンテナを起動してみます。
$ docker run -itd --init --name zombie-test centos:7
コンテナで実行されているプロセスを確認すると、PID=1のプロセスは/sbin/docker-init
となっています。
こちらが--init
オプションを追加した際にデフォルトで使用されるdocker-initバイナリです。
$ docker exec -it zombie-test bash
[root@a6810cb3b725 /]# ps -eo pid,ppid,pgid,stat,command
PID PPID PGID STAT COMMAND
1 0 1 Ss /sbin/docker-init -- /bin/bash
6 1 6 S+ /bin/bash
15 0 15 Ss bash
28 15 28 R+ ps -eo pid,ppid,pgid,stat,command
それでは実際にゾンビプロセスを発生させてみます。
下記コマンドを実行した2・3秒後にCtrl+Cでプロセスを中断させます。
[root@a6810cb3b725 /]# seq 0 50 | xargs -n 1 -P 5 sleep 1
^C
[root@a6810cb3b725 /]#
プロセスを確認するとゾンビプロセスが発生していないことを確認できます。
これは、PID=1である/sbin/docker-init
のプロセスがゾンビプロセスを掃除したからです。
[root@a6810cb3b725 /]# ps -eo pid,ppid,pgid,stat,command
PID PPID PGID STAT COMMAND
1 0 1 Ss /sbin/docker-init -- /bin/bash
6 1 6 S+ /bin/bash
15 0 15 Ss bash
38 15 38 R+ ps -eo pid,ppid,pgid,stat,command
このように、PID=1で起動するプロセスを適切なものに設定することでゾンビプロセスの発生を防ぐことができます。