Execução local do docker e propagação do sinal CTRL-C

Execução local do docker e propagação do sinal CTRL-C

Eu tenho um sistema de envio de cluster baseado em docker e estou tentando fazer com que ele também suporte a execução local. Ao executar localmente, o comando que inicia o trabalho é basicamente

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

Para execução do cluster, outro script está sendo executado. A dificuldade que estou enfrentando é como executar o código como usuário local e ainda oferecer suporte a CTRL-C corretamente. Como docker run inicia o ponto de entrada como uid 0, preciso executar o ponto de entrada do usuário com su -c. Basicamente, o script precisa executar duas coisas:

  1. Um script de pré-execução (chamado de root)
  2. Um programa Python (chamado de usuário chamador)

A essência do script é atualmente a seguinte:

# 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

A propagação do sinal é tratada no mesmo script por:

_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

Eu tenho um manipulador de sinal /results/src/launcher/entrypoint.py, que é o código executado por su -c. No entanto, parece nunca obter o SIGINT. Presumo que o problema esteja no su -c. Como esperado PYTHON_PIDno script bash não é atribuído o PID do interpretador python, mas do suprograma. Se eu fizer um os.system("ps xa")no meu ponto de entrada do Python, vejo o seguinte:

  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é atribuído o PID 61. No entanto, eu gostaria de poder desligar normalmente o interpretador python, para poder capturar algum sinal lá. Alguém sabe como encaminhar um SIGINT para o interpretador Python em uma situação como essa? Haveria uma maneira mais inteligente de fazer o que estou tentando realizar? Tenho controle total sobre o código que reúne o docker runcomando quando o código está agendado para execução local.

Responder1

Há algumas coisas acontecendo aqui. Primeiro, você está executando um script de shell como pid 1 dentro do contêiner. Esse processo em vários cenários é o que vê o cont+c, ou docker stopenviando o sinal, e cabe ao bash capturá-lo e tratá-lo. Por padrão, ao executar como pid 1, o bash irá ignorar o sinal (acredito que ele lide com o modo de usuário único em um servidor Linux). Você precisaria capturar e manipular explicitamente esse sinal com algo como:

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

na parte superior do roteiro. Isso capturaria o SIGTERM e o SIGINT (gerados por cont+c), mataria os processos filhos e sairia imediatamente.

Em seguida, há o sucomando, que bifurca um processo que pode interromper o tratamento do sinal. Eu prefiro gosuque execute um exec em vez de um syscall fork, removendo-se da lista de processos. Você pode instalar gosucom o seguinte em um 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

Por último, há muita lógica no ponto de entrada para bifurcar e aguardar a conclusão de um processo em segundo plano. Isso poderia ser simplificado executando os processos em primeiro plano. O último comando executado pode ser iniciado com um execpara evitar deixar o shell em execução. Você pode detectar erros set -eou expandi-los para mostrar a depuração de quais comandos estão sendo executados com um -xsinalizador. O resultado final é semelhante a:

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

Se você conseguir se livrar dos /resultslogs, poderá alternar de /bin/bashpara /bin/shna parte superior do script e apenas contar com docker logspara ver os resultados do contêiner.

informação relacionada