로컬 도커 실행 및 CTRL-C 신호 전파

로컬 도커 실행 및 CTRL-C 신호 전파

나는 docker를 기반으로 한 클러스터 제출 시스템을 가지고 있으며 로컬 실행도 지원하도록 노력하고 있습니다. 로컬로 실행할 때 작업을 시작하는 명령은 기본적으로

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

클러스터 실행을 위해 다른 스크립트가 대신 실행됩니다. 내가 직면한 어려움은 CTRL-C를 올바르게 지원하면서 로컬 사용자로 코드를 실행하는 방법입니다. docker run은 진입점을 uid 0으로 시작하므로 su -c. 기본적으로 스크립트는 다음 두 가지를 실행해야 합니다.

  1. 사전 실행 스크립트(루트라고 함)
  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 진입점에서 다음을 수행하면 다음이 표시됩니다.

  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_PIDPID 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다음으로, 신호 처리를 중단할 수 있는 프로세스를 포크하는 명령이 있습니다 . 나는 gosu포크 syscall 대신 exec를 실행하여 프로세스 목록에서 자신을 제거하는 것을 선호합니다. gosuDockerfile에 다음을 사용하여 설치할 수 있습니다 .

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/shdocker logs

관련 정보