AWS運用・トラブル解決ガイド|切り分けの型と監視設計
目次
AWSで動くシステムの運用を任されて最初に困るのは、「エラーは出ているが、どこを見ればいいか分からない」という状態です。アプリのログに異常がないのにALBが502を返す。昨日まで動いていたバッチがAccessDeniedで落ちる。RDSのCPUは低いのにレスポンスが遅い。こうした事象はエラーメッセージで検索しても断片的な答えしか返ってきません。
このページでは、経路上のどこで詰まっているかを順に確認していく手順をまとめます。レイヤごとの切り分け、ログの置き場所、再発を防ぐ監視の作り方、運用スキルを身につける順番まで扱います。深掘りが必要な論点は該当記事へ案内します。
AWS運用のトラブルは「原因」より先に「場所」を特定する
障害対応が長引くチームには共通点があります。原因の仮説から入り、その仮説に合う証拠だけを探してしまうことです。切り分けはその逆で、まず「リクエストがどこまで到達しているか」を機械的に絞り込みます。場所が決まれば見るべきログは1〜2種類に減り、原因候補も数個に収束します。
症状から疑うレイヤを対応づける早見表
症状とレイヤの対応は、ある程度は定型化できます。
| 症状 | 最初に疑うレイヤ | 最初に見るもの |
|---|---|---|
| 接続自体が確立しない(タイムアウト) | DNS / SG / NACL / ルートテーブル | dig、VPCフローログのREJECT |
| TLSハンドシェイクで切れる | ACM証明書 / SNI / セキュリティポリシー | ALBアクセスログのssl_protocol、error_reason |
| 503 Service Unavailable | ターゲットグループの健全性 | HealthyHostCount、ターゲットの登録状態 |
| 502 Bad Gateway | ALB〜ターゲット間の接続・レスポンス | ALBアクセスログのtarget_status_code |
| 504 Gateway Timeout | ターゲットの処理時間 / アイドルタイムアウト | target_processing_time |
| 全体的に遅い | DB / 外部API / CPU・メモリ | Performance Insights、target_processing_timeの分布 |
| 特定の操作だけ失敗 | IAM / リソースポリシー / KMS | CloudTrailのerrorCode |
| デプロイ直後だけ失敗 | 接続のドレイン / 起動順序 / 設定差分 | デプロイイベントとエラーの時刻突合 |
暗記する必要はありません。「症状→見るログ」の最短経路をチームで共有するための表です。自組織の構成に合わせて列を足せば、CloudFrontやWAFを挟んでいる環境でも新しいメンバーの初動が揃います。
推測から入ると外す典型パターン
「最近デプロイしたからアプリのバグだろう」という推測は、半分当たって半分外れます。ALBの502は、アプリのコードが正常でも発生します。アプリ側のKeep-Aliveタイムアウトが短ければ、ALBとの間で競合状態が起きるからです。アプリのログには何も残らないので、コードを読み続けても原因にたどり着きません。
「CPU使用率が低いから性能問題ではない」という判断も同じくらい多い。DBがI/O待ちやロック待ちで詰まっているとき、CPUはむしろ下がります。CPU使用率は忙しさの指標であって、待たされていることの指標ではありません。待ちを見るには待機イベント(Performance InsightsのDBLoad内訳)が必要です。
もう一つは「昨日まで動いていたから設定は変わっていない」という前提です。AWSではIAMポリシー、SCP、KMSキーポリシー、VPCエンドポイントポリシー、証明書の有効期限、マネージドサービスのマイナーバージョン更新など、アプリのリポジトリの外側で状態が変わる要素が多数あります。「変わっていないはず」は、CloudTrailやConfigで確認してから言うべき仮説です。
対応の優先順位は「影響範囲」と「再現性」で決める
複数の事象が同時に見えているときは、影響範囲と再現性の2軸で順番を決めます。影響範囲が全ユーザーなら、原因究明より先に切り戻しや縮退を検討します。再現性については、常時再現するものは原因特定が容易なので、調査コストが低い順に片付けると全体の収束が早まります。
ランダムに発生する事象(数千リクエストに1回の502など)は、原因特定に時間がかかる一方で影響が小さいことが多い。障害対応の最中に深追いすると全体の復旧が遅れます。「暫定対処で影響を止める」「恒久対処は別チケットにする」を明示的に分ける運用ルールがあれば、判断が属人化しません。
切り分けの土台になるAWSの構造を押さえる
切り分けの速さは、知識量よりも「どこに何が記録されているか」を知っているかで決まります。AWSはレイヤごとに別のサービスがログを出すため、構造を頭に入れておかないと探す場所が見つかりません。
リクエストが通る経路とホップごとの責任分界
一般的な3層構成では、リクエストは次のホップを通ります。
クライアント
→ Route 53(名前解決)
→ CloudFront(任意/エッジキャッシュ)
→ ALB(TLS終端・ルーティング・ヘルスチェック)
→ ターゲット(EC2 / ECSタスク / Lambda)
→ RDS・Aurora / ElastiCache / S3 / 外部API
各ホップは「自分が受けたリクエストと返したレスポンス」しか知りません。ALBはターゲットが内部でDBを待っていることを知りませんし、アプリはALBが接続を切ったことを知らない場合があります。あるホップで異常が見えたら、その上流と下流の両方のログで同じリクエストを突き合わせる。これが切り分けの基本動作です。
突き合わせに使えるのがX-Amzn-Trace-Idです。ALBが付与するこのヘッダをアプリのアクセスログにも出力しておくと、ALBアクセスログの1行とアプリのログ行を一意に紐づけられます。構成を組んだ段階で入れておきたい設定です。
どこで何が記録されるか:ALBアクセスログ・VPCフローログ・CloudTrail・CloudWatch Logs
ALBアクセスログはHTTPレベルの結果を記録します。S3に出力され、デフォルトでは無効なので明示的な有効化が必要です。切り分けの核心は、elb_status_code(ALBが返した値)とtarget_status_code(ターゲットが返した値)が別の列になっている点にあります。
VPCフローログはIP・ポート・プロトコル単位のACCEPT/REJECTです。HTTPの中身は見えませんが、パケットが届いているか、どこで弾かれたかを判定できます。
CloudTrailはAWS APIの呼び出し履歴で、誰がいつ何を変更したか、AccessDeniedがどのAPIで出たかを特定できます。管理イベントは記録されますが、S3オブジェクトレベルやLambda実行などのデータイベントは別途有効化が必要です。
CloudWatch Logsはアプリ、OS、マネージドサービスのログ本体です。RDSのエラーログやスロークエリログもここに集まり、Logs Insightsでクエリできます。
「接続が確立しない」ならVPCフローログ、「接続はできるがエラーが返る」ならALBアクセスログとアプリログ、「権限で落ちている」ならCloudTrail。この対応を押さえるだけで探索時間が大きく減ります。
マネージドサービスで「見えない部分」の扱い方
ALBやNAT Gatewayのように、内部の挙動を直接観測できないコンポーネントがあります。この場合はメトリクスから内部状態を推定します。
NAT GatewayではErrorPortAllocationが0より大きければ、同一宛先への同時接続が多すぎてポートが枯渇しています。ALBのRejectedConnectionCountが増えていれば接続数の上限に達しており、TargetConnectionErrorCountが増えていればALBがターゲットへTCP接続を張れていません。中は見えないが、症状としては外に出る指標です。
マネージドサービスを使うときは、サービス固有のメトリクス一覧に一度目を通し、異常時にどの値が動くかを把握しておくと、障害時にゼロから探さずに済みます。
時刻とタイムゾーンを揃えてログを突き合わせる
ログ突合で最もよくあるつまずきが時刻のズレです。ALBアクセスログのtimeはUTCのISO8601、CloudTrailもUTC。一方でアプリがJSTでログを出していると、9時間ずれた箇所を探すことになります。
対処は単純で、アプリのログもUTCに統一するか、少なくともタイムゾーンオフセット付きで出力します。またALBアクセスログのtimeは「レスポンスを送信し終えた時刻」で、リクエストを受け付けた時刻はrequest_creation_timeです。処理に10秒かかったリクエストでは、両者が10秒ずれます。遅延を追うときはこの違いが結果を左右します。
Logs Insightsの時刻指定も、コンソールのタイムピッカーがブラウザのローカルタイムで解釈される点に注意してください。
ロードバランサとネットワークのエラーを切り分ける
Webシステムで最も頻度が高いのがALB周りのエラーです。ステータスコードと数個のログ列で機械的に原因を絞れるので、型を覚える価値が大きい領域です。
502・503・504はそれぞれ別の問題を指している
3つとも「ゲートウェイのエラー」ですが、意味はまったく違います。502はターゲットに接続はできたものの、返ってきたレスポンスが不正か、レスポンス途中で接続が切れた状態で、原因はALB〜ターゲット間にあります。503は正常なターゲットが1つもない、またはALB側のキャパシティ不足で、ターゲットグループの健全性の問題です。504はターゲットがアイドルタイムアウト(デフォルト60秒)以内にレスポンスを返さなかったケースで、ターゲットの処理時間の問題になります。
ログ上の見分け方も明確です。504ではtarget_processing_timeがアイドルタイムアウト値に張り付きます。502では多くの場合target_status_codeが-になり、ターゲットが有効なHTTPレスポンスを返していないことを表します。503でターゲットが1つも登録されていなければ、target_processing_timeは-1です。
ALBの502が出たときに最初に見るログの列
502はALBエラーの中でも原因が分かれやすく、ここを機械的に処理できるかで対応時間が変わります。見る順番は次の通りです。
target_status_codeが数値か-か。数値(例:502)ならアプリ自身が502を返しているので、調査先はアプリ側です。-ならALB〜ターゲット間の問題です。error_reason列。ClientDisconnected、TLSHandshakeFailureなどが入っていれば、それが答えに直結します。target_processing_time。極端に小さい値(0.000〜0.002秒程度)で502なら、接続直後に切られています。Keep-Aliveの競合か、プロセスの再起動が疑われます。- 発生が一様か、デプロイやスケールインのタイミングに集中しているか。
Keep-Aliveの競合は、ALBのアイドルタイムアウトよりアプリ側のKeep-Aliveタイムアウトが短いときに起きます。ALBが「まだ生きている」と思って再利用した接続を、アプリ側が先に閉じてしまうためです。原則としてアプリ側のKeep-Aliveタイムアウト > ALBのアイドルタイムアウトになるよう設定します(例:ALB 60秒に対しアプリ側70秒)。
原因パターンごとの確認方法と対処は、ALBの502 Bad Gatewayの原因4つと切り分け手順で手順ベースにまとめています。
名前解決とセキュリティグループで止まっているケース
そもそも接続が確立しない場合は、HTTPより下のレイヤを順に確認します。
# 1. 名前解決は通るか、返るIPは想定どおりか
dig +short api.example.com
# 2. TCPは到達するか(ポートまで指定)
nc -zv api.example.com 443
# 3. TLSハンドシェイクは成立するか、証明書は正しいか
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject
2で止まるならセキュリティグループ、ネットワークACL、ルートテーブルのいずれかです。VPCフローログで該当IP・ポートを検索し、REJECTが記録されていればパケットはENIまで届いています。レコードが存在しない場合は、ルーティングで到達していません。
セキュリティグループはステートフルなので戻り通信を個別に許可する必要はありません。一方ネットワークACLはステートレスなので、戻り用のエフェメラルポート(1024〜65535)をアウトバウンドで許可していないと通信が片道で終わります。NACLを独自に絞っている環境では見落としやすい箇所です。
EC2・コンテナ・デプロイ時に起きる不具合
インフラ構成が正しくても、ターゲット側のライフサイクル管理が甘いとエラーが出ます。デプロイやスケーリングのタイミングで出るエラーは、設定値の組み合わせで決まる部分が大きい領域です。
ヘルスチェックが落ちる理由とチェック設定の見直し
ターゲットがUnhealthyになる理由は、大きく「本当に落ちている」「起動が間に合っていない」「ヘルスチェックの設定が実態に合っていない」の3つに分かれます。
まずターゲットグループの状態理由を確認します。
aws elbv2 describe-target-health \
--target-group-arn <TARGET_GROUP_ARN> \
--query 'TargetHealthDescriptions[].{Id:Target.Id,State:TargetHealth.State,Reason:TargetHealth.Reason,Desc:TargetHealth.Description}' \
--output table
Target.ResponseCodeMismatchならパスは通っているがステータスコードが期待値と違う、Target.Timeoutなら時間内に応答していない、Target.FailedHealthChecksは応答そのものが失敗、Elb.InitialHealthCheckingは起動直後の初回判定中です。
設定面で見直したいのは、ヘルスチェックのパスにDBアクセスを含めていないかです。含めると、DBが一瞬詰まっただけで全ターゲットが同時にUnhealthyになり、503を引き起こします。依存先の健全性を確認するディープチェックと、プロセスの生存だけを見るシャローチェックは分けてください。ALBのヘルスチェックにはシャローチェックを割り当て、依存先の異常は別途メトリクスで監視します。
起動が間に合わないケースでは、ECSならヘルスチェック猶予期間(healthCheckGracePeriodSeconds)、EC2 Auto Scalingならヘルスチェックの猶予期間を、アプリの実起動時間より長く設定します。JVMのようにウォームアップに時間がかかるランタイムでは、ここが短すぎると起動とUnhealthy判定を繰り返す無限ループに陥ります。
デプロイ中だけエラーが出るときの接続の切れ方
デプロイ時の瞬間的なエラーは、ほぼ接続の終了処理に起因します。ALBがターゲットを登録解除するとdraining状態になり、新規リクエストの振り分けは止まりますが、処理中のリクエストは登録解除の遅延(デフォルト300秒)まで待たれます。ここでアプリがSIGTERMを受けて即座にプロセスを落とすと、処理中のリクエストが切断されて502になります。
必要なのは次の3点です。
- アプリが
SIGTERMを受けたら、新規受付を停止しつつ処理中のリクエストを完了させる(グレースフルシャットダウン)。 - ECSなら
stopTimeout、EC2ならシャットダウンスクリプトの猶予を、最長リクエスト処理時間より長くする。 - 登録解除の遅延 ≧ アプリのグレースフルシャットダウン時間、という大小関係を守る。
コンテナ起動直後にリクエストを受けてエラーになるパターンもあります。ヘルスチェックは通るものの、アプリ内部のキャッシュやコネクションプールが未初期化というケースです。起動時にプールを事前確立しておけば解消します。
スケールアウトしてもさばけない場合に見る指標
台数を増やしても改善しないのは、ボトルネックがアプリサーバの外にあるからです。ALBのキューについては、SurgeQueueLength(CLB)や、ALBではTargetResponseTimeの悪化とRequestCountの頭打ちが同時に起きていないかを見ます。DBの同時実行では、接続数が上限に達していれば台数を増やすほど接続待ちが悪化するので、アプリ台数 × プールサイズがDBのmax_connectionsを超えていないか計算します。外部APIやSaaSのレート制限は429やスロットリングのエラー率、ロック競合はDBの行ロックやアプリ側の分散ロック待ちを確認します。
共有リソースが詰まっているときの水平スケールは、待ち行列を長くするだけで状況を悪化させます。台数を増やす前に、各リクエストが何を待っているかを特定してください。
インスタンスの再起動・リタイアに備える
EC2では基盤ハードウェアの劣化により、インスタンスの再起動やリタイアがAWSから予定されることがあります。障害ではなく通知された計画事象なので、受け取れる体制を作ることが対策になります。
EventBridgeでAWS Healthのイベント(AWS Health Event)を購読し、Slackやチケットに流す仕組みがあれば、メールの見落としを防げます。StatusCheckFailed_System(基盤側の障害)とStatusCheckFailed_Instance(インスタンス内部の問題)は意味が違い、前者はインスタンスの停止・起動で別のハードウェアに移動することで復旧する場合があります。
恒久的な対策は、単一インスタンスに依存しない構成です。Auto Scalingグループで最小台数2以上、複数AZに分散し、状態をインスタンス内に持たない(セッションはElastiCacheやDynamoDB、ファイルはS3やEFS)設計に寄せていくと、個々のインスタンスの寿命を気にしなくてよくなります。
RDS・Auroraの接続と性能のトラブル
データベースは、トラブル時に最も状態が見えにくいレイヤです。マネージドサービスでOSに入れない分、メトリクスと内部ビューから状態を読む力が求められます。
接続数が枯渇するまでの流れとコネクションプールの設計
「接続できません」というエラーは、多くの場合サーバ側の接続上限に達した結果です。RDSのmax_connectionsはデフォルトでインスタンスクラスのメモリから計算される式が設定されており(PostgreSQLならLEAST({DBInstanceClassMemory/9531392},5000)のような形)、インスタンスを小さくすると上限も下がります。現在値と使用状況は次で確認できます。
-- PostgreSQL:上限と現在の接続内訳
SHOW max_connections;
SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY 2 DESC;
-- idle in transaction が長時間残っていないか
SELECT pid, usename, application_name, state,
now() - state_change AS duration, left(query, 80) AS query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY duration DESC
LIMIT 20;
-- MySQL:上限と現在値
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
枯渇の典型的な流れは、(1) どこかで処理が遅くなる → (2) リクエストが滞留する → (3) アプリがプールから接続を取り続ける → (4) 上限到達です。接続枯渇は原因ではなく結果であることが多いため、「なぜ遅くなったか」を別途追う必要があります。
設計上は、アプリの最大台数 × プール最大サイズ + 運用・バッチ用の余裕 < max_connections を必ず満たします。オートスケールするアプリやLambdaでは台数が読めないので、RDS ProxyやPgBouncerのような接続プロキシを挟み、DB側から見た接続数を固定するのが安全です。特にLambdaは同時実行数がそのまま接続数になりうるため、プロキシなしの運用はリスクが高くなります。
idle in transaction(PostgreSQL)はトランザクションを開いたままアプリが放置している状態です。接続を占有するだけでなく、後述のVACUUMも阻害します。idle_in_transaction_session_timeoutを設定して強制終了させる運用が有効です。
遅いクエリは待機イベントと実行計画から原因を分ける
「遅い」の原因は、待たされている(I/O、ロック、ラッチ)か、無駄な仕事をしている(不適切な実行計画)かに分かれます。対処がまったく違うので、最初に切り分けます。
待機の把握にはPerformance Insightsが最短です。DBLoad(平均アクティブセッション数、AAS)がvCPU数を超えていれば、DBは過負荷です。内訳を見て、CPUが支配的なら計算・ソート・関数処理、IO:DataFileRead(PostgreSQL)やio/table/sql/handler(MySQL)が支配的ならバッファヒットしていない読み込み、Lock:*やsynch/mutex系ならロック競合、と当たりがつきます。
Performance Insightsを使わない場合でも、SQLで同等の情報が取れます。
-- PostgreSQL:現在の待機状況
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE state = 'active'
GROUP BY 1, 2
ORDER BY 3 DESC;
-- ブロックしている側のPIDを特定
SELECT pid, pg_blocking_pids(pid) AS blocked_by, left(query, 100)
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;
次に、重いSQLを累積で特定します。PostgreSQLならpg_stat_statements、MySQLならsys.statement_analysisやスロークエリログです。
-- PostgreSQL:総実行時間が大きい順
SELECT calls, round(total_exec_time::numeric, 1) AS total_ms,
round(mean_exec_time::numeric, 2) AS mean_ms,
rows, left(query, 120) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
ここではmean_exec_time(1回あたりの遅さ)に加えてtotal_exec_time(総負荷)を見ます。1回5msでも毎秒1万回呼ばれるクエリは、1回3秒のクエリより影響が大きいことがあります。
最後に実行計画です。PostgreSQLでは必ずBUFFERSを付けます。
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT ...;
見るポイントは、actual rowsとestimated rows(rows=)の乖離です。桁違いにずれていれば統計情報が古いか、相関のある条件をオプティマイザが独立と仮定して見積もりを誤っています。前者はANALYZE、後者は拡張統計(PostgreSQLのCREATE STATISTICS)やクエリの書き換えで改善します。Buffers: shared read=が大きければ共有バッファに乗っておらず、EBSからの読み込みが発生しています。
PostgreSQLとMySQLで挙動が違うところ(VACUUMやロックの扱い)
同じRDSでも、エンジンが違えば運用上の注意点は別物です。
追記型とUNDOの違いがまず大きい。PostgreSQLはMVCCを追記型で実装しており、UPDATE/DELETEは旧バージョンの行(デッドタプル)をテーブル内に残します。これを回収するのがVACUUMです。長時間のトランザクションやレプリカのhot_standby_feedback、放置されたレプリケーションスロットがあると、「まだ誰かが古いバージョンを見るかもしれない」と判断されて回収できません。結果としてテーブルが肥大化(ブロート)し、全体が遅くなります。
-- PostgreSQL:デッドタプルと最終VACUUM時刻
SELECT relname, n_live_tup, n_dead_tup,
round(n_dead_tup * 100.0 / NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
デッドタプル比率が20%を超え、last_autovacuumが更新されていないテーブルは要注意です。さらにトランザクションID周回(XID wraparound)が迫ると、PostgreSQLは書き込みを拒否する保護モードに入ります。age(datfrozenxid)を監視対象に入れておくべき理由がここにあります。
一方MySQL(InnoDB)は旧バージョンをUNDOログに持ち、パージスレッドが回収します。長時間トランザクションがあるとヒストリリスト長が伸び、UNDO表領域が膨らみます。SHOW ENGINE INNODB STATUSのHistory list lengthが継続的に増え続けていないかが指標です。
ロックの待ち方も異なります。InnoDBにはinnodb_lock_wait_timeout(デフォルト50秒)があり、待ちきれなければ自動でエラーになります。PostgreSQLはlock_timeoutがデフォルト0(無制限)なので、設定しないと待ち続けます。DDLを本番に流すときは、PostgreSQLではlock_timeoutを短く設定してから実行してください。
SET lock_timeout = '5s';
ALTER TABLE ... ;
PostgreSQLのALTER TABLEはACCESS EXCLUSIVEロックを取るため、待っている間に後続の参照クエリまで全部ブロックされます。「ロックが取れなければ諦めて後で再試行する」運用のほうが、サービス停止よりはるかに軽傷で済みます。
Auroraはストレージ層が分離されており、チェックポイントやWALの書き出しの挙動がコミュニティ版と異なります。レプリカラグもストレージ共有によるため通常は非常に小さい一方、AuroraReplicaLagが跳ねるときは長時間クエリやDDLなど別の要因を疑います。メトリクスの意味がエンジンごとに違う点は、必ず公式ドキュメントで確認してください。
この記事のテーマをさらに学べる本
EC2・IAM・RDSの運用やバックアップ、セキュリティ統制を体系立てて解説しており、運用設計の土台作りに使えます。
楽天ブックスで詳しく見るフェイルオーバー後に復旧しないときの接続先キャッシュ
Multi-AZのフェイルオーバー自体は完了しているのに、アプリだけが旧ライタに接続しようとして失敗し続ける。この場合はDNSキャッシュが原因であることがほとんどです。
RDS/Auroraのエンドポイントは短いTTL(RDSのエンドポイントは5秒程度)で切り替わりますが、JVMは歴史的にDNS解決結果を長時間、設定によっては無期限にキャッシュする挙動があります。Javaアプリではnetworkaddress.cache.ttlを明示的に短く(例:5〜30秒)設定してください。OS側でnscdやsystemd-resolvedがキャッシュしている場合も同様です。
アプリ側の対策は次のいずれかです。
- RDS Proxyを挟み、アプリは常にProxyのエンドポイントに接続する。フェイルオーバー時の切り替えをProxy側に任せられます。
- AWS Advanced JDBC Wrapperのようなフェイルオーバー対応ドライバを使い、クラスタのトポロジを認識させる。
- コネクションプールに接続の生存確認(validation query、
maxLifetime)を設定し、壊れた接続をプールに残さない。
フェイルオーバー直後は使えない接続がプールに残るため、アプリ側で一定回数のリトライを実装していないと、復旧しているのにエラーが続くように見えます。「切り替えは数十秒で終わるが、アプリの復帰には数分かかる」場合、原因はほぼアプリ側の接続管理です。
権限と設定ミスによる「動かない」を素早く潰す
この領域のトラブルは、原因さえ分かれば対処は一瞬です。逆に言えば、特定に時間をかけないことがすべてです。
AccessDeniedの原因をCloudTrailから逆算する
AccessDeniedが出たら、まずCloudTrailで該当イベントを引きます。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=GetObject \
--start-time 2026-10-15T00:00:00Z \
--max-results 20 \
--query 'Events[].CloudTrailEvent' --output text | jq -r '.errorCode, .errorMessage, .userIdentity.arn'
確認すべきは3点です。
- 誰が(
userIdentity.arn)。想定したロールで実行されているか。EC2のインスタンスプロファイル、ECSのタスクロールと実行ロールの取り違えは頻出です。 - 何を(
eventName・resources)。エラーになったAPIが想定と違うことがあります。S3への書き込みでKMS暗号化が有効なら、s3:PutObjectに加えてkms:GenerateDataKeyが必要です。 - エラーメッセージの文面。明示的なDenyなのか、暗黙のDeny(許可が存在しない)なのかで対処が変わります。SCPによる拒否の場合、メッセージにその旨が含まれることがあります。
AWSの権限評価は、SCP → リソースベースポリシー → アイデンティティベースポリシー → パーミッションバウンダリ → セッションポリシーのすべてを通過する必要があり、どこか1つでも明示的Denyがあれば拒否されます。IAMポリシーシミュレータは便利ですが、SCPやVPCエンドポイントポリシーを再現しない範囲があるため、「シミュレータでは通るのに実環境で落ちる」場合はそれらを疑います。
セキュリティグループ・ネットワークACL・ルートテーブルの確認順序
ネットワーク系の設定確認は、順番を固定すると速くなります。VPC Reachability Analyzerが使える環境なら、まずそれを実行して経路上のどこで落ちるかを機械判定させるのが最短です。手動で追う場合の順序は次の通りです。
- ルートテーブル。宛先への経路が存在するか。プライベートサブネットからインターネットへ出るならNAT Gatewayへの
0.0.0.0/0、VPCエンドポイント経由ならプレフィックスリストへのルート。 - セキュリティグループ(送信元側のアウトバウンド)。デフォルトは全許可ですが、絞っている環境では見落としがちです。
- セキュリティグループ(宛先側のインバウンド)。ポートとソース(CIDRかSGか)。SG間参照にしている場合、参照元のSGが正しいか。
- ネットワークACL。インバウンド・アウトバウンドの両方。ステートレスなので戻りのエフェメラルポートを忘れていないか。
- ホスト側のファイアウォール。OSのiptables/firewalld、コンテナのポートマッピング。
VPCフローログのaction列でREJECTを探せば、SGかNACLで弾かれていることは確定します。ただしフローログだけではどちらが原因か判別できないので、上の順序で設定を突き合わせます。
パラメータグループやタイムアウト値の不一致
設定を変更したのに反映されていない、という事象の多くはパラメータグループの適用タイミングです。RDSのパラメータにはdynamic(即時反映)とstatic(再起動が必要)があり、staticを変更すると状態がpending-rebootになります。
aws rds describe-db-instances --db-instance-identifier <ID> \
--query 'DBInstances[].DBParameterGroups'
ここでParameterApplyStatusがpending-rebootなら未反映です。Auroraではクラスタパラメータグループとインスタンスパラメータグループが別にあり、設定すべき場所を間違えるとエラーも警告もないまま無視されます。
もう一つはタイムアウト値の整合です。クライアント、ALB、アプリ、DBのそれぞれにタイムアウトがあり、不整合があると「どこで切れたか分からないエラー」になります。原則は次の通りです。
- アプリのリクエスト処理タイムアウト < ALBのアイドルタイムアウト(アプリ側で制御されたエラーを返す)
- ALBのアイドルタイムアウト < アプリのKeep-Aliveタイムアウト(502の回避)
- DBクエリのタイムアウト(
statement_timeoutなど)< アプリのリクエストタイムアウト - 登録解除の遅延 ≧ グレースフルシャットダウン時間 ≧ 最長リクエスト処理時間
この4つの不等式を構成図に書き添えておくだけで、再発する謎のエラーが大きく減ります。
監視とアラートを「気づける状態」に整える
障害対応の速さは、最終的には「どれだけ早く気づけるか」で決まります。ただし監視は増やせばよいものではなく、設計が必要です。
何を監視するかはSLOと復旧手順から決める
監視項目を決める基準は2つです。SLOに直結するか、つまり可用性、レイテンシ、エラー率といったユーザー影響を表す指標かどうか。これらは必ずアラート対象にします。もう一つは、鳴ったときに取るべき行動があるか。行動が決まっていないアラートは、通知ではなくダッシュボードに置く情報です。
ユーザー影響の指標として最低限押さえるのは、ALBのHTTPCode_ELB_5XX_CountとHTTPCode_Target_5XX_Count、TargetResponseTimeのp90/p99、HealthyHostCountです。5XXは前者がALB起因、後者がアプリ起因なので分けて監視します。レスポンスタイムをパーセンタイルで見るのは、平均が少数の極端に遅いリクエストを隠してしまうためです。
リソース指標(CPU、メモリ、ディスク)は将来の障害の予兆であり、ユーザー影響の指標とは別枠で扱います。RDSならFreeStorageSpace、FreeableMemory、DatabaseConnections、DBLoadあたりが予兆系の中心です。
アラートが鳴りすぎると誰も見なくなる
アラート疲れは、監視システムの機能不全そのものです。次の原則でノイズを削ります。
- 評価期間を適切に取る。1分間のスパイクで鳴らさず、「5分間のうち3回」(M out of N)のような条件にする。CloudWatchアラームでは評価期間とアラーム内のデータポイント数を分けて設定できます。
- 欠損データの扱いを明示する。デフォルトの
missingのままだと、メトリクスが途切れたときの挙動が意図とずれます。バッチ系のメトリクスではnotBreaching、死活監視ではbreachingが適切なことが多い。 - 複合アラームを使う。「CPUが高い AND レスポンスタイムが悪化」で鳴らす形は、CloudWatchの複合アラーム(Composite Alarm)で表現できます。
- 通知先を分ける。即時対応が必要なもの(電話・オンコール)と、翌営業日でよいもの(チャット・チケット)を分離します。
「深夜に鳴るが、誰も何もしない」アラートが1つでもあると、本当に重要なアラートへの反応速度が落ちます。定期的に棚卸しし、過去3か月で一度も対応アクションに結びつかなかったアラートは、閾値を見直すか廃止します。
バッチ時間帯だけ通知を止める方法と注意点
定期バッチでCPUやIOが跳ねると分かっている場合、その時間帯だけ通知を止めたくなります。CloudWatchアラーム自体に時間帯条件の機能はないため、アラームアクションを時間帯で無効化します(DisableAlarmActions/EnableAlarmActions)。
# バッチ開始前:通知だけ止める(評価は継続する)
aws cloudwatch disable-alarm-actions --alarm-names "rds-cpu-high"
# バッチ終了後:通知を戻す
aws cloudwatch enable-alarm-actions --alarm-names "rds-cpu-high"
アラームの評価自体は止まらないので履歴は残ります。注意点は、戻し忘れると監視が無効のままになることです。EventBridge Schedulerで開始・終了の両方を必ずペアでスケジュールし、「有効化されているはずのアラームが無効になっていないか」を別途チェックする仕組みまで含めて設計します。
そもそも閾値や評価条件の見直しで解決できるケースも多く、抑制は最後の手段です。自動化の手順と代替策は、CloudWatchアラームの抑制|バッチ時間帯だけ通知を止めるで整理しています。
ログベースの監視とメトリクス監視の使い分け
メトリクス監視は、数値の時系列を定義済みの閾値で判定するもので、低コスト・低遅延・長期保持に向きます。常時監視したい指標はこちらに寄せます。
ログベースの監視は、特定の文字列やパターンの出現を検知します。想定外のエラーメッセージ、特定のスタックトレース、監査上の操作など、事前に数値化できないものに向きます。CloudWatch Logsのメトリクスフィルタでログからメトリクスを生成すれば、両者を橋渡しできます。
aws logs put-metric-filter \
--log-group-name /aws/app/api \
--filter-name app-error-count \
--filter-pattern '?ERROR ?FATAL' \
--metric-transformations metricName=AppErrorCount,metricNamespace=App,metricValue=1,defaultValue=0
defaultValue=0を指定すると、エラーがない時間帯に0が記録されるため、欠損データ扱いにならずアラームが安定します。
調査用途ではCloudWatch Logs Insightsを使います。
fields @timestamp, @message
| filter @message like /AccessDenied/
| stats count() by bin(5m)
| sort @timestamp desc
CloudWatch Logsは取り込み量とストレージで課金されるため、DEBUGログを大量に送ると費用が膨らみます。保持期間を用途別に設定し(アプリログ30日、監査ログは長期など)、不要なログは出力段階で絞ってください。
運用負荷とコストから環境構成を見直す
トラブルが減らない原因が、構成そのものにあることもあります。運用で消耗している部分は、構成を変えたほうが早いケースが少なくありません。
AWSとVPSで変わる運用負荷と障害時の責任範囲
責任共有モデルの線引きは、構成の選び方でそのまま運用負荷に変わります。VPSは料金が定額で分かりやすい反面、OSのパッチ適用、ミドルウェアのバージョン管理、バックアップ、障害時の切り分けがすべて利用者側の責任です。AWSのマネージドサービスは、その一部をAWS側に預けられます。
RDSを使えば、バックアップ、パッチ、フェイルオーバーの仕組みはAWSが提供します。代わりにOSに入って調査することはできず、Performance Insightsや拡張モニタリングなど提供されたインターフェース経由で状態を見ます。手間が減る代わりに、見える範囲も変わるトレードオフです。
個人開発でコストを最優先するか、実務で使われる構成を学ぶかで選択は変わります。料金体系・運用負荷・障害時の責任範囲での比較は、VPSとAWSどっちを選ぶ?個人開発・学習・転職の目的別に違いを比較にまとめています。
運用を楽にする構成変更とマネージド化の判断基準
マネージド化すべきかは、次の問いで整理できます。その作業に差別化の価値があるか。自前でMySQLをチューニングすることが事業価値を生まないなら、RDSに寄せたほうが合理的です。障害時に自力で復旧できるか。深夜に自前構築のクラスタをフェイルオーバーさせられる人員がいないなら、マネージドのほうが可用性は高くなります。制約を受け入れられるか。マネージドサービスは拡張機能やパラメータに制限があるので、使いたい機能がサポート対象かを事前に確認します。最後にコストの差を運用工数で回収できるか。マネージドのほうが単価は高いことが多いですが、人件費と障害コストを含めて比較します。
効果が大きい構成変更の例を挙げると、EC2上のcronをEventBridge Scheduler + ECSタスクに移してサーバの寿命とジョブの実行を切り離す、インスタンス内のファイル保存をS3に移してスケールアウトとバックアップを同時に解決する、固定サーバをAuto Scalingグループ化してインスタンス障害を自動復旧にする、あたりです。
コスト増加の原因を特定する手順
請求額が跳ねたときの調査にも型があります。
- Cost Explorerでサービス別に分解する。日次粒度で、どのサービスがいつから増えたかを特定します。
- 使用タイプ(Usage Type)で分解する。同じEC2でも、インスタンス時間なのかデータ転送なのかで原因がまったく違います。
DataTransfer-Out-Bytes、NatGateway-Bytes、VPC-Peeringなどが増えていればネットワーク設計の問題です。 - リソース単位で特定する。コスト配分タグを有効化していれば、タグ別に分解できます。有効化していないとこの段階で手が止まるので、早い段階での整備を推奨します。
増加原因として頻出するのは、NAT Gateway経由のデータ処理料(S3やDynamoDBへの通信をVPCエンドポイント経由にすれば削減できる)、CloudWatch Logsの取り込み量、使われていないEBSスナップショットやElastic IP、開発環境の停止忘れです。
AWS Cost Anomaly Detectionで異常検知を有効にしておくと、月末に気づく事態を防げます。料金体系は変更されることがあるため、最新の単価や無料枠の条件は必ず公式の料金ページで確認してください。
AWS運用スキルを身につける進め方
ここまでの内容は、一度に身につくものではありません。実務で使える順番があります。
最初に固めるべき領域の順番
優先順位は、トラブル対応で足を引っ張られる頻度の高い順になります。
- IAMとネットワーク(VPC)。ほぼすべてのトラブルがこの2つのどちらかに触れます。権限評価のロジックと、SG/NACL/ルートテーブルの役割分担を手で設定して理解するまでやります。
- ログとメトリクスの所在。CloudWatch Logs、ALBアクセスログ、VPCフローログ、CloudTrailを実際に有効化し、Logs Insightsでクエリを書けるようにします。
- コンピュート層のライフサイクル。EC2のAuto Scaling、ECSのタスク配置とデプロイ、ヘルスチェックとドレインの挙動。
- データベース。接続管理、実行計画、待機イベント。深さが無限にあるので、まず「遅いクエリを特定して実行計画を読む」までを目標にします。
- IaCと自動化。TerraformやCloudFormationで環境を再現できるようにすると、検証が速くなります。
1と2ができていないと3以降の調査が始められません。逆に1と2が固まっていれば、知らないサービスのトラブルでも「ログを探す→経路を確認する」という同じ型で対応できます。
障害を自分で再現できる検証環境をつくる
運用スキルが伸びる最大の要因は、壊して直した回数です。本番では試せないので、検証環境で意図的に壊します。再現しておく価値が高いシナリオを挙げます。
- ALBのアイドルタイムアウトより短いKeep-Aliveをアプリに設定し、502を発生させる
- ターゲットを全部Unhealthyにして503を確認し、
HealthyHostCountのアラームが鳴ることを確かめる - RDSの
max_connectionsを小さくして接続枯渇を起こし、アプリ側のエラーメッセージを確認する - RDSのフェイルオーバーを手動実行し、アプリが何秒で復帰するかを計測する
- セキュリティグループのルールを1行消して、VPCフローログにREJECTが出ることを確認する
RDSの手動フェイルオーバーは次のコマンドで実行できます。
aws rds reboot-db-instance --db-instance-identifier <ID> --force-failover
Auroraの場合は次のようになります。
aws rds failover-db-cluster --db-cluster-identifier <CLUSTER_ID>
より体系的に障害を注入したい場合は、AWS Fault Injection Service(FIS)でインスタンスの停止、ネットワークの遅延、APIのエラー注入などを安全に実行できます。
検証環境のコストが心配なら、使うときだけ起動する運用にします。Auto Scalingグループの希望台数を0にする、RDSを停止する(停止は最大7日で自動起動する点に注意)、EventBridge Schedulerで夜間停止を自動化する、といった方法があります。
資格学習と現場の運用知識をどうつなげるか
資格学習は「AWSにどんなサービスがあり、どういう場面で使うか」の地図を作るのに有効です。一方、資格のテキストに「502が出たときに何を見るか」は書かれていません。両者をつなぐには、学習中に次の変換を意識します。サービス名を覚えたら、そのサービスが異常時にどのメトリクスを出すかを一緒に調べる。構成パターンを学んだら、その構成で起きうる障害と切り分け方を書き出す。ベストプラクティスとされる設定については、守らないと何が起きるかを調べる。
マルチAZ構成を学んだら、「フェイルオーバーにかかる時間」「その間アプリはどんなエラーを受け取るか」「DNSキャッシュの問題」まで追う、という具合です。この追い方をすると、資格の知識がそのまま障害対応の引き出しになります。
現場で遭遇したトラブルを起点に資格範囲を埋めていく進め方も有効です。実際に困った箇所は記憶に残るため、学習効率が高くなります。
障害対応の記録を資産として残す
同じ障害を2回調べるのは純粋な損失です。対応が終わったら、次の項目で記録を残します。
- 事象。いつ、何が、どの範囲で起きたか。数値と時刻を文字で書く
- 検知。どうやって気づいたか。ユーザー報告で気づいたなら、それ自体が監視の不備として改善対象です
- 切り分けの経路。何を見て、何を否定し、どこで原因にたどり着いたか。外れた仮説も残せば、次回同じ道を通らずに済みます
- 原因。直接原因と、それを許した背景要因(レビュー不足、設定値の不整合、監視の欠落)
- 恒久対処。実施済みのものと、チケット化したもの
- 判断基準の更新。この障害を踏まえて、閾値や手順書をどう変えたか
この記録が10件も溜まると、冒頭の「症状→見るログ」の早見表が自組織の実態に合った形に育ちます。そこまで来れば、障害対応は個人の経験則からチームの再現可能な手順に変わります。
AWS運用のトラブル対応の速さは、覚える知識の量より切り分けの型を持っているかで決まります。症状からレイヤを絞り、該当するログを見て、上流と下流で突き合わせる。この流れを崩さなければ、初見のサービスでも調査は進みます。個別の事象については、本ページから各記事へ進んでください。
このテーマの記事一覧(3本)
- AWSトラブル解決CloudWatch
アラーム の 抑制 | バッチ 時間 帯 だけ 通知 を 止める バッチ処理中のCPU高騰でCloudWatchアラームが鳴り続ける場合、アラームアクションを時間帯で無効化すれば通知だけを止められます。EventBridge Schedulerでの自動化、評価は止まらない点、閾値見直しなどの代替策も解説します。
- AWSトラブル解決ALB
の 502 を error_reason で 特定 し 原因 別に 直す ALBの502は、ターゲットに接続できたが有効なHTTPレスポンスを得られなかった状態です。アクセスログのerror_reasonで原因を絞り、Keep-Aliveの不一致や不正ヘッダ、ECS・EKSのデプロイ時の502を切り分けます。
- サーバー・ドメインAWS
と VPS の 違い を 7 軸 で 比較 | 個人 開発 と 実務 学習 の 選び 分け AWSとVPSは予算感が近くても、障害時の責任分界点や拡張性、運用の手間が違います。料金体系から移行コストまで7軸で比較し、費用と作業量の見積もり方、個人開発と実務学習での選び分け、VPSからAWSへ移る際の注意点も扱います。