本地 docker 執行和 CTRL-C 訊號傳播

本地 docker 執行和 CTRL-C 訊號傳播

我有一個基於 docker 的叢集提交系統,我試圖讓它也支援本地執行。在本地執行時,啟動作業的命令基本上是

docker run /results/src/launcher/local.sh

對於叢集執行,正在運行另一個腳本。我面臨的困難是如何以本地用戶身份運行程式碼,同時仍然正確支援 CTRL-C。由於 docker run 將入口點啟動為 uid 0,因此我需要使用su -c.基本上,該腳本需要運行兩件事:

  1. 預運行腳本(以 root 身分呼叫)
  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_PIDbash 腳本中沒有分配 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/shdocker logs

相關內容