Lokale Docker-Ausführung und CTRL-C-Signalausbreitung

Lokale Docker-Ausführung und CTRL-C-Signalausbreitung

Ich habe ein Cluster-Submission-System, das auf Docker basiert, und versuche, es so zu gestalten, dass es auch die lokale Ausführung unterstützt. Bei der lokalen Ausführung lautet der Befehl, der den Job startet, im Grunde

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

Für die Clusterausführung wird stattdessen ein anderes Skript ausgeführt. Die Schwierigkeit, mit der ich konfrontiert bin, besteht darin, den Code als lokaler Benutzer auszuführen und gleichzeitig STRG-C korrekt zu unterstützen. Da Docker Run den Einstiegspunkt als UID 0 startet, muss ich den Einstiegspunkt des Benutzers mit ausführen su -c. Im Wesentlichen muss das Skript zwei Dinge ausführen:

  1. Ein Prerun-Skript (aufgerufen als Root)
  2. Ein Python-Programm (aufgerufen als aufrufender Benutzer)

Der Kern des Skripts ist derzeit Folgendes:

# 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

Die Signalausbreitung wird im selben Skript durch Folgendes geregelt:

_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

Ich habe einen Signalhandler in /results/src/launcher/entrypoint.py, das ist der Code, der von ausgeführt wird su -c. Allerdings scheint er nie das SIGINT zu erhalten. Ich vermute, dass das Problem in der liegt su -c. Wie erwartet PYTHON_PIDwird im Bash-Skript nicht die PID des Python-Interpreters zugewiesen, sondern die des suProgramms. Wenn ich os.system("ps xa")in meinem Python-Einstiegspunkt ein mache, sehe ich Folgendes:

  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_PIDwird die PID 61 zugewiesen. Ich möchte jedoch den Python-Interpreter ordnungsgemäß herunterfahren können, sodass ich dort ein Signal empfangen kann. Weiß jemand, wie man in einer solchen Situation ein SIGINT an den Python-Interpreter weiterleitet? Gibt es eine intelligentere Möglichkeit, das zu tun, was ich erreichen möchte? Ich habe die volle Kontrolle über den Code, der den Befehl zusammenstellt, docker runwenn Code für die lokale Ausführung geplant ist.

Antwort1

Hier passieren mehrere Dinge. Zunächst führen Sie ein Shell-Skript als PID 1 im Container aus. Dieser Prozess erkennt in verschiedenen Szenarien das cont+c oder docker stopsendet das Signal, und es liegt an Bash, es abzufangen und zu verarbeiten. Standardmäßig ignoriert Bash das Signal, wenn es als PID 1 ausgeführt wird (ich glaube, um den Einzelbenutzermodus auf einem Linux-Server zu handhaben). Sie müssten dieses Signal explizit abfangen und verarbeiten, beispielsweise mit:

trap 'pkill -P $$; exit 1;' TERM INT

oben im Skript. Dadurch werden SIGTERM und SIGINT (generiert durch cont+c) abgefangen, untergeordnete Prozesse beendet und das Skript sofort beendet.

Als nächstes gibt es den suBefehl, der selbst einen Prozess aufspaltet, der die Signalverarbeitung unterbrechen kann. Ich bevorzuge, gosudass er einen Exec anstelle eines Fork-Systemaufrufs ausführt und sich selbst aus der Prozessliste entfernt. Sie können mit dem Folgenden in einer Docker-Datei installieren 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

Schließlich gibt es im Einstiegspunkt viel Logik, um zu forken und dann auf die Beendigung eines Hintergrundprozesses zu warten. Dies könnte vereinfacht werden, indem die Prozesse im Vordergrund ausgeführt werden. Der letzte von Ihnen ausgeführte Befehl kann mit einem gestartet werden, execum zu vermeiden, dass die Shell weiterläuft. Sie können Fehler mit abfangen set -eoder dies erweitern, um das Debuggen der ausgeführten Befehle mit einem -xFlag anzuzeigen. Das Endergebnis sieht folgendermaßen aus:

#!/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)

Wenn Sie die Protokolle entfernen können , sollten Sie oben im Skript /resultsvon zu wechseln können und sich einfach auf verlassen können , um die Ergebnisse aus dem Container anzuzeigen./bin/bash/bin/shdocker logs

verwandte Informationen