Dockerコンテナを停止する際、ほとんどの人が無意識に使っているコマンドがあります。
それが docker stop
です。
単にコンテナを止めるためのコマンドだと思われがちですが、実はこの1行にはプロセス管理・シグナル伝達・リソース解放の設計思想が詰まっています。
この記事では、中〜上級者向けに docker stop
の動作原理と、アプリケーションを**優雅に停止(Graceful Shutdown)**させるための実践テクニックを掘り下げていきます。
1. docker stop
の内部動作を理解する
docker stop
の動作を簡単に言うと、「コンテナ内のメインプロセスに SIGTERM を送信し、指定時間後に SIGKILL を送る」というシンプルなものです。
実際のフローは以下のようになります。
- Docker はコンテナのPID 1に対して
SIGTERM
を送信 - 一定時間(デフォルト10秒)待機
- プロセスが終了しなければ
SIGKILL
で強制終了
この「10秒」という待機時間は、アプリケーションが終了処理(クリーンアップ)を行う猶予でもあります。
アプリ側がSIGTERMを無視していたり、時間内に処理を終えない場合、Dockerは容赦なく強制終了します。
タイムアウトの秒数は -t
オプションで調整可能です。
# 5秒以内に終了しなければSIGKILLを送る
$ docker stop -t 5 webapp
一方で、docker kill
は最初から SIGKILL
を送るため、Graceful Shutdown は一切行われません。
停止の設計を意識するなら、必ず stop を使うことが基本です。
2. アプリケーション側での Graceful Shutdown 実装
docker stop
が SIGTERM を送るといっても、アプリケーション側がそれを正しく受け取って処理しないと意味がありません。
特に注意すべきなのは「PID 1 問題」です。
コンテナ内で直接アプリを起動している場合、そのプロセスが PID 1 となり、シグナル伝達やゾンビプロセスのリープに関する特殊な挙動が発生します。
結果として、SIGTERM が無視されて強制終了されるケースもあります。
この問題を解決するには、Dockerの --init
オプションを利用します。
$ docker run --init -d myapp
--init
を付けることで tini
がプロセスをラップし、シグナル伝達や子プロセスの回収を正しく行ってくれます。
本番環境での安定運用にはほぼ必須です。
3. 停止タイミングをチューニングする
アプリによっては、終了処理に数十秒かかる場合もあります。
そのため、-t
オプションを適切に設定することが重要です。
例えば、データベースやキャッシュサーバなどは、セッション終了やフラッシュ処理のために長めの待機時間を設定します。
docker-compose.yml での設定例
services:
web:
image: nginx
depends_on:
- db
stop_grace_period: 30s
db:
image: mysql
stop_grace_period: 60s
Composeでは stop_grace_period
を設定することで、停止シーケンス全体を制御可能です。
依存関係のあるサービスを順序よく、安全に止める設計が求められます。
4. ハンズオン:Graceful Stopを実際に試す
ここからは実際にアプリを動かして、docker stop
の挙動を確認してみましょう。
ステップ1:Goアプリを用意
以下のコードは、SIGTERMを受け取って3秒かけて終了する簡単なプログラムです。
// main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
fmt.Println("App started. Waiting for stop signal...")
<-sigs
fmt.Println("Received SIGTERM, cleaning up...")
time.Sleep(3 * time.Second)
fmt.Println("Shutdown complete.")
}
ステップ2:Dockerイメージをビルド
$ docker build -t graceful-go .
ステップ3:実行して停止を試す
$ docker run --name graceful graceful-go
$ docker stop -t 5 graceful
ログに以下のような出力があれば成功です。
App started. Waiting for stop signal...
Received SIGTERM, cleaning up...
Shutdown complete.
もし -t
の値を2に変更して実行すると、Shutdown complete.
が表示されずに終了します。
これは SIGKILL
によって強制終了された証拠です。
5. 運用での応用とスクリプト連携
docker stop
は単独でも便利ですが、スクリプトやCI/CDの中で活用するとさらに効果を発揮します。
すべてのコンテナを安全に停止
$ docker stop $(docker ps -q)
順次停止スクリプトの例
for c in $(docker ps -q); do
echo "Stopping container $c..."
docker stop -t 15 $c
done
CI/CDでの利用
デプロイパイプラインで古いコンテナを安全に停止する際にも、docker stop
が使われます。
停止→削除→新コンテナ起動、という一連の流れの中でアプリの整合性を保つことができます。
6. トラブルシューティング
docker stop
で停止が遅い、あるいは止まらないケースがあります。
- プロセスが I/O 待ち (
D
状態) - SIGTERM を無視している
- PID 1 問題でシグナルが伝わらない
確認には docker inspect
コマンドを使いましょう。
$ docker inspect --format='{{.State.ExitCode}}' graceful
どうしても停止できない場合、最終手段として docker kill
を使用しますが、状態不整合のリスクを理解した上で使うようにしましょう。
7. 次のステップ:停止もインフラ設計の一部に
コンテナを安全に止めることは、起動と同じくらい重要です。
システム全体をGracefulにシャットダウンできる設計を目指すと、トラブル発生時やデプロイ時の安定性が格段に向上します。
次に挑戦してみるべきテーマは以下です。
- systemdでのDockerサービス停止制御
- Kubernetesの
preStop hook
やterminationGracePeriodSeconds
との比較 - 停止ログとイベント(
docker events
)を活用した監視設計
まとめ
docker stop
は、単なる「停止」ではなく、アプリケーションの終了設計そのものです。
Graceful Shutdownを理解し、適切な停止時間とシグナル処理を組み込むことで、
Docker環境の信頼性は大きく向上します。
起動の巧みさよりも、停止の優雅さこそが安定運用の証。
コメントを残す