tail -f /dev/null

If you haven't had any obstacles lately, you're not challenging. be the worst.

zabbix-server container で一部の external-scripts が zombie process 化する

Env

# ECS container host
$ uname -r
4.14.193-113.317.amzn1.x86_64

$ docker version
Client:
 Version:           19.03.6-ce
 API version:       1.40
 Go version:        go1.13.4
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          19.03.6-ce
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.4
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.2
 runc:
  Version:          1.0.0-rc10
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Case

  • Zabbix server contianer において external-scripts 関連の process zombie が init に回収されず process table 上で増加していく.
    • Tasks: 4261 total, 1 running, 160 sleeping, 0 stopped, 4035 zombie
    • 親 process 側の何らかの bug で子 process の pid を回収出来ていない?
  • Process 総数が PID 制限値に達し Container が thread を生成出来ず ECS container host 側で Resource temporarily unavailable の syslog error を出力し external-scripts 関連の監視が止まる.
    • Resource... の log は container が thread を作成できない, もしくは fork 出来ない場合に出力される.
  • Zabbix server container の log では sh: fork: retry: No child processes の log を出力する.
    • pid の max 制限値 (default 32768) に達して子の fork が失敗している?
Failed to execute command "/usr/lib/zabbix/externalscripts/foo": [11] Resource temporarily unavailable
# zombie
$ ps aux | grep defunct | wc -l
861

# thread limit
$ cat /proc/sys/kernel/threads-max
124476

# max process
$ ulimit -u
62238

# max stack size
$ ulimit -s
8192

# max file count
$ ulimit -n
1024

# PID max
$ cat /proc/sys/kernel/pid_max
32768

# container process limits
$ sudo ps aux | grep docker-entrypoint
root     18420  0.0  0.0  11696  2608 ?        Ss   11:51   0:00 /bin/bash /usr/local/xxx/docker-entrypoint.sh
root     23761  0.0  0.0 110532  2228 pts/0    S+   12:12   0:00 grep --color=auto docker-entrypoint

$ sudo cat /proc/18420/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             62238                62238                processes
Max open files            1024                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       62238                62238                signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us

Zombie process が増加していく例.

# top command on container
top - 18:29:11 up  3:38,  0 users,  load average: 1.08, 1.33, 1.49
Tasks: 521 total,   1 running, 167 sleeping,   0 stopped, 286 zombie
Cpu(s):  9.0%us,  1.7%sy,  0.0%ni, 88.5%id,  0.0%wa,  0.0%hi,  0.8%si,  0.0%st
Mem:  16124384k total,  1792144k used, 14332240k free,   122976k buffers
Swap:        0k total,        0k used,        0k free,  1031308k cached
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    1 root      20   0 19688 2536 2212 S  0.0  0.0   0:00.72 su zabbix -s /bin/bash -c /usr/sbin/zabbix_server --foreground -c /etc/zabbix/zabbix_server.conf

PID1 problem と対処方法

  • Unix process は tree 構造で順序付けられ, 最上位 process である init process は system 起動時に kernel によって開始される.
  • init process は ssh, docker 等各種 daemon や Apache, Nginx 等 server の起動, signal の handling, zombie process の削除等を行う.
  • Unix は exit status を収集するため, 親が子の終了を wait するよう設計されており, 終了しているが親によって wait されていない子は zombie となる.
    • 親によって zombie 化した子を回収する為に system call の waitpid() を呼び出す (reaping).
    • Zombie 化した子は親が exit status を回収するまで, 存在する.
  • kernel は zombie process に関する最小限の情報 (PID, exit status, resource 使用状況) を保持し, 親が後で回収できるようにする..
    • 子が終了すると OS は親に SIGCHILD signal を送信し wake up する. それを親が reaping し, 子を回収する.
  • 親が子を残したまま終了すると orphaned (孤児) となる.
    • たとえ直接 init が作成していない子でも, orphaned な process を init process (PID1) が adopt (採用) し親となる.
  • 多くの daemon software は daemon 化された子が init に回収されることを想定して作られている.
  • init に回収されず zombie 化した子は kernel process table の slot 等を消費し, table が溢れるとそれ以上 process を作成することが出来ない状態になる.
  • Docker container の場合, container の子 process が zombie となり, それを init (PID1) が採用 (adopt) したとて container の親 process がそれを認識しない為, zombie は残り続けたままとなる.
    • 本来であれば container process が init process のように振る舞うよう書かれ, signal を handling し, container process 側で zombie を採用 (adopt) して reaping すべきところ, 別の init の回収に期待してしまっている.
    • 子 process 終了時 container に対し SIGTERM 等の signal 送信を送っても, container 側 (PID1) で handling せず無視する為, 子は exit しない.
  • 上記 PID1 problem をどうしたらよいか?
    • application 側で signal を handling する.
    • もしくは PID1 以外で process を起動するようにする.
      • Docker の init option を有効化すると init process を wrap し SIGTERM を行えるようになる.
      • AWS ECS の場合は InitProcessEnabled = True で上記 docker --init option 相当な wrap が可能.

init option で起動した場合, Zabbix server container での実行 command が docker-init となり, zombie が直ぐに回収されることが分かる.

# top command on container
top - 11:50:36 up 20:59,  0 users,  load average: 1.44, 1.51, 1.67
Tasks:  61 total,   3 running,  58 sleeping,   0 stopped,   0 zombie
%Cpu(s): 38.0 us,  3.9 sy,  0.0 ni, 57.2 id,  0.0 wa,  0.0 hi,  0.8 si,  0.0 st
KiB Mem : 16124384 total, 14208136 free,   585040 used,  1331208 buff/cache
KiB Swap:        0 total,        0 free,        0 used. 15155168 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    1 root      20   0    1112      4      0 S   0.0  0.0   0:00.07 /sbin/docker-init -- docker-entrypoint.sh
    7 root      20   0   84536   4364   3824 S   0.0  0.0   0:00.06 su zabbix -s /bin/bash -c /usr/sbin/zabbix_server --foreground -c /etc/zabbix/zabbix_server.conf

# top command on container host
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    1 root      20   0 19688 2536 2212 S  0.0  0.0   0:00.72 /sbin/init