
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:
- Ein Prerun-Skript (aufgerufen als Root)
- 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_PID
wird im Bash-Skript nicht die PID des Python-Interpreters zugewiesen, sondern die des su
Programms. 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_PID
wird 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 run
wenn 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 stop
sendet 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 su
Befehl, der selbst einen Prozess aufspaltet, der die Signalverarbeitung unterbrechen kann. Ich bevorzuge, gosu
dass 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, exec
um zu vermeiden, dass die Shell weiterläuft. Sie können Fehler mit abfangen set -e
oder dies erweitern, um das Debuggen der ausgeführten Befehle mit einem -x
Flag 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 /results
von zu wechseln können und sich einfach auf verlassen können , um die Ergebnisse aus dem Container anzuzeigen./bin/bash
/bin/sh
docker logs