地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

tiniがSIGTERM を受け取ったとき最初の子プロセスが終了すると自身のプロセスを終了するならさらにラッパーを書けばいいじゃない

qiita.com

これを見ていて思ったネタ(?)記事です。実用性は全然ない話になります。PID 1だとSIGTERMを無視するというLinuxのPID 1問題を解決したいという話。--initをつけることでENTRYPOINTに指定したコマンドがTiniという軽量 initを間に挟むことでPID 1を回避しつつtiniがシグナルをハンドリングしてくれるというもの。

ここで問題になるのはtini配下に親プロセスが1つあってその下にさらに子プロセスが連なっているみたいなケースでtiniは子プロセス(parent.sh)にSIGTERMを送りparent.shで適切に処理していないとプロセスが終了しそのまま終了処理となります。(1コンテナ複数プロセスで何かしたいみたいなケースって実際どれくらいあるのだろうか)

bash-5.1# ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/docker-init -- parent.sh
    8 root      0:00 {parent.sh} /usr/local/bin/bash /usr/local/bin/parent.sh
   10 root      0:13 {child.sh} /usr/local/bin/bash /usr/local/bin/child.sh 1
   11 root      0:13 {child.sh} /usr/local/bin/bash /usr/local/bin/child.sh 2
   12 root      0:13 {child.sh} /usr/local/bin/bash /usr/local/bin/child.sh 3

ラッパー(親プロセス)のイメージはこんなのです。子プロセス5つを生成してwaitしておくだけでchild.shはなんでもよくてとりあえず無限ループでもしてるイメージです。これだとtiniからSIGTERMが送られてきてもchild.shには届かずにparent.shが終了してしまいコンテナが終了してしまいます。

#!/bin/bash

for i in $(seq 5); do
  child.sh ${i} &
done

wait

じゃあどうすると良いのか。parent.shでSIGTERMをハンドリングして子プロセスへ送信しつつwaitで待つみたいなことをやれればよさそうです。

#!/bin/bash

sig_handle_parent() {
    echo "recv signal: parent"
    pkill -15 child.sh
    wait
}

trap sig_handle_parent 15

for i in $(seq 5); do
  child.sh ${i} &
done

wait

または生成した子プロセスのpidを親プロセスで持っておいてwaitの引数に与えておけばシグナルハンドラでkillできる

for i in $n_procs; do
    ./child.sh[${i}] &
    pids[${i}]=$!
done

for pid in ${pids[*]}; do
    # この辺でkillなりをする
    # pidは再利用されるので厳密にやると長くなりそう
    wait $pid
done