OGS/GEのコマンドでDockerコンテナジョブを停止、サスペンド/再開する

GridEngineでDockerコンテナをジョブ投入することができます。ただしqdelでジョブを削除してもDockerコンテナは我知らずと動き続けてしまいます。ジョブと一緒にコンテナを停止するにはどうしたら良いか。困ったなと彷徨っているとコンテナ内のPID1でシグナルハンドルする[run_wrapper.py](https://github.com/gifford-lab/docker_signal_wrapper)なるスクリプトを発見しました。これで一件落着、としても良いのですが、このラッパースクリプトをコンテナに含めるためにDockerイメージをいちいちビルドするのは面倒です。

ビルドしなくて良い方法を考えてみました。

次のシェル関数でDockerコマンドをラップするとGridEngineのコマンドでコンテナジョブを停止、サスペンド、アンサスペンドできます。

```bash:/etc/profile.d/docker.sh
#
# docker run wrapper for OGS/GE
#
# Copyright (c) 2016 Akihiro Matsushima
# Released under the MIT license
# http://opensource.org/licenses/mit-license.php
#

function sigconthandler() {
    docker unpause $cid
    echo "caught sigcont, container unpaused."
    wait
}
function sigusr1handler() {
    docker pause $cid
    echo "caught sigusr1, container paused."
    wait
}
function sigusr2handler() {
    if [ `docker inspect --format="{{ .State.Status }}" $cid` == "paused" ]; then
        docker unpause $cid
    fi
    docker stop $cid
    echo "caught sigusr2, container stopped."
}

function docker() {
    # emulate fairly POSIX sh in zsh
    $(type "emulate" >/dev/null 2>&1) && emulate -L sh

    local IFS=$' \t\n'

    if [ "$1" = "run" ]; then
        local DOCKER_RUN_LOCALOPTS=${DOCKER_RUN_OPTS:-'-u `id -u`:`id -g` -v $PWD:$PWD -w $PWD'}
        if [ -n "$JOB_ID" ]; then
            # define the unique cidfile name
            TEMPDIR=/var/tmp/${LOGNAME:-$USER}
            CIDFILE="${TEMPDIR}/${JOB_NAME:-SOMEJOB}.o${JOB_ID}.${SGE_TASK_ID:-SOMETASK}_$(date +%Y%m%d%H%M%S%3N).cid"
            if [ ! -e "$TEMPDIR" ]; then
                mkdir "$TEMPDIR"
            fi

            /usr/bin/docker run $(eval echo $DOCKER_RUN_LOCALOPTS) -i --cidfile="$CIDFILE" "${@:2:($#-1)}" &
            pid=$!
            while [[ -d /proc/$pid && -z $cid ]]; do
                sleep 1
                if [ -s "$CIDFILE" ]; then
                    read -r cid < "$CIDFILE"
                    rm -f "$CIDFILE"
                fi
            done

            trap sigconthandler SIGCONT
            trap sigusr1handler SIGUSR1
            trap sigusr2handler SIGUSR2
            wait
        else
            /usr/bin/docker run $(eval echo $DOCKER_RUN_LOCALOPTS) "${@:2:($#-1)}"
        fi
    else
        /usr/bin/docker "$@"
    fi
}
if [[ ! $(readlink /proc/$$/exe) =~ "zsh" ]]; then
    export -f sigconthandler sigusr1handler sigusr2handler docker
fi
```
ただし、このシェル関数だけではジョブは止まりません。qsubオプションに`-notify`を指定する必要があります。qsubスクリプトでは次の一行を追記してください。

```bash
#$ -notify
```
`-notify`オプションについては`man qsub`に次のように記載されています。

```
       -notify
              Available for qsub, qrsh (with command) and qalter only.

              This flag, when set causes Grid Engine to send "warning" signals to a  running  job
              prior  to  sending  the  signals  themselves. If a SIGSTOP is pending, the job will
              receive a SIGUSR1 several seconds before the SIGSTOP. If a SIGKILL is pending,  the
              job  will  receive  a SIGUSR2 several seconds before the SIGKILL.  This option pro‐
              vides the running job, before receiving the SIGSTOP or SIGKILL, a  configured  time
              interval  to do e.g. cleanup operations.  The amount of time delay is controlled by
              the notify parameter in each queue configuration (see queue_conf(5)).

              Note that the Linux operating system "misused" the user signals SIGUSR1 and SIGUSR2
              in  some  early Posix thread implementations. You might not want to use the -notify
              option if you are running multi-threaded applications in  your  jobs  under  Linux,
              particularly on 2.0 or earlier kernels.

              Qalter allows changing this option even while the job executes.

              Only  if  this  option  is used the parameter named notify with the value y will be
              passed to defined JSV instances.  (see -jsv option above or find  more  information
              concerning JSV in jsv(1))
```
要は「`-notify`指定したときはジョブに停止信号を送る前に別の信号を送るから、その信号を頼りに止まらないプログラムをきちんと止めてね」です。

シェル関数内の`DOCKER_RUN_LOCALOPTS`環境変数で指定するデフォルトの`docker run`オプションはお好みで書き換えてください。`docker run`のオプションには後ろに書いたオプションが前に書いたオプションを打ち消せるものがあるので、それも念頭にデフォルトを決めるといいと思います。上記シェル関数を例にすると`docker run --rm=false -u 0:0 -w /otherdir`といった具合です。`-v`は何個でも書けるので打ち消せないですね。他にも状況によっては都合の悪いオプションはあると思うので`DOCKER_RUN_OPTS`環境変数を定義しておくとそちらを優先するようにしています。

## 追伸
シェル関数ではコンテナIDを取得するためにCIDファイルを生成してそのファイルからコンテナIDを読み込んでいます。CIDファイルを生成せずとも`docker run --detach`すればコンテナIDが返り値となるのに、それを利用せず何ゆえ周りくどい方法を取っているのか。それは`docker run`は`--detach`オプションと`--rm`オプションを同時に指定するが出来ないからです。ユーザが指定する`docker run`オプションに`--rm`が含まれている場合も考慮して`--detach`オプションを使わずにコンテナIDを取得しています。