生成・起動・終了と終了コード
このラボは本物のVM上で動きます
この環境はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。
以前はこのラボがPodの中で動いていました。カーネル権限をすべて落とした環境だったため、コンテナを起動するステップが塞がれており、イメージアーカイブを自分で展開してみるという回り道で学んでいました。もう回り道は必要ありません。
知っておくべきことが2つあります。
- 最初の起動に1分ほどかかります。VMが起動してDockerをインストールするためです。Podのラボ(通常40秒)より遅くなります。
- ブラウザープレビューはありません。VMへの接続は、採点用のポート1つしか開いていません。Webサーバーを起動した場合は、VMの中で
curlを使って確認してください。
目標
コンテナの状態遷移を手で作り出し、終了コード143・137・42をそれぞれ意図的に再現して、その意味を体で覚えます。
なぜ重要なのか
運用で最も多く受ける報告は、「コンテナが落ちました」です。しかし、落ち方は少なくとも3種類あり、それぞれ対処がまったく違います。アプリが自分で終了したならコードを見る必要があり、SIGTERMを受けて終了したなら正常なデプロイであり、SIGKILLで落ちたならアプリのログには何も残りません。終了コードは、この3つを区別する最も安価なシグナルです。128を超える値は128 + 신호번호(プレースホルダーはシグナル番号です)というルールさえ知っていれば、143 = 128+15 = SIGTERM、137 = 128+9 = SIGKILLとすぐに読み取れます。
ステップ
/root/dk2を作成し、alpine:3.20でdk-lifeというコンテナを作成だけします。コマンドはsleep 600です。dk-lifeを起動して、running状態にします。alpine:3.20でdk-trapをバックグラウンド実行します。SIGTERMを受けたらcaught-termを出力し、終了コード143で終わるようにします。そのあと、正常に停止させます。alpine:3.20でdk-notrapをバックグラウンド実行し(コマンドはsleep 600)、猶予時間を2秒だけ与えて停止させます。終了コードが137になる必要があります。alpine:3.20でdk-failを実行して終了コード42で終わらせ、その値を/root/dk2/exit42.txtに数字だけで書きます。dk-restartコンテナを、再起動ポリシーon-failure、最大3回で起動します。- コンテナ内でプロセス一覧を取得し、
/root/dk2/pid1.txtに保存します。PIDの列が一緒に見える必要があります。 このラボの環境自体がコンテナです。ここでps -e -o pid,ppid,commを実行すると、PID 1がこのコンテナのメインコマンドであることと、見えるプロセスが片手で数えられるほど少ないことを、同時に確認できます。 /root/dk2/lifecycle.mdに、dk-trap、dk-notrap、dk-failの3つのコンテナの名前と終了コードを、1行に1つずつ書きます。
参考
docker stop -t <초>で猶予時間を調整できます(プレースホルダーは秒数です)。- シェルのシグナルハンドラーは、
trap '명령' TERMの形で登録します(プレースホルダーはコマンドです)。 docker inspect <이름> | jq -r '.[0].State.ExitCode'で終了コードを読み取ります(プレースホルダーはコンテナ名です)。- よくある間違い1: ステップ3でハンドラーを登録する前に
sleepが先に実行されると、シグナルを受け取れません。短いsleepを繰り返すループにしてください。 - よくある間違い2: ステップ7をホストで実行すると、プロセスが数十個出てきます。コンテナ内であれば、片手で数えられる程度のはずです。
実行せずに作成だけする
/root/dk2を作成し、alpine:3.20でdk-lifeというコンテナを作成だけします。コマンドはsleep 600です。
runは作成してすぐに起動します。作成だけを行う別のサブコマンドがあります。この状態では、まだプロセスはありません。
作成済みのコンテナを起動する
dk-lifeを起動して、running状態にします。
作成と起動が分かれている理由を考えてみてください。起動すると、State.Pidに実際のプロセス番号が入ります。
SIGTERMを受けて正常終了する
alpine:3.20でdk-trapをバックグラウンド実行します。SIGTERMを受けたらcaught-termを出力し、終了コード143で終わるようにします。そのあと、正常に停止させます。
シェルには、シグナルハンドラーを登録する組み込みコマンドがあります。ハンドラーの中で目的の終了コードを指定して終了すると、その値がそのままコンテナの終了コードになります。
ハンドラーがないと何が起きるか
alpine:3.20でdk-notrapをバックグラウンド実行し(コマンドはsleep 600)、猶予時間を2秒だけ与えて停止させます。終了コードが137になる必要があります。
ハンドラーを登録していないPID 1は、SIGTERMを無視します。猶予時間を短くすると、待たずに結果を確認できます。
アプリケーションが出した終了コード
alpine:3.20でdk-failを実行して終了コード42で終わらせ、その値を/root/dk2/exit42.txtに数字だけで書きます。
シグナルで死んだ場合と、アプリが自分で終了した場合とでは、終了コードの範囲が違います。128より小さい値は、アプリが直接出した値です。
失敗したときだけ再起動する
dk-restartコンテナを、再起動ポリシーon-failure、最大3回で起動します。
再起動ポリシーは4種類あります。失敗した場合だけ、しかも回数を制限して再起動する値を選んでください。
コンテナ内のプロセス一覧
コンテナ内でプロセス一覧を取得し、/root/dk2/pid1.txtに保存します。PIDの列が一緒に見える必要があります。
このラボの環境自体がコンテナです。ここでps -e -o pid,ppid,commを実行すると、PID 1がこのコンテナのメインコマンドであることと、見えるプロセスが片手で数えられるほど少ないことを、同時に確認できます。
コンテナ内でプロセスを一覧表示すると、リストがとても短く、PID 1が何かがすぐにわかります。ホストで実行してはいけません。
終了コードの表を作る
/root/dk2/lifecycle.mdに、dk-trap、dk-notrap、dk-failの3つのコンテナの名前と終了コードを、1行に1つずつ書きます。
3つのコンテナの終了コードを実際のinspectで確認し、表に書いてください。それぞれの値がなぜその値なのかも併せて書いておくと、あとで役に立ちます。