はじめに
この投稿は、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 ドキュメント] (http://docs.docker.jp/v1.11/engine/reference/builder.html?highlight=cmd#cmd)を参照してください。
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します。
参考:
- [Dockerコンテナー内のbashからアプリケーション停止処理を実施するTIPS - めもめも] (http://enakai00.hatenablog.com/entry/20140824/1408855090)
- [Bashのシグナルトラップ - 双六工場日誌] (http://sechiro.hatenablog.com/entry/20130216)
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で停止できず、
-
docker-stop
のタイムアウト(デフォルト10秒)まで待つ -
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を使ったバックアップ方法なども考えていきたいです。
その他参考にしたページ