📦

wrapper scriptを使ったマルチサービスコンテナについて

2023/09/15に公開

はじめに

まず前提として、コンテナのベストプラクティスとしては、1コンテナ1サービスです。
ただやはり、色々な理由で複数サービスを立ち上げたいというケースがあります。

ということで、Dockerのドキュメントにもその方法が紹介されており、
大体がこれらの方法を使用することになります。

Run multiple services in a container

この中でどれを採用するかはお好みで・・・となりますが、
今回は"Use a wrapper script"を使ったときに苦労したことの共有です。

何に苦労したのか

公式のwrapper scriptを転記すると下記のような表記です。

#!/bin/bash

# Start the first process
./my_first_process &

# Start the second process
./my_second_process &

# Wait for any process to exit
wait -n

# Exit with status of process that exited first
exit $?

これは複数のサービスを起動しつつ、いずれかのサービスが停止するまでwaitする。という設定になりますが、これには罠があります。

実はこのスクリプトの記載方法だと、各サービスのプロセスが停止した場合は問題なく動作しますが、DockerやKubernetesなどから停止処理(docker stop とか kubectl deleteとか)があった場合に、停止処理が走らないという罠があります・・・na,nandatte!!

なぜそんなことが起きるか

通常コンテナの停止処理が行われるとSIGTERMが送信されます。
そしてタイムアウト時間までに終了しない場合はSIGKILLが送信され強制終了します。

これが1コンテナ1サービスのコンテナであれば、SIGTERMが送られてくると、
メインプロセス(PID1)のプロセスがSIGTERMを受信して問題なく停止処理を行います。

しかしDockerドキュメントに記載されているスクリプトの記載では、wrapper sciprt自体がPID1として起動していますので、その子プロセスとして起動している各サービスのプロセスにはSIGTERMは受信されません。
そもそもスクリプト自体にもSIGTERMを処理する記載はないので、何時まで経ってもコンテナのサービスは停止されません。
そして最終的にはタイムアウト時間がきてSIGKILLで強制終了されてしまいます。

じゃあどう改善するべきか

wrapper sciprtにSIGTERMを受けた場合の処理を追加しましょう。
※例えばこんな感じです。

#!/bin/bash

## Shutdown Function
function sigterm() {
    for i in "${processid[@]}"
    do
        kill -15 $i; wait $i
    done
}
# Start the first process
./my_first_process &
processid+=($!)

# Start the second process
./my_second_process &
processid+=($!)

# SIGTERM Trap
trap 'sigterm; exit 0' 15

# Wait for any process to exit
wait -n

# Exit with status of process that exited first
exit $?
  • 各サービスのメインプロセスIDをprocessid配列に格納しておく
  • trapコマンドでSIGTERM = 15 (kill 15)を受信した場合の処理(sigterm関数)を追加する
  • sigterm関数ではprocessidの配列分、kill 15コマンドを実行する

こうすれば、元々のサービスいずれかのプロセスが落ちた場合はコンテナが停止するという処理を維持したまま、コンテナの通常停止処理を受けた場合でも正常停止できるコンテナを用意することが可能です。

複数サービスのコンテナを作ることに悩んでいる方がいれば参考になれば幸いです。
※もちろんこの記事は1コンテナ複数サービスを推奨するものではありませんので、
 できる限りは1コンテナ1サービスを維持することをお勧めします。

Discussion