はじめに
Paper & Pine は、キューに入ったイベントのプログラムを実際の PDF に変換します。制作を一時停止する前に、現在の 3 ページの配布資料を完成させ、次のジョブを保留のままにし、完成済みの朝の PDF を維持する必要があります。提供された Python ワーカーは停止要求後に現在のジョブを終える方法を知っていますが、現在は起動用シェルが要求の到達を妨げています。
ターミナルのパス、基本的な Docker、コンテナーログを知っていることを前提とします。小さな Compose ファイルとシェルの挙動は、使用前に説明します。Python のプログラミングも実物のプリンターも不要です。成果物はダウンロードでき、デコード可能な PDF です。
Workstation と Print Desk を使います。練習用と配布用のキューは独立しているため、配布の進捗を失わずに 1 つの制御された失敗を調べられます。学習モードは最初のページを描画した後、停止要求が来るまで一時停止し、その後のページ間には明示された 0.5 秒の待機を入れます。これは判断を見えるようにする待機であり、本番速度のベンチマークではありません。読む時間とよくある間違いへの対処を含め、約 45 分を見込んでください。作者による見積もりです。
停止要求をワーカーに届ける
このステップでは練習用の停止失敗を観察し、起動用シェルを修正して、停止要求を受け取る元のワーカーで実際の配布を完成させます。
実際の完成済みプログラムから始める
Print Desk から始めます。Delivery attempt の Morning welcome handout の Preview text を開き、Download PDF を選びます。プレビューはその保存済みの 2 ページ PDF からデコードされ、元テキストから再構築されるものではありません。Back to Print Desk で戻ります。現在の Community open day handout は保留中です。Volunteer notes はこの受け渡し後も保留である必要があります。
desk は練習と配布を分けており、最初に利用できるのは以前の配布資料だけです。

Practice attempt 列は学習用キューの別コピーであり、2 台目のマシンではありません。各列に独自の出力とワーカー状態があります。現在の PDF は文書全体の最終処理後にだけダウンロード可能になります。内部でページ 1 を描画しても、まだ完全な PDF ファイルにはなりません。
練習の停止が失敗する理由を観察する
Workstation に切り替え、/home/labex/project/print-signal から始めます。cd はそこに戻り、cat はファイルを読みます。
cd /home/labex/project/print-signal
cat compose.trial.yaml
Compose は services.worker の下に設定をまとめます。準備済みイメージにはワーカーと PDF ライブラリがあります。user はすでに実際の数値アカウント ID に一致し、出力を編集できるようにしています。この値は置き換えないでください。読み取り専用の /input は元のジョブを持ち、/work はこの試行の書き込み可能な状態/出力、/entry.sh は読み取り専用の起動スクリプトです。:ro は読み取り専用を意味します。entrypoint: ["/bin/sh", "/entry.sh"] はシェルをコンテナーのプロセス 1、PID 1 として直接起動します。コンテナーは VM のカーネルを共有しますが、独自のプロセス表示を持ちます。
stop_grace_period: 10s はアプリケーションに有限の 10 秒の終了猶予を与えます。Docker はまず通常の終了要求である SIGTERM を送ります。猶予後には SIGKILL を送れます。これはプロセスを強制終了し、処理して仕事を終えることはできません。PID 1 にシグナルを送っても、自動的にすべての子へ転送されるわけではありません。
既存の練習用 entry は & で Python をバックグラウンドの子として起動し、wait で待ちます。シェルが PID 1 のままです。提供スクリプトを読みます。
cat trial-entry.sh
シェルには転送ハンドラーがありません。コンテナーの PID 1 は特別なデフォルトのシグナル挙動を持つため、このシェルが生きたまま、Python が TERM 要求を受け取らない場合があります。
練習用プロジェクトだけを起動します。-f はファイルを選び、-p は資源をまとめ、up -d はバックグラウンドで起動します。末尾のオプションは pull や build なしで準備済みイメージを使います。
docker compose -f compose.trial.yaml -p print-trial up -d --pull never --no-build
Print Desk に切り替えて再読み込みし、Practice attempt に Training pause after page 1; waiting for stop request と出るまで待ちます。ワーカーは実際にページ 1 を描画し、意図的に待っています。狭いタイミングに合わせる必要はありません。
Workstation に切り替えます。docker exec は既存コンテナーでコマンドを実行します。Linux は /proc にプロセス情報を公開し、このコンテナー内の /proc/1/comm は PID 1 のコマンド名を示します。この読み取り専用の観察では sh と出るはずです。
docker exec print-trial cat /proc/1/comm
stop は構成された猶予でこのサービスに停止を要求します。
docker compose -f compose.trial.yaml -p print-trial stop worker
ワーカーが強制終了されても、stop コマンド自体は 0 で終了し得ます。Docker が停止操作を処理したという意味で、プログラムの完成を意味しません。docker inspect は保持されたコンテナーを読みます。シングルクォートのテンプレートはワーカーの終了コードを選びます。
docker inspect print-trial --format '{{.State.ExitCode}}'
docker logs print-trial
終了コード 137 で、ワーカーの stop-received や completed イベントがない想定です。この制御された練習では猶予切れで Docker が強制停止しましたが、137 だけがすべてのコンテナー失敗の一般的な診断になるわけではありません。ログは 1 行につき 1 つの JSON イベントです。event は観察名、job はプログラムの識別、page は実際の描画進捗です。
Print Desk に切り替えて再読み込みします。練習には現在の完成済み PDF がなく、以前の資料と未変更の配布キューは引き続き利用できます。
シェルをワーカーに置き換える
Workstation に切り替えます。配布用の entry.sh だけを編集し、trial-entry.sh、プログラム入力、既存 PDF、その他の設定は保ちます。シェルの exec は現在のシェルプロセスを指定プログラムに置き換えます。そのため Python が PID 1 となり、提供された TERM ハンドラーが Docker の要求を受け取ります。
このリテラルのヒアドキュメントは短いスクリプト全体を書きます。> は entry.sh を置き換え、引用した SH マーカーは終了行までのシェル展開を防ぎます。設定された /bin/sh が読むため、実行ビットは不要です。
cat > entry.sh <<'SH'
#!/bin/sh
exec python3 -B /app/worker.py
SH
cat entry.sh
独立した配布用プロジェクトを起動します。その 10 秒の猶予はこの 3 ページ資料にすでに十分です。
docker compose -p print-delivery up -d --pull never --no-build
Print Desk に切り替えて再読み込みし、Delivery attempt の学習用一時停止メッセージを待ちます。Workstation に戻って PID 1 を再び読みます。
docker exec print-delivery cat /proc/1/comm
待機するシェルではなく python3 の想定です。通常の停止を要求します。
docker compose -p print-delivery stop worker
docker inspect print-delivery --format '{{.State.ExitCode}}'
docker logs print-delivery
終了コード 0 の想定です。同じワーカーが stop-received、残りの page-completed イベント、現在のプログラムの completed、worker-stopped を記録します。後から新しいプロセスが不足 PDF を埋めても、このワーカーがジョブを完了して停止した証明にはなりません。
配布ファイルと維持されたキューを調べる
Print Desk に切り替え、再読み込みして Delivery attempt にある現在の資料の Preview text を開きます。デコードされた全 3 ページ、セクション見出し、プログラム ID、ページ番号があるはずです。実際の PDF をダウンロードして開きます。desk に戻ります。Morning welcome handout は元の 2 ページを維持し、Volunteer notes は PDF が生成されず保留のままである必要があります。ネイティブな検査証拠のため、正常停止した print-delivery コンテナーを保持します。
名前を打ち間違えたら、エラーを読み、指定のコンテナー名を使います。たとえば docker logs print-workr ではこの配布用コンテナーが見つからず、表示した docker logs print-delivery なら読めます。代用 PDF を手作業で作らないでください。チェックは稼働環境の停止事実を読み、全ページをデコードした後、隔離したプログラム fixture で保存した entry を再実行します。実際の進捗は変更しません。
失敗した配布から回復する
配布の試行が失敗したら、ログと終了ステータスを読むまで停止コンテナーを保持します。entry または猶予の構成だけを修正します。提供の回復操作は実行中または成功済みのコンテナーを拒否します。失敗して停止したコース用ワーカーに対してだけ、現在のジョブを再びキューに入れ、失敗した成果物を delivery/attempt-history/ に保ちます。以前の PDF、元の入力、次の保留ジョブは維持します。
python3 /opt/labex/dk05/source/retry.py
この明示的な回復後に docker compose -p print-delivery down で失敗した配布プロジェクトのコンテナーだけを削除し、表示した配布起動、一時停止の待機、停止の手順を繰り返します。元の PDF は再生成されず、この回復がタブや採点チェックから実行されることもありません。失敗を隠すために VM をリセットしたりキュー JSON を変更したりしないでください。
まとめ
待機する PID 1 のシェルが子プロセスに停止要求を届かなくする様子を確認し、exec で提供ワーカーが TERM を直接受け取るようにしました。元の配布ワーカーは 3 ページ PDF を完成して正常終了し、朝の文書を維持し、次のジョブを保留のままにしました。



