システムに障害や遅延が起きた時、原因を調べるために監視・トレーシングの仕組みを入れる。これ自体は当たり前の話ですが、実は監視の入れ方を間違えると「監視を強化したせいでシステムがさらに遅くなる」という本末転倒が起こり得ます。
この記事では、AWSでの監視設計における「非侵襲性(既存の処理に影響を与えないこと)」という視点を軸に、X-Ray・CloudWatch・VPCフローログの実践パターンを整理します。
なぜ「監視のための監視」が処理を遅くしてはいけないのか
典型的な例で考えてみます。
API Gatewayとオンプレミスの自社APIを連携させたシステムで、レスポンス遅延が悪化している。原因を調べたいが、調査のための仕組みがさらに遅延を生んでは意味がない。
ここで重要なのは、「監視データの収集・送信」という処理を、本来のリクエスト処理のクリティカルパスから外すという発想です。
悪い例:
リクエスト受信 → 処理 → 監視データを同期送信 → レスポンス
↑
ここが遅延要因に追加される
良い例:
リクエスト受信 → 処理 → レスポンス
↓ (非同期・別プロセス)
監視データ収集・送信
この原則を踏まえて、具体的なサービスの使い方を見ていきます。
AWS X-Ray: 分散トレーシングの非同期設計
AWS X-Rayは、リクエストがシステム内のどこを通って、どこで時間がかかっているかを可視化する分散トレーシングサービスです。
X-Rayデーモンが担う役割
X-Rayの実装で重要なのが「X-Rayデーモン」の存在です。
アプリケーション → (UDP送信) → X-Rayデーモン(ローカルプロセス)
↓ バッチ処理
↓ 非同期アップロード
X-Rayサービス
デーモンはアプリケーションとは別プロセスとして動作し、UDP経由でセグメントデータを受け取ります。受け取ったデータはローカルでバッファされ、ある程度まとまった単位でバッチとしてX-Rayサービスへ非同期に送信されます。
この仕組みにより、アプリケーション側は「UDPでデータを投げるだけ」で済み、X-Rayサービスへの実際のHTTP通信(ネットワーク遅延やX-Ray側の応答待ち)がリクエスト処理のクリティカルパスに影響することはありません。
避けるべきアンチパターン: 同期アップロード
もしリクエストごとに同期でX-Rayへセグメントをアップロードすると、そのHTTP通信自体がリクエスト処理の一部になってしまいます。ネットワークが不安定だったり、X-Rayサービスの応答が遅れたりすると、本来のAPIレスポンス時間がそのまま延びてしまいます。「遅延を調査するための仕組みが、遅延の原因になる」という本末転倒が起きるわけです。
セグメントとサブセグメント
X-Rayのトレースデータは階層構造を持ちます。
- セグメント: 1つのサービス(例: オンプレミスAPI)が処理した内容全体
- サブセグメント: その中の詳細な処理単位(例: DB呼び出し、外部API呼び出し)
これにより「どのサービスの、どの処理で」遅延が発生しているかまで細かく特定できます。
CloudWatchエージェント: サーバーサイドの独立監視
X-Rayがリクエストの「流れ」を追うのに対して、CloudWatchエージェントはサーバー自体の「状態」を監視します。
CloudWatchエージェントは、アプリケーションプロセスとは完全に独立したプロセスとしてサーバー上で動作し、CPU使用率・メモリ消費量・ディスクI/O・ネットワーク遅延といったシステムリソース情報を継続的に収集します。
Webサーバー
├─ アプリケーションプロセス(リクエスト処理)
└─ CloudWatchエージェント(独立プロセス)
↓ 非同期でメトリクス送信
CloudWatch
この収集プロセスはアプリケーションのリクエストフローとは完全に切り離されているため、リクエスト遅延には一切影響しません。X-Rayが「エンドツーエンドの処理時間」を可視化するのに対し、CloudWatchエージェントは「なぜそのサーバーで処理に時間がかかっているのか」というサーバー内部のボトルネック(CPU逼迫、メモリ不足など)を特定するのに役立ちます。
使い分けのまとめ
| 手法 | 何が分かるか | 侵襲性 |
|---|---|---|
| X-Ray(デーモン経由) | リクエストのエンドツーエンド処理フロー、各コンポーネントの処理時間 | 低い(非同期送信) |
| CloudWatchエージェント | サーバーのリソース状況(CPU/メモリ/ディスク/ネットワーク) | 低い(独立プロセス) |
| リクエストへのログ埋め込み | – | 高い(ペイロード増加、互換性への影響) |
| 同期的なメトリクス送信 | – | 高い(クリティカルパスに乗る) |
VPCフローログによるリアルタイム脅威検知
次に、ネットワークレベルの監視です。「拒否リスト(deny list)にあるIPアドレスからのアクセスをリアルタイムで検知したい」というような要件でよく使われるのが、VPCフローログとCloudWatchの組み合わせです。
基本構成
VPC Flow Logs
→ CloudWatch Logs ロググループ
→ メトリクスフィルタ(拒否リストIPのパターンにマッチする行を抽出)
→ CloudWatch Alarm(閾値超過で発火)
→ SNS(通知)
resource "aws_flow_log" "vpc" {
vpc_id = aws_vpc.main.id
log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn
log_destination_type = "cloud-watch-logs"
traffic_type = "ACCEPT"
}
resource "aws_cloudwatch_log_metric_filter" "denylist_ip" {
name = "denylist-ip-access"
log_group_name = aws_cloudwatch_log_group.vpc_flow_logs.name
pattern = "[..., srcaddr=\"192.0.2.0\" || srcaddr=\"198.51.100.0\", ...]"
metric_transformation {
name = "DenylistIPAccessCount"
namespace = "VPCFlowLogs"
value = "1"
}
}
resource "aws_cloudwatch_metric_alarm" "denylist_alert" {
alarm_name = "denylist-ip-detected"
metric_name = "DenylistIPAccessCount"
namespace = "VPCFlowLogs"
statistic = "Sum"
period = 60
evaluation_periods = 1
threshold = 1
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_actions = [aws_sns_topic.security_alert.arn]
}
ACCEPTのみを記録する理由
VPCフローログはtraffic_typeでACCEPT/REJECT/ALLを選べます。ここで押さえておきたいのは、ACCEPT/REJECTは「そのIPが安全かどうか」ではなく、セキュリティグループやNACLがその通信を許可したか拒否したかを表しているという点です。
「拒否リスト(deny list)のIP」とは、脅威インテリジェンス的に危険だと分かっているIPのリストであり、セキュリティグループのルールとは別物です。たとえば「80番ポートは0.0.0.0/0から許可」というSGルールになっていれば、危険なIPからのアクセスであってもSG的には普通にACCEPTされ、中まで到達してしまいます。
危険なIP(拒否リスト掲載)が80番ポートにアクセス
→ SGルール上は「80番は誰でも許可」なのでACCEPTされる
→ VPCフローログに action=ACCEPT として記録される
→ このACCEPTログのsrcaddrを拒否リストと照合すれば検知できる
つまり、拒否リストIPからのアクセスを見つけるには、「実際に中まで到達してしまった通信(ACCEPT)」の中から送信元IPを拒否リストと照合する必要があり、この検知にはACCEPTログで十分です。
一方でREJECTログは、SGルール違反によってそもそも通信が弾かれたことを示すログであり、拒否リストのIPかどうかとは無関係に発生します。SGで既に弾かれている以上、実害としては小さく、拒否リストとの照合対象にする実益も薄いログです。それにもかかわらず、ポートスキャンや攻撃的な接続試行によってREJECTログは大量に記録されがちで、ALLを選ぶとこの分だけコストが不必要に膨らみます。「需要が高まっている(攻撃的なトラフィックが疑われる)」ような状況ほどREJECTログの量は跳ね上がりやすいため、ACCEPT限定は検知の目的にも、コスト効率の観点でも理にかなった選択です。
アラームの評価条件
period = 300 # 5分
evaluation_periods = 1 # 1データポイントで即発火
evaluation_periodsを大きくする(連続して条件を満たした場合のみ発火させる)と、より確実な検知にはなりますが通知が遅れます。「疑わしいIPからのアクセスが1回でもあれば即座に知りたい」という要件では、最小構成(1データポイントで即発火)が適切です。
CloudWatch Logsのコストと、S3 + Athenaとの使い分け
CloudWatch Logsは高コストになりやすい
VPCフローログは、VPC内の全ENIを通過する通信のフローごとにレコードが生成されます。トラフィック量の多い本番環境では、1日に数百万〜数千万レコードに達することもあり、ログ量(≒コスト)は簡単に膨らみます。
コストを抑える工夫:
-
traffic_type = "ACCEPT"のみに絞る - ロググループの
retention_in_daysを明示設定する(未設定だと無期限保存でコストが積み上がり続ける)
resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
name = "/vpc/flow-logs/example"
retention_in_days = 30
}
リアルタイム性が不要なら S3 + Athena
| CloudWatch Logs | Athena(S3) | |
|---|---|---|
| 課金基準 | 取り込み量+保存量(継続的) | クエリ実行時のスキャン量(実行都度) |
| リアルタイム性 | 高い(メトリクスフィルタ+Alarmと直結) | 低い(バッチ的な調査向け) |
| 向いている用途 | 常時監視・即時アラート | 過去ログの調査・監査・レポート作成 |
事後調査や監査目的であれば、S3への保存 + Athenaでのクエリの方がコスト効率が良いケースが多いです。ただしAthenaも設計次第でコストが膨らむため、以下の最適化が重要になります。
1. Parquet形式(列指向)への変換
テキスト形式では1行全体を読み込む必要がありますが、Parquetでは必要な列だけを読み込めるため、スキャン量が大幅に削減されます。
2. パーティショニング
s3://flow-logs/year=2026/month=07/day=26/
WHERE句で対象パーティションだけに絞り込めるようにしておくと、該当しない日付のデータはスキャン対象から除外され、コストが大きく下がります。
3. 圧縮
スキャンされるバイト数自体を減らせるため、そのままコスト削減に直結します。
VPCフローログはdestination_optionsでParquet出力とパーティション分割を直接指定できるため、S3に流す時点で最適化しておくのが最も手間の少ない方法です。
resource "aws_flow_log" "vpc" {
destination_options {
file_format = "parquet"
per_hour_partition = true
}
}
まとめ
非侵襲的な監視設計の要点は、次の3つに集約できます。
- 監視データの収集・送信は、本来の処理から切り離して非同期に行う(X-Rayデーモン、CloudWatchエージェント)
- リアルタイム性が必要な検知は CloudWatch Logs + メトリクスフィルタ + Alarm、事後分析は S3 + Athena と用途で使い分ける
- コスト効率は「対象を絞る」ことで実現する(ACCEPTのみ記録、Parquet化、パーティショニング、適切なretention設定)
次回の記事では、この監視の仕組みをさらに一歩進めて、「設定そのものが正しい状態を保っているか」を継続的にチェックするAWS Configについて整理します。