Docker本番運用 実践ガイド|「動く」から「守って回す」へ

はじめに

Docker は「環境差分をなくす」「再現性を上げる」「デプロイを速くする」ための強力な道具です。ですが本番運用になると、開発環境では見えにくい現実が一気に押し寄せます。たとえば——

  • メモリがじわじわ増え続けて深夜に OOM(メモリ不足)で落ちる
  • ある日突然、外部 API が遅くなってコンテナが詰まり、全体が巻き込まれる
  • ログが溜まりすぎてディスクが枯渇し、Docker 自体が動かなくなる
  • 更新のたびに「止めて入れ替えて祈る」状態になっている
  • そもそも障害時に “何を見ればいいか” が決まっていない

つまり、本番のテーマは「作る」より「守る」「回す」「壊れても復旧できる」に寄ります。
この記事は、**Docker を単体運用するケース(Docker Engine + Compose を含む)**を想定しつつ、Kubernetes の話に逃げずに Dockerで本番を成立させる運用設計を整理します。ハンズオンでは、ヘルスチェック・ログ・ローテーション・更新手順・障害対応の入口まで実際に触れる形にしています。


座学

1) 本番運用で最初に決めるべき “成功条件”

本番運用を成功させるには、技術の前に「成功条件(SLO/運用目標)」が必要です。難しい言葉に見えますが、要するに次のような合意です。

  • 可用性:落ちていいのか?落ちたら何分で戻すのか?
  • 更新:更新時に止めていいのか?無停止に近づけるのか?
  • 障害対応:誰が、何を見て、どの順序で切り分けるのか?
  • 容量:CPU/メモリ/ディスクの上限と、超えた時の振る舞いは?

これが決まらないと「何を監視すべきか」「どこまで冗長化すべきか」がブレます。
Docker の本番運用は、コンテナのテクニックだけではなく 運用品質の設計が勝負です。


2) “コンテナが落ちる” を前提にした設計思想

本番ではコンテナが落ちます。落ちない前提は崩壊します。
なので設計は「落ちてもよい」「落ちたら戻る」「戻ったあとに原因が追える」に寄せます。

  • 自己回復:再起動ポリシー(restart)とヘルスチェック
  • 状態の外出し:永続データはボリューム/外部DB、アプリはステートレスへ
  • 観測可能性:ログ、メトリクス、最低限のトレース(相関ID)
  • 復旧手順:落ちた時に “正しいコマンド” がすぐ出せる

「落ちたら手で直す」から、「落ちても勝手に戻って、後から原因を詰める」へ。
これが Docker 本番運用の基本姿勢です。


3) リソース・ファイル・ログ——地味だけど死に直結する話

本番で本当に怖いのは、地味な枯渇です。

  • メモリ枯渇:OOM Killer によるプロセス強制終了
  • ディスク枯渇:ログ肥大、イメージ溜まり、overlay2 の肥大化
  • ファイルディスクリプタ枯渇:同時接続やログの扱いで起きる
  • CPU張り付き:スレッドの暴走や外部待ちでのスピン

対策の方向性はシンプルです。

  • 上限を決める(無限に使わせない)
  • 観測する(増え方を見て予防する)
  • 掃除する(ログと不要イメージの整理を定期化する)

運用の現場では「たまたま耐えていた」が一番危険です。
本番は “たまたま” がいつか必ず崩れます。


4) 更新(デプロイ)の設計:まずは “怖くない更新” を作る

本番更新で重要なのは速度より 確実性です。

  • 更新対象のイメージが正しいか(タグ運用のルール、または digest 固定)
  • 更新手順が自動化されているか(人が覚えなくていい)
  • 更新後の健全性チェックがあるか(落ちてない、だけでは足りない)
  • ロールバックできるか(戻す手順が “同じ強さ” で用意されているか)

いきなり無停止更新を目指すより、まずは
「止めてもいいから、安全に更新できる」→「止めない更新へ」
の順に成熟させるのが現実的です。


5) “障害対応の型” を最初に用意する

障害時は焦ります。焦るとミスします。
だから、普段から “型” を持ちます。例:

  1. 現象の確認:どのコンテナが落ちた?いつから?頻度は?
  2. ログ確認:直近ログ、エラーパターン、外部依存のタイムアウト
  3. リソース確認:メモリ/ディスク/CPU、OOM の有無
  4. 復旧:再起動・ロールバック・一時的な機能制限
  5. 再発防止:アラート・上限設定・タイムアウト設計・負荷試験

Docker 本番運用は、障害をゼロにすることより
障害が起きても早く戻して、学習して強くすることが本質です。


ハンズオン

ここでは「本番らしい運用」を体験するために、**Nginx + 簡易アプリ(/health)**を Compose で動かし、以下を作ります。

  • healthcheck で “死んでるのに動いてる” を防ぐ
  • ログ肥大を防ぐ(json-file のローテーション)
  • 再起動ポリシーで自己回復
  • 更新手順(段階的に入れ替える)
  • 障害の入口:ディスク・メモリ・ログの確認コマンド

※ コマンドは Linux / macOS を想定。Windows は WSL2 推奨。


0) 構成ファイルを作成

作業ディレクトリを作ります。

mkdir docker-prod-ops && cd docker-prod-ops
mkdir app

簡易アプリ(ここでは Node でも Python でもなく、依存少なめに “HTTPで返すだけ” に寄せます)。
Nginx だけでも良いですが、/health を確実に作るために tiny な HTTP サーバを置きます。

app/server.js(Node.jsの超最小例)

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.url === "/health") {
    res.writeHead(200, {"Content-Type": "application/json"});
    res.end(JSON.stringify({ status: "ok" }));
    return;
  }
  res.writeHead(200, {"Content-Type": "text/plain"});
  res.end("hello from app\n");
});

server.listen(3000, "0.0.0.0", () => {
  console.log("app listening on :3000");
});

app/Dockerfile

FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 3000
CMD ["node", "server.js"]

nginx.conf

events {}
http {
  upstream app {
    server app:3000;
  }

  server {
    listen 80;

    location /health {
      proxy_pass http://app/health;
    }

    location / {
      proxy_pass http://app/;
    }
  }
}

1) docker-compose.yml(本番運用を意識した項目入り)

services:
  app:
    build: ./app
    container_name: prodops-app
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health | grep -q ok"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 10s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  nginx:
    image: nginx:1.27-alpine
    container_name: prodops-nginx
    depends_on:
      app:
        condition: service_healthy
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

ポイント(本番運用の匂いがする部分):

  • restart: unless-stopped:落ちても勝手に戻る
  • healthcheck:プロセスが動いてても “サービスとして死んでる” を検出
  • depends_on: condition: service_healthy:起動順を “健康状態” ベースに
  • loggingmax-size/max-file:ログでディスクを殺さない

2) 起動して “健康状態” を確認

docker compose up -d --build
docker compose ps

疎通確認:

curl -s http://localhost:8080/
curl -s http://localhost:8080/health

ヘルスの確認:

docker inspect -f '{{json .State.Health}}' prodops-app | head

3) 障害を起こして自己回復を見る(わざと落とす)

アプリプロセスを kill してみます。

docker exec -it prodops-app sh -lc "ps | head; kill 1"

少し待ってから:

docker compose ps
docker logs --tail=30 prodops-app
  • restart によりコンテナが復帰する
  • healthcheck により “復帰したか” が状態として見える

この「落ちても戻る」を目で確認するのが重要です。


4) ログ肥大の防止を確認(ローテーション設定チェック)

ログドライバの設定確認:

docker inspect -f '{{json .HostConfig.LogConfig}}' prodops-nginx

json-file ログの実体は Docker の管理領域にあります。
肥大しやすいので、max-size/max-file を必ず意識します(ここが抜けると、ある日ディスクが死にます)。


5) “更新” の手順を型にする(安全に入れ替える)

まず現状を把握:

docker images | head
docker compose ps

app/server.js を少し変えて “更新” を作ります(例:レスポンス文言変更)。
変更後に、app だけ再ビルドして入れ替え

docker compose up -d --build app

疎通:

curl -s http://localhost:8080/
curl -s http://localhost:8080/health

運用のコツ:

  • 「全部再起動」ではなく 変更したサービスだけを更新する
  • 更新後に health と疎通を必ず確認する
  • 手順を固定化して、事故を減らす(人が変わっても同じ動きになる)

6) 障害対応の基本コマンド(入口セット)

本番運用で “まず叩く” コマンドを手元に置きます。

# どれが落ちてる?再起動してる?
docker compose ps

# 直近ログ(まずは原因の匂いをつかむ)
docker logs --tail=200 prodops-app
docker logs --tail=200 prodops-nginx

# リソース(CPU/メモリ)
docker stats --no-stream

# ディスク(Dockerが食ってる容量)
docker system df

# 不要物掃除(※本番では慎重に。手順化して実行)
docker image prune

「コマンドを知ってる」ではなく、障害時に順番通り打てるが大事です。
順番が決まっているだけで、深夜の判断ミスが激減します。


まとめ

Docker 本番運用は、コンテナを動かすことよりも 運用の設計で差が出ます。重要なのは次の考え方です。

  • 落ちる前提で設計する(再起動・ヘルスチェック・状態の外出し)
  • 枯渇を防ぐ(ログローテ、ディスク・メモリの監視と上限)
  • 更新を怖くしない(更新手順の固定化、更新後チェック、ロールバック意識)
  • 障害対応の型を持つ(見る順番、戻す順番、学ぶ仕組み)

本番は「うまくいった」より、「壊れた時に戻せた」が価値です。
今日作った仕組みは小さいですが、考え方はそのまま大規模にも拡張できます。


投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

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