docker stop の本質:Graceful Shutdownと停止チューニングを極める

Dockerコンテナを停止する際、ほとんどの人が無意識に使っているコマンドがあります。
それが docker stop です。

単にコンテナを止めるためのコマンドだと思われがちですが、実はこの1行にはプロセス管理・シグナル伝達・リソース解放の設計思想が詰まっています。
この記事では、中〜上級者向けに docker stop の動作原理と、アプリケーションを**優雅に停止(Graceful Shutdown)**させるための実践テクニックを掘り下げていきます。


1. docker stop の内部動作を理解する

docker stop の動作を簡単に言うと、「コンテナ内のメインプロセスに SIGTERM を送信し、指定時間後に SIGKILL を送る」というシンプルなものです。

実際のフローは以下のようになります。

  1. Docker はコンテナのPID 1に対して SIGTERM を送信
  2. 一定時間(デフォルト10秒)待機
  3. プロセスが終了しなければ 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 hookterminationGracePeriodSecondsとの比較
  • 停止ログとイベント(docker events)を活用した監視設計

まとめ

docker stop は、単なる「停止」ではなく、アプリケーションの終了設計そのものです。
Graceful Shutdownを理解し、適切な停止時間とシグナル処理を組み込むことで、
Docker環境の信頼性は大きく向上します。

起動の巧みさよりも、停止の優雅さこそが安定運用の証。


投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です