
私はDockerベースのクラスタサブミッションシステムを持っており、ローカル実行もサポートできるようにしようとしています。ローカルで実行する場合、ジョブを開始するコマンドは基本的に
docker run /results/src/launcher/local.sh
クラスター実行では、代わりに別のスクリプトが実行されます。私が直面している難しさは、CTRL-C を正しくサポートしながら、ローカル ユーザーとしてコードを実行する方法です。docker run はエントリポイントを uid 0 として開始するため、ユーザーのエントリポイントを で実行する必要がありますsu -c
。基本的に、スクリプトは次の 2 つを実行する必要があります。
- 事前実行スクリプト(ルートとして呼び出される)
- Python プログラム (呼び出しユーザーとして呼び出される)
スクリプトの要点は現在次のとおりです。
# Run prerun script
$PRERUN &
PRERUN_PID=$!
wait $PRERUN_PID
PRERUN_FINISHED=true
status=$?
if [ "$status" -eq "0" ]; then
echo "Prerun finished successfully."
else
echo "Prerun failed with code: $status"
exit $status
fi
# Run main program dropping root privileges.
su -c '/opt/conda/bin/python /results/src/launcher/entrypoint.py \
> >(tee -a /results/stdout.txt) 2> >(tee -a /results/stderr.txt >&2)' \
$USER &
PYTHON_PID=$!
wait $PYTHON_PID
PYTHON_FINISHED=true
status=$?
if [ "$status" -eq "0" ]; then
echo "Entrypoint finished successfully."
else
echo "Entrypoint failed with code: $status"
exit $status
fi
シグナル伝播は同じスクリプト内で次のように処理されます。
_int() {
echo "Caught SIGINT signal!"
if [ "$PRERUN_PID" -ne "0" ] && [ "$PRERUN_FINISHED" = "false" ]; then
echo "Sending SIGINT to prerun script!"
kill -INT $PRERUN_PID
PRERUN_PID=0
fi
if [ "$PYTHON_PID" -ne "0" ] && [ "$PYTHON_FINISHED" = "false" ]; then
echo "Sending SIGINT to Python entrypoint!"
kill -INT $PYTHON_PID
PYTHON_PID=0
fi
}
PRERUN_PID=0
PYTHON_PID=0
PRERUN_FINISHED=false
PYTHON_FINISHED=false
trap _int SIGINT
にシグナル ハンドラがあります/results/src/launcher/entrypoint.py
。これは によって実行されるコードですsu -c
。しかし、SIGINT を取得できないようです。問題は にあると思われますsu -c
。 で予想されたとおり、PYTHON_PID
bash スクリプトには Python インタープリタの PID ではなく、su
プログラムの PID が割り当てられます。Python エントリポイントで を実行するとos.system("ps xa")
、次のようになります。
PID TTY STAT TIME COMMAND
1 ? Ss 0:00 /bin/bash /results/src/launcher/local.sh user 1000 1000 /results/src/example/compile.sh
61 ? S 0:00 su -c /opt/conda/bin/python /results/src/launcher/entrypoint.py \ > >(tee -a /results/stdout.txt) 2> >(tee -a /results/stderr.txt >&2) user
62 ? Ss 0:00 bash -c /opt/conda/bin/python /results/src/launcher/entrypoint.py \ > >(tee -a /results/stdout.txt) 2> >(tee -a /results/stderr.txt >&2)
66 ? S 0:01 /opt/conda/bin/python /results/src/launcher/entrypoint.py
67 ? S 0:00 bash -c /opt/conda/bin/python /results/src/launcher/entrypoint.py \ > >(tee -a /results/stdout.txt) 2> >(tee -a /results/stderr.txt >&2)
68 ? S 0:00 bash -c /opt/conda/bin/python /results/src/launcher/entrypoint.py \ > >(tee -a /results/stdout.txt) 2> >(tee -a /results/stderr.txt >&2)
69 ? S 0:00 tee -a /results/stdout.txt
70 ? S 0:00 tee -a /results/stderr.txt
82 ? R 0:00 /opt/conda/bin/python /results/src/launcher/entrypoint.py
83 ? S 0:00 /bin/dash -c ps xa
84 ? R 0:00 ps xa
PYTHON_PID
PID 61 が割り当てられます。ただし、Python インタープリターを正常にシャットダウンできるようにしたいので、そこで何らかのシグナルをキャッチできる必要があります。このような状況で SIGINT を Python インタープリターに転送する方法を知っている人はいますか? 私が達成しようとしていることを行うよりスマートな方法はありますか? コードがdocker run
ローカル実行用にスケジュールされているときにコマンドをまとめるコードを完全に制御できます。
答え1
ここではいくつかのことが起こっています。まず、コンテナ内で pid 1 としてシェル スクリプトを実行しています。さまざまなシナリオでそのプロセスが cont+c を認識し、docker stop
シグナルを送信し、それをトラップして処理するのは bash の役割です。デフォルトでは、pid 1 として実行している場合、bash はシグナルを無視します (Linux サーバーでシングル ユーザー モードを処理するためだと思います)。次のようにして、そのシグナルを明示的にトラップして処理する必要があります。
trap 'pkill -P $$; exit 1;' TERM INT
スクリプトの先頭に、SIGTERM と SIGINT (cont+c によって生成される) をキャッチし、子プロセスを強制終了して、すぐに終了します。
次に、コマンドがありますsu
。このコマンドは、シグナル処理を中断する可能性のあるプロセスをフォークします。私は、このコマンドが fork システムコールの代わりに exec を実行し、プロセス リストから自分自身を削除することを好みます。Dockerfileで次のようにgosu
インストールできます。gosu
ARG GOSU_VER=1.10
ARG GOSU_ARCH=amd64
RUN curl -sSL "https://github.com/tianon/gosu/releases/download/${GOSU_VER}/gosu-${GOSU_ARCH}" >/usr/bin/gosu \
&& chmod 755 /usr/bin/gosu \
&& gosu nobody true
最後に、エントリポイントには、フォークしてバックグラウンド プロセスが終了するのを待つロジックが多数あります。これは、プロセスをフォアグラウンドで実行することで簡素化できます。最後に実行するコマンドは、exec
シェルの実行を中断しないように で開始できます。 でエラーをキャッチするset -e
か、 フラグを使用して実行中のコマンドのデバッグを表示するために を展開できます-x
。最終結果は次のようになります。
#!/bin/bash
set -ex
# in case a signal is received during PRERUN
trap 'exit 1;' TERM INT
# Run prerun script
$PRERUN
# Run main program dropping root privileges.
exec gosu "$USER" /opt/conda/bin/python /results/src/launcher/entrypoint.py \
> >(tee -a /results/stdout.txt) 2> >(tee -a /results/stderr.txt >&2)
ログを削除できれば、スクリプトの先頭で から に切り替えて、 だけを使用してコンテナ/results
からの結果を確認できるようになります。/bin/bash
/bin/sh
docker logs