本番環境が完全停止!緊急対応から再発防止策まで
はじめに
本番環境へのデプロイ直後に502 Bad Gatewayエラーが発生ました。その結果サービスが停止し、ユーザーがアクセスできない状態が30分近く続きました。エンジニアにとって最も緊張する時間です。
本記事では、WordPress本番環境で実際に発生した502エラーの完全な対応記録をご紹介します。コミット直後のエラー発生から原因特定、緊急復旧、そして再発防止策の実装まで、約2時間の対応を時系列で詳しく解説いたします。
危機的状況からの復旧手順と、GitHub ActionsやcronJobを活用した自動化による再発防止策は、同様の課題に直面している方の参考になれば幸いです。
インシデント発生:502エラーとの遭遇
発生時の状況
リンクカードのデザイン変更をコミットし、本番環境へのデプロイを完了した直後、サイトが502 Bad Gatewayエラーを返すようになりました。時刻は2025年12月11日00時29分39秒。発生からの経過時刻は約15分でした。
以下の図は、インシデント発生時の状況を示しています。
flowchart TD
A["デプロイ実行
00:08"] --> B["コミット完了
d0dc005"]
B --> C["約21分後"]
C --> D["502エラー発生
00:29:39"]
D --> E["コンテナ停止確認"]
E --> F{"原因調査開始"}
F --> G["ログ確認"]
F --> H["コンテナ状態確認"]
F --> I["システムログ確認"]
この図のように、デプロイとエラー発生の間に時間差があったため、最初はコードの問題を疑いました。
初期の仮説
デプロイ直後のエラーであったため、以下の仮説を立てました。
- コードの問題: リンクカードのPHPコードに構文エラーがあるのではないか。30行以上のコードを4行に簡素化したため、削除した要素への参照が残っている可能性がありました。
- コンテナのクラッシュ: 新しいコードが原因で何かしらのプロセスが異常終了したのではないか。メモリリークや無限ループの可能性も考えられました。
- デプロイの失敗: GitHub Actionsが正常に完了せず、中途半端な状態でデプロイされたのではないか。
後の調査でこれらの仮説はすべて誤りでした。実際の原因は全く別のところにあったのです。
原因調査:ディスク容量不足の発見
システムログの確認
コンテナのログを確認したところ、重要な手がかりが見つかりました。WordPressとnginxのコンテナが00時29分39秒に正常終了(exitコード0)していました。
[11-Dec-2025 00:29:39] NOTICE: Finishing ...
[11-Dec-2025 00:29:39] NOTICE: exiting, bye-bye!
正常終了であれば、コードの問題ではありません。もう少しログを見ていきます。
error checkpointing container state: no space left on device
Cannot restart container: mkdir: no space left on device
ディスク容量不足が根本原因であることが発覚しました。
ディスク使用状況の分析
ディスク使用率を確認したところ、衝撃的な結果が判明しました。
Filesystem Size Used Avail Use% Mounted on
/dev/root 19G 19G 295M 99% /
19GBのルートボリュームが99%使用されており、残り295MBしかありません。
さらに詳しく調査したところ、以下のような内訳でした。
/varディレクトリ: 17GB(全体の89%)/var/libディレクトリ: 17GB(Dockerデータ)- Docker使用量: イメージ12.8GB、ボリューム1.976GB
Dockerイメージのうち75%(9.6GB)が未使用で回収可能な状態ですね。
以下の図は、ディスク使用状況の内訳を示しています。
flowchart LR
A["ルートボリューム
19GB"] --> B["/var 17GB"]
A --> C["その他 2GB"]
B --> D["/var/lib 17GB"]
D --> E["Dockerイメージ
12.8GB"]
D --> F["Dockerボリューム
1.976GB"]
D --> G["その他 2.2GB"]
E --> H["使用中 3.2GB"]
E --> I["未使用 9.6GB
回収可能"]
F --> J["使用中 0.6GB"]
F --> K["未使用 1.3GB
回収可能"]
style I fill:#ff6b6b
style K fill:#ff6b6b
style A fill:#ffd93d
不要なDockerイメージの蓄積
Dockerイメージを詳しく調査したところ、WordPressイメージが16個も保存されています。
docker images | grep slab-wp/wordpress | wc -l
16
これらのイメージは以下の2種類に分かれていました。
- 古い巨大イメージ(12月2〜4日): 1.6GB〜1.7GB × 5個 = 約8GB
- 新しいイメージ(12月4〜8日): 353MB × 11個 = 約3.8GB
現在使用中のイメージはlatestタグの1つのみで、他の15個はすべて不要でした。合計で約12GBが無駄に消費されていたのです。
なぜイメージが蓄積したのか
5 Whysの手法で根本原因を深掘りしました。
flowchart TD
Q1["なぜ502エラーが
発生したのか?"] --> A1["コンテナが停止
していたため"]
A1 --> Q2["なぜコンテナが
停止したのか?"]
Q2 --> A2["ディスク容量不足で
状態保存に失敗"]
A2 --> Q3["なぜディスク容量が
不足したのか?"]
Q3 --> A3["17GBをDockerが
使用していた"]
A3 --> Q4["なぜDockerデータが
蓄積したのか?"]
Q4 --> A4["定期クリーンアップの
仕組みがなかった"]
A4 --> Q5["なぜクリーンアップが
なかったのか?"]
Q5 --> A5["監視・アラート・
自動化がすべて欠如"]
style A5 fill:#ff6b6b
style Q5 fill:#a8dadc
最終的な根本原因は、以下の4つの要素がすべて欠如していたことでした。
- ディスク使用量の監視システム
- 使用率超過時のアラート
- Dockerリソースの定期クリーンアップ
- デプロイ時の古いイメージ削除
緊急復旧:15分での対応
コンテナの再起動
まず、停止しているコンテナの再起動を試みました。
cd /home/ubuntu/slab-wp/docker
docker compose -f docker-compose.prod.yml restart wordpress nginx
しかし、ディスク容量不足のため再起動に失敗しました。nginxイメージが破損しており、エントリーポイントが見つからないというエラーが発生しました。
Error: exec: "/docker-entrypoint.sh": stat: no such file or directory
イメージの再取得とコンテナ再作成
WordPressコンテナは再作成に成功しましたが、nginxは失敗しました。そこで、nginxイメージを再取得してから起動しました。
# 破損したnginxコンテナを削除
docker rm -f slab-nginx
# 新しいイメージを取得
docker pull nginx:alpine
# コンテナを再作成
docker compose -f docker-compose.prod.yml up -d nginx
この手順により、約10分でサイトが復旧しました。最終確認として、外部からアクセスして200 OKのレスポンスを確認しました。
curl -s -o /dev/null -w '%{http_code}' https://scriptlab.jp
200
以下の図は、復旧手順の流れを示しています。
sequenceDiagram
participant Ops as 運用者
participant Docker as Docker
participant WP as WordPress
participant Nginx as nginx
Ops->>Docker: コンテナ再起動試行
Docker-->>Ops: 失敗(ディスク不足)
Ops->>Docker: WordPressコンテナ再作成
Docker->>WP: 起動成功
WP-->>Docker: 正常稼働
Ops->>Docker: nginxコンテナ再起動試行
Docker-->>Ops: 失敗(イメージ破損)
Ops->>Docker: nginxイメージ削除+再取得
Docker->>Nginx: 新イメージ取得
Ops->>Docker: nginxコンテナ再作成
Docker->>Nginx: 起動成功
Nginx-->>Docker: 正常稼働
Ops->>Docker: ヘルスチェック
Docker-->>Ops: 全コンテナ正常
不要リソースの削除:11.5GBの回収
Dockerイメージの削除
復旧後、すぐにディスク容量の問題に対処しました。現在使用中のlatestタグ以外のWordPressイメージをすべて削除しました。
docker images --format '{{.ID}}' | \
grep -E '^(be6e04|011797|6b4999|...)$' | \
xargs docker rmi -f
結果として、14個のイメージを削除できました(1つは使用中のため削除不可)。
未使用ボリュームの削除
次に、使用されていないDockerボリュームも削除しました。
docker volume prune -f
6個のボリュームが削除され、1.359GBを回収しました。
削除後のディスク状況
クリーンアップ後、ディスク使用率が劇的に改善されました。
Filesystem Size Used Avail Use% Mounted on
/dev/root 19G 7.5G 11G 41% /
99%から41%へ、58ポイントの改善でした。合計で約11.5GBを回収し、安全なレベルまで回復しました。
以下の図は、クリーンアップ前後の比較を示しています。
flowchart LR
subgraph Before["クリーンアップ前"]
B1["使用: 19GB
99%"]
B2["空き: 295MB
1%"]
end
subgraph After["クリーンアップ後"]
A1["使用: 7.5GB
41%"]
A2["空き: 11GB
59%"]
end
Before -."削除 11.5GB".-> After
style B1 fill:#ff6b6b
style A1 fill:#51cf66
style A2 fill:#94d82d
再発防止策の実装
GitHub Actionsへの自動削除追加
デプロイ時に自動的に古いイメージを削除するよう、GitHub Actionsワークフローを修正しました。
.github/workflows/deploy.ymlに以下の処理を追加しました。
# Cleanup old images (keep only latest 2 versions)
echo "Cleaning up old Docker images..."
docker images --format '{{.ID}}\t{{.Repository}}:{{.Tag}}\t{{.CreatedAt}}' | \
grep '${{ env.ECR_REPOSITORY }}' | \
grep -v 'latest' | \
sort -k3 -r | \
tail -n +3 | \
awk '{print $1}' | \
xargs -r docker rmi -f || true
# Cleanup untagged images
docker image prune -f
# Cleanup unused volumes
docker volume prune -f
この処理により、デプロイのたびに以下が実行されます。
- 最新2バージョン以外のWordPressイメージを削除
- タグなしイメージの削除
- 未使用ボリュームの削除
定期クリーンアップのcron設定
週次で自動クリーンアップを実行するスクリプトを作成しました。
#!/bin/bash
# /usr/local/bin/docker-cleanup.sh
LOG_FILE="/var/log/docker-cleanup.log"
echo "=== Docker Cleanup Started: $(date) ===" >> ${LOG_FILE}
# Remove old WordPress images (keep only latest 2)
docker images --format '{{.ID}}\t{{.Repository}}:{{.Tag}}\t{{.CreatedAt}}' | \
grep 'slab-wp/wordpress' | \
grep -v 'latest' | \
sort -k3 -r | \
tail -n +3 | \
awk '{print $1}' | \
xargs -r docker rmi -f >> ${LOG_FILE} 2>&1 || true
# Remove dangling images
docker image prune -f >> ${LOG_FILE} 2>&1
# Remove unused volumes
docker volume prune -f >> ${LOG_FILE} 2>&1
echo "=== Docker Cleanup Completed: $(date) ===" >> ${LOG_FILE}
cronジョブとして毎週日曜日12時に実行するよう設定しました。
0 12 * * 0 /usr/local/bin/docker-cleanup.sh
ディスク容量監視の導入
30分ごとにディスク使用率をチェックし、閾値を超えた場合にログに記録するスクリプトを作成しました。
#!/bin/bash
# /usr/local/bin/disk-monitor.sh
THRESHOLD_WARNING=80
THRESHOLD_CRITICAL=90
LOG_FILE="/var/log/disk-monitor.log"
USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
DATE=$(date '+%Y-%m-%d %H:%M:%S')
if [ ${USAGE} -ge ${THRESHOLD_CRITICAL} ]; then
MESSAGE="CRITICAL: Disk usage is ${USAGE}% (threshold: ${THRESHOLD_CRITICAL}%)"
echo "[${DATE}] ${MESSAGE}" >> ${LOG_FILE}
logger -t disk-monitor -p user.crit "${MESSAGE}"
elif [ ${USAGE} -ge ${THRESHOLD_WARNING} ]; then
MESSAGE="WARNING: Disk usage is ${USAGE}% (threshold: ${THRESHOLD_WARNING}%)"
echo "[${DATE}] ${MESSAGE}" >> ${LOG_FILE}
logger -t disk-monitor -p user.warning "${MESSAGE}"
fi
cronジョブとして30分ごとに実行するよう設定しました。
*/30 * * * * /usr/local/bin/disk-monitor.sh
閾値は以下のように設定しました。
- 80%: 警告レベル – ログに記録
- 90%: アラートレベル – ログに記録+syslogに送信
以下の図は、再発防止策の全体像を示しています。
flowchart TD
A["デプロイ"] --> B["GitHub Actions"]
B --> C["古いイメージ削除
最新2つ保持"]
B --> D["未使用ボリューム削除"]
E["週次
日曜12:00"] --> F["cronジョブ"]
F --> G["Docker
クリーンアップ"]
G --> C
H["30分ごと"] --> I["監視スクリプト"]
I --> J{"使用率チェック"}
J -->|"80%以上"| K["警告ログ"]
J -->|"90%以上"| L["アラートログ
+ syslog"]
J -->|"80%未満"| M["OK"]
style C fill:#51cf66
style G fill:#51cf66
style K fill:#ffd93d
style L fill:#ff6b6b
style M fill:#51cf66
ポストモーテム:学んだ教訓
コミットとの因果関係
最初、デプロイ直後のエラーだったため、コードに問題があると考えました。しかし、調査の結果、以下の事実が判明しました。
- 本番環境のイメージ:
20251208-221128-6af75b9(12月8日22時11分) - 最新のコミット:
d0dc005(12月11日00時08分) - デプロイギャップ: 約26時間
最新のコードは本番環境にデプロイされていませんでした。時間的な一致は偶然であり、因果関係はありませんでした。
ディスク容量が限界に達したタイミングで、Dockerが定期的なメンテナンス処理を試みた際に書き込み領域がなくなり、コンテナが停止したのです。
予防的メンテナンスの重要性
今回の障害から、以下の教訓を得ました。
インフラメトリクスの継続的な監視が必須: ディスク使用率を定期的にチェックし、閾値を超えた時点でアラートを発する仕組みが必要でした。
問題が発生する前の定期的なクリーンアップ: 手動での対応ではなく、自動化されたクリーンアップを事前に実装すべきでした。
適切なリソース計画: サービス開始時に、将来の成長を見据えた十分なディスク容量を確保する必要がありました。
デプロイプロセスの検証: デプロイが正常に動作していることを定期的に確認する仕組みが必要でした。
効果的だった対応
一方で、以下の点は効果的に機能しました。
ログの保存: システムログに明確な証拠(no space left on device)が残っており、根本原因の特定が容易でした。
迅速な復旧: コンテナ再起動により15分で復旧できました。
データ保護: MySQLコンテナは影響を受けず、データ損失はありませんでした。
終わりに
本記事では、WordPress本番環境で発生した502エラーの完全な対応記録をご紹介しました。ディスク容量99%という危機的状況から、わずか15分で復旧し、さらに11.5GBを回収して安全なレベルまで改善できました。
今回の経験から、予防的メンテナンスの重要性を痛感しました。監視、アラート、自動化の3つの仕組みをすべて実装することで、同様の問題の再発を防ぐことができます。GitHub ActionsやcronJobを活用した自動クリーンアップは、運用負荷を下げながら安定性を高める効果的な手法です。
本番障害は誰にでも起こりうるものです。重要なのは、問題が発生したときに冷静に原因を特定し、適切な対応を取り、そして再発防止策を確実に実装することです。本記事が、同様の課題に直面している方の一助となれば幸いです。
Docker Hub公式ドキュメント
GitHub Actions公式ドキュメント
AWS EC2ユーザーガイド
WordPress公式ドキュメント
コメントを残す