Dockerコンテナで実行されているサービスは sudo docker stop <container>
あるいは sudo docker-compose down
などによって停止することができます。
vibe.dをDockerコンテナ上でサービスとして実行する際、安全に停止するために停止処理を仕込みたい場合があります。
以下、Linux(Ubuntu)での実行を前提とします。
Dockerコンテナが停止される際何が起こるか
コンテナ内のメイン・プロセスは SIGTERM を受信します。一定期間経過すると、 SIGKILL を送ります。
http://docs.docker.jp/engine/reference/commandline/stop.html
sudo docker stop <container>
を行うと、メインプロセス(PID 1)に対してSIGTERMシグナルを送信します。
正常に終了させたい場合、アプリケーションはこれ(SIGTERM)を正しく処理して終了処理を行う必要があります。
また、ドキュメントの一定期間というのは、デフォルトだと10秒ですので、10秒以内に終了処理を行うのが良いでしょう。
幸いなことに、vibe.dで書かれたアプリケーションはデフォルトでこれ(SIGTERM)を処理するよう仕込まれています。
これは、コマンドライン上で実行した際、Ctrl-C
でサービスを止める際にSIGINTが発生して止めるのと同じです。
なお、docker kill <container>
や一定期間後の SIGKILL で停止する際には本当に処理の途中で即死します。
vibe.dサービスの停止処理を書く場所
vibe.dはアプリケーションがSIGTERM(あるいはSIGINT)を受信すると、イベントループを終了します。したがって、終了処理は runApplication();
や runEventLoop();
の後に書きます。
仮に終了処理が、Exit service!
とログ出力することだとすると、以下のような感じになります。
import vibe.vibe;
void index(HTTPServerRequest req, HTTPServerResponse res)
{
import diet.html;
import std.string: chompPrefix, outdent;
import std.array: appender;
auto contents = appender!string;
contents.compileHTMLDietString!(`
doctype 5
html
head
title Welcome
body
h1 Welcome
`.chompPrefix("\n").outdent);
res.writeBody(contents.data, "text/html");
}
void main()
{
auto router = new URLRouter;
router.get("/", &index);
auto settings = new HTTPServerSettings;
settings.port = 80;
settings.bindAddresses = ["0.0.0.0"];
auto listener = listenHTTP(settings, router);
scope (exit)
listener.stopListening();
runApplication();
// 終了処理としてログ出力する
logInfo("Exit service!");
}
コンテナイメージの作り方
アプリケーションで正しくSIGTERMを受信するには、注意点があります。
ドキュメントにはkillコマンドの注意点として記載されていますが、stopも同じです。
ENTRYPOINT
とCMD
を シェル 形式で実行している場合は、/bin/sh -c
のサブコマンドとして実行されていますので、シグナルを受け取ることができません。つまり、(シェル形式では)コンテナの PID 1 は Unix シグナルを受け取りません。
http://docs.docker.jp/engine/reference/commandline/kill.html
とのことですので、コンテナイメージは ENTRYPOINT
や CMD
の書き方に注意が必要です。
なお、以下の例では、アプリケーションのバイナリ名は test-app
です。
FROM ubuntu:latest
RUN DEBIAN_FRONTEND=noninteractive \
apt-get update && \
apt-get install -y libssl1.1
COPY ./test-app /root/test-app
RUN chmod +x /root/test-app
WORKDIR /root
ENTRYPOINT ["/root/test-app"]
ここで、 ENTRYPOINT
が ENTRYPOINT /root/test-app
とか ENTRYPOINT "/root/test-app"
とか書くとシェル形式になるためNGです。
CMD
にしたい場合も同じで、 CMD ["/root/test-app"]
のようにしないといけません。
サービス実行して確認する
Dockerfileが書けたらビルドして、サービスを立ち上げてみます。
# サービスのビルド
dub build
# コンテナイメージのビルド: イメージ名 = test-image
docker build -t test-image .
# コンテナ立ち上げ: コンテナ名 = test-container / --rm で終了後コンテナ削除 / -d でサービス化
docker run --name test-container --rm -d test-image
そして、ちゃんとstop時の処理ができているかを以下で確認します。
今回の場合、ログ(=アプリケーションの標準出力/標準エラー出力=コンテナのログ)に出力されることを確認するので、docker logs -f --tail=10 test-container
とかしてログを出しながら終了します。
docker logs -f --tail=10 test-container & docker stop test-container
結果:
[1] 26666
[main(----) INF] Listening for requests on http://0.0.0.0:80/
Vibe was run as root, and no user/group has been specified for privilege lowering. Running with full permissions.
[main(----) INF] Received signal 15. Shutting down.
[main(----) INF] Exit service!
[main(----) INF] Stopped to listen for HTTP requests on 0.0.0.0:80
test-container
[1]+ Done docker logs -f --tail=10 test-container
ちゃんと[main(----) INF] Exit service!
と出力されることが確認できます。