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