5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

vibe.dを使ったサービスをDockerコンテナ上で動かして停止を検出して終了処理する

Posted at

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!とログ出力することだとすると、以下のような感じになります。

source/app.d
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も同じです。

ENTRYPOINTCMD を シェル 形式で実行している場合は、 /bin/sh -c のサブコマンドとして実行されていますので、シグナルを受け取ることができません。つまり、(シェル形式では)コンテナの PID 1 は Unix シグナルを受け取りません。
http://docs.docker.jp/engine/reference/commandline/kill.html

とのことですので、コンテナイメージは ENTRYPOINTCMD の書き方に注意が必要です。

なお、以下の例では、アプリケーションのバイナリ名は test-app です。

Dockerfile
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"]

ここで、 ENTRYPOINTENTRYPOINT /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!と出力されることが確認できます。

5
0
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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?