LoginSignup
29
24

More than 5 years have passed since last update.

docker-stopでコンテナ停止時にスクリプトを実行する

Posted at

はじめに

この投稿は、Dockerのコンテナ停止時に、コンテナ内でスクリプトを実行する方法を試行錯誤しながらまとめたものです。

なぜやるのか

職場で
「コンテナ停止時にデータのバックアップが取りたい」
という要望があったので調べていました。
「Volumeを使ってマウントすればいいじゃん」というツッコミはご遠慮ねがいます…。

先に結論

  • 実行したいスクリプトをシェルのtrap機能で呼び出す
  • CMD命令はJSON形式で書く
  • フォアグラウンドのプロセスはsleepの無限ループにする

環境

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04 LTS"

$ docker version
Client:
 Version:      1.11.2
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   b9f10c9
 Built:        Wed Jun  1 22:00:43 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.11.2
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   b9f10c9
 Built:        Wed Jun  1 22:00:43 2016
 OS/Arch:      linux/amd64

最初にDockerのrun(start)とstopについておさらい

docker-runとDockerfileのCMDの関係

dockerサービスはdocker-runの実行時に指定されたイメージの
CMDに記述されたコマンドをPID:1としてコンテナ内で実行します。
※CMDに書かれた内容を上書きして実行する方法もあります。
詳しくはDockerfile リファレンス — Docker-docs-ja 1.11.0 ドキュメントを参照してください。

docker-stopで何が起こるか

dockerサービスはdocker-stopの実行時に指定されたコンテナで
kill -SIGTERM 1を実行します。
つまり、docker-runで実行されたCMDのプロセスに対してkillを実行することになります。
この時SIGTERMでプロセスが停止できるとExited (0)とステータスが表示されます。

$ docker ps -a
CONTAINER ID  IMAGE  COMMAND       CREATED        STATUS                    PORTS  NAMES
9d7823cc3cbc  hoge   "/hoge.bash"  3 minutes ago  Exited (0) 2 minutes ago         hoge

どうやってdocker-stopでスクリプトを実行するか?

ここからが本題です。
まずコンテナ内で実行したいサービスやコマンドをシェルスクリプトにまとめます。

#!/bin/bash

# すべてデーモンやバックグラウンドのプロセスとして起動する
/path/to/service/fuga-service start

sudo -b -u piyouser /path/to/command/piyo-command

スクリプト内でSIGTERMをtrapします。
参考:

trap_TERM() {
  echo 'SIGTERM ACCEPTED.'
  exit 0
}
trap 'trap_TERM' TERM

さらに、スクリプトの最後にsleepを無限ループするように書きます。

while :
do
  sleep 5
done

Dockerfileでは作ったシェルスクリプトをCMDで実行するように書きます。

FROM ubuntu:16.04
.....
COPY hoge.bash /hoge.bash
CMD ["/hoge.bash"]

こうするとdocker-runで起動したhoge.bashが、
docker-stopによって実行されるkill -SIGTERMを受け取ります。
そうするとtrapがTERM受け取るように指定されているので、
trap内のechoコマンドが実行されます。

この例では標準出力が使われるので、

$ docker logs hoge
SIGTERM ACCEPTED.

と、docker-logsコマンドから確認ができます。

はまったところ

フォアグラウンドのプロセスがsleepではなくtailだった

バックグラウンドで実行するプロセスしかない場合、

tail -f /dev/null

でフォアグラウンドのプロセスを残す例をよく見ますよね。

tailをフォアグラウンドにする問題点

この場合だとtailがSIGTERMで停止できず、
1. docker-stopのタイムアウト(デフォルト10秒)まで待つ
2. kill -SIGKILLが実行される
となります。
この場合trapに掛からないので想定した動きになりません。

DockerfileのCMDをシェル形式で書いていた

CMD命令は複数の書き方があります。最初はこう書いていました。

CMD "/hoge.bash"

シェル形式の問題点

シェル形式で書いた上の例の場合は

/bin/sh -c '/hoge.bash'

と展開された後で実行されます。
そうするとDockerコンテナ内では下のようになります。
PID:1のプロセスに対してSIGTERMを実行しても、/bin/shにはtrapを設定していないので、何も実行されず終わってしまいます。
※実際にはPID:5のプロセスが残るので、タイムアウト待ちになります。

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 05:42 ?        00:00:00 /bin/sh -c "/hoge.bash"
root         5     1  0 05:42 ?        00:00:00 bash /hoge.bash
root        17     5  0 05:42 ?        00:00:00 sleep 5

sleepの時間がdocker-stopのタイムアウトよりも長かった

これは単なる凡ミスです。
「スリープは長いほうがいいよね」と何も考えずに

while :
do
  sleep 100
done

としていました。
これだと、sleepの実行が終わって制御が返ってくるよりも
docker-stopのタイムアウトの方が早くきてしまうので、
kill -SIGKILLが実行されてしまいます。

まとめ

コンテナ停止時にスクリプトを実行する方法についてまとめました。
試行錯誤していく過程で、コンテナの開始時と停止時に何が起きているのか少しだけ理解できた気がします。
今回は試さなかったData volume containerを使ったバックアップ方法なども考えていきたいです。

その他参考にしたページ

29
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
24