
我有一個基於 docker 的叢集提交系統,我試圖讓它也支援本地執行。在本地執行時,啟動作業的命令基本上是
docker run /results/src/launcher/local.sh
對於叢集執行,正在運行另一個腳本。我面臨的困難是如何以本地用戶身份運行程式碼,同時仍然正確支援 CTRL-C。由於 docker run 將入口點啟動為 uid 0,因此我需要使用su -c
.基本上,該腳本需要運行兩件事:
- 預運行腳本(以 root 身分呼叫)
- 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。如果我os.system("ps xa")
在 Python 入口點中執行 a,我會看到以下內容:
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。有誰知道在這種情況下如何將 SIGINT 轉送到 Python 解釋器?有沒有更明智的方法來完成我想要完成的任務?docker run
當程式碼安排在本地執行時,我可以完全控制將命令組合在一起的程式碼。
答案1
這裡發生了一些事情。首先,您在容器內以 pid 1 身分執行 shell 腳本。在各種場景中,該進程都會看到 cont+c,或docker stop
發送訊號,並且由 bash 來捕獲和處理它。預設情況下,當以 pid 1 運行時,bash 將忽略該訊號(我相信可以在 Linux 伺服器上處理單一使用者模式)。您需要使用以下命令明確擷取並處理該訊號:
trap 'pkill -P $$; exit 1;' TERM INT
在腳本的頂部。這將捕獲 SIGTERM 和 SIGINT(由 cont+c 產生)、終止子進程並立即退出。
接下來是su
命令,它本身分叉了一個可以中斷訊號處理的程序。我更喜歡gosu
運行 exec 而不是 fork 系統調用,將其自身從進程列表中刪除。您可以gosu
在 Dockerfile 中使用以下內容進行安裝:
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
以避免讓 shell 繼續運作。您可以使用 捕獲錯誤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