VPCフローログ1年半運用で月15万円の請求に気づいた話
セキュリティ監査で導入したVPCフローログ、実は垂れ流しで月15万円の請求に。Athena×Grafanaで本番運用して気づいた費用削減と実装の落とし穴を共有します。
VPCフローログ、導入したら放置してた話
うちのチームがVPCフローログを有効化したのは2024年の中ごろ。セキュリティ監査が入るタイミングで「ネットワークトラフィックの可視化が必要」って言われたんですよね。最初は「じゃあ有効化しよう」くらいの軽い気持ちで、CloudWatch Logsに放り込んでたんです。
そしたら3ヶ月後、請求を見てぶっ倒れました。VPCフローログだけで月15万円。本来なら監視と分析に使う予定だったのに、ただ垂れ流されてるだけ。これはヤバい。
そこからチームで本気で取り組むことにして、Athena + Grafana構成に移行して、1年半本番運用してきたんです。今日はそこで学んだ実装の現実的な話をしたいと思います。
VPCフローログの費用構造——なぜ爆上がりするのか
最初、僕らが失敗したのは「VPCフローログを有効化した」で安心しちゃったこと。実際にはいくつか決定ポイントがあるんですよ。
ログの取得レベル選択
VPCフローログには大きく3つのレベルがあります:
- ACCEPT:許可されたトラフィックのみ
- REJECT:拒否されたトラフィックのみ
- ALL:両方
うちは最初ALLにしてたんです。理由は「セキュリティは慎重に」という心がけだったんですが、これが費用を3倍にしちゃった。REJECTだけでも十分な異常検知ができるってことに、1年後に気づきました。正直、最初からREJECTにしておけばなって思いますね。
保存先の選択が重要
保存先によって費用体系がガラッと変わります。実際のコスト比較はこんな感じです:
| 保存先 | 月額(100万フロー) | クエリ費用 | 向いてる用途 |
|---|---|---|---|
| CloudWatch Logs | ¥30,000 | 0 | リアルタイム短期監視 |
| S3 | ¥3,000 | ¥5,000 | 長期保存・分析 |
| Kinesis Firehose | ¥8,000 | ¥5,000 | ストリーム処理・リアルタイム |
CloudWatch Logsはリアルタイム性はいいんですが、保存コストが致命的。うちは結局S3 + Athena構成に完全移行しました。保存は安いけど、クエリのたびに費用がかかるので、ここも工夫が必要なんですよ。
実装パターン——S3 + Athena + Grafanaで何が見えるか
先日チームで構成図を描き直したので、現状のアーキテクチャを紹介します。
graph TB
subgraph VPC["VPC リージョンA"]
ENI["ENI(複数)"]
SG["Security Group"]
end
subgraph Logging["ログ保存層"]
VPCFlow["VPC Flow Logs"]
S3Raw["S3(Raw)<br/>パーティション: s3://date/hour/"]
S3Processed["S3(Processed)<br/>Glueで整形"]
end
subgraph Query["クエリ・分析層"]
Athena["Amazon Athena<br/>標準SQL"]
Glue["AWS Glue<br/>ETL/カタログ"]
end
subgraph Viz["可視化・監視層"]
Grafana["Grafana"]
EventBridge["EventBridge<br/>異常検知ルール"]
SNS["SNS<br/>通知"]
end
ENI --> VPCFlow
VPCFlow --> S3Raw
S3Raw --> Glue
Glue --> S3Processed
S3Raw --> Athena
S3Processed --> Athena
Athena --> Grafana
Athena --> EventBridge
EventBridge --> SNS
重要な実装のポイント
1. S3のパーティション設計
VPCフローログってめちゃくちゃ量が多いので、パーティショニングを正しくやらないとAthenaのクエリが遅くなります。うちはs3://bucket/vpc-flow-logs/region=ap-northeast-1/year=2026/month=05/day=11/hour=14/という階層構成にしてます。
-- パーティション設計が効いたクエリ例
SELECT
srcaddr,
dstaddr,
dstport,
COUNT(*) as flow_count
FROM vpc_flow_logs
WHERE year = 2026
AND month = 5
AND day = 11
AND action = 'REJECT'
GROUP BY srcaddr, dstaddr, dstport
ORDER BY flow_count DESC
パーティション指定なしだと同じクエリで30秒かかってたのが、ちゃんと指定したら2秒に短縮されました。地味に便利ですよ。
2. Glueで定型的な変換を自動化
VPCフローログのRawデータって、タイムスタンプがUNIXタイムとか、IPが数値になってたり、フォーマットが微妙に扱いづらいんです。毎回のクエリで変換してたら費用が跳ね上がる。
うちはGlue Jobで毎時間、昨日のデータを整形してS3 Processedに保存してます。
# Glue Job例
import awswrangler as wr
import pandas as pd
# Raw S3から読み込み
df = wr.s3.read_parquet(
path='s3://bucket/vpc-flow-logs/raw/...',
dataset=True
)
# タイムスタンプをパース
df['timestamp'] = pd.to_datetime(df['start'], unit='s')
df['srcaddr_readable'] = df['srcaddr'] # 実際にはIP逆引きも
# 整形済みで保存
wr.s3.to_parquet(
df,
path='s3://bucket/vpc-flow-logs/processed/...',
dataset=True,
mode='overwrite'
)
これだけで、Athenaのクエリ費用が月3万円削減されました。やる価値ありますね。
3. Grafanaダッシュボードで異常を即座に発見
うちが実装した監視ダッシュボードで、時間別のREJECT数を可視化するとこんな感じです:
xychart-beta
title VPCフローログ監視:時間別REJECT数
x-axis [00, 02, 04, 06, 08, 10, 12, 14, 16, 18, 20, 22]
y-axis "REJECT Count" 0 --> 5000
line [100, 120, 95, 150, 200, 180, 320, 2800, 1500, 600, 400, 280]
このグラフだと、14:00に異常な数のREJECTが出てることが一目瞭然。これが不正なポートスキャンなのか、設定ミスなのか、さっと調査できるようになったんですよ。
実運用で踏んだ落とし穴
1. False Positive地獄
半年運用してわかったのは、REJECTログのほぼ80%がノイズだということ。具体的には:
- ヘルスチェック失敗(ELBが不正なセキュリティグループに向かった)
- VPC内でのDNS解決失敗
- 期限切れのNAT Gateway設定
こういった定型的なパターンばかりが引っかかるんです。ルールベース検知だけだと、運用チームが疲弊します。うちは3ヶ月目に「異常検知がうるさすぎる」ってクレームを受けました。
対策:機械学習ベースの異常検知
2025年中ごろから、Grafana Alerting + SimpleExponentialSmoothing(SES)で、「通常と大きく乖離したパターン」だけを検出するようにしました。
-- Grafanaアラートルール例(Prometheus QL)
abs(
rate(reject_flow_count_5m[5m])
-
avg_over_time(
rate(reject_flow_count_5m[5m] offset 7d)[1d:5m]
)
) > avg_over_time(
rate(reject_flow_count_5m[5m] offset 7d)[1d:5m]
) * 0.3 -- 通常値から30%以上の乖離
これにしてからアラートが1/4に減りました。やっぱり機械学習的なアプローチが効くんですね。
2. VPCピアリングやPrivateLink経由のトラフィック
うちは複数のVPCを運用してるんですが、VPCフローログの「怖さ」は見えすぎることなんです。
ピアリング先のVPCで何かが起きると、自分たちのフローログに記録される。でも自分たちには何もしようがないんですよね。こういう時は本当にイライラします。
対策:VPC単位でログを分離し、タグで責任範囲を管理
# クエリ時点でフィルタリング
SELECT *
FROM vpc_flow_logs
WHERE vpcid = 'vpc-xxxxx' -- 自VPCに限定
AND srcsubnetid LIKE 'subnet-xxxxx%' -- 自サブネット限定
AND action = 'REJECT'
見える範囲を限定することで、チーム間の責任境界をはっきりさせられました。これで誰が対応するのかが明確になったんです。
3. 日中と深夜のトラフィックパターンの違い
これは監視設定を作る上で痛い失敗だったんですが、朝のデプロイラッシュと深夜の定期バッチで、トラフィックパターンが全然違うんです。
おかしなパターンを固定ルールで検知しようとすると、どっちかがアラートになる。困りますよね。
対策:時間帯別のベースライン学習
Grafanaのアラートを時間帯別に設定する、あるいは1週間の周期を学習させる。最初は手動でしたが、今は簡単なPythonスクリプトでベースラインを自動更新してます。
実装する上で注意すべきこと
コスト面でのバランス
正直なところ、全部やると費用が膨らみます。うちの現実的な費用構成はこんな感じです:
- VPCフローログ有効化:月数千円
- S3保存:月数千円
- Athenaクエリ:月数万円(工夫次第)
全部合わせて月3~5万円の範囲に落ち着きました。最初の月15万円から考えると、かなり改善できたんですよ。
Athenaクエリのコツ
避けるべきパターンと推奨パターンをまとめました:
-- ❌ 避けるべき:全行スキャン
SELECT * FROM vpc_flow_logs WHERE srcaddr LIKE '%192%';
-- ✅ 推奨:パーティション指定
SELECT *
FROM vpc_flow_logs
WHERE year=2026 AND month=5 AND day=11
AND srcaddr = '192.168.1.100';
同じ結果でも、10倍コストが違う。Athena Query Resultsのスキャンバイト数を常に見て、クエリを改善する癖をつけるのが大事ですね。
セキュリティ監査対応
VPCフローログは【REJECT:不正アクセス試行】を検出できるので、セキュリティ監査ではかなり評価されます。ただし、以下を運用ルール化しておかないと、監査で突っ込まれます:
- ログの改ざん対策(S3のバージョニング、Object Lock)
- ログの長期保存ポリシー
- アクセス権限の制限
この辺の話はSOC2対応についての別記事にも詳しく書いてます。
異常検知の実例
1年半運用してて、実際に検出できた異常をいくつか紹介しましょう。
事例1:ポートスキャン検出
同一の送信元IPが、短時間に複数の宛先ポートへREJECTを出してた。これはセキュリティグループの設定ミスじゃなく、実際の外部からの侵入試行だったんです。早期対応で被害を防げました。良かった。
事例2:DNS設定エラー
深夜に急にREJECT数が増えた。調べたら、あるマイクロサービスがDNS解決失敗でクエリを垂れ流してた。フローログがなければ、翌朝まで気づかなかった可能性が高い。
事例3:ネットワークセグメンテーション設計ミス
想定外のVPC間通信が増えてた。これはネットワーク設計の見直しきっかけになりました。正直、この発見があっただけでもVPCフローログ導入の価値があったなって思いますね。
Grafana設定の実装例
うちが使ってるダッシュボード定義はこんな感じです(簡略版):
{
"dashboard": {
"title": "VPC Flow Logs Monitoring",
"panels": [
{
"title": "REJECT Count by Hour",
"targets": [
{
"rawSql": "SELECT DATE_TRUNC('hour', timestamp) as hour, COUNT(*) FROM vpc_flow_logs WHERE action='REJECT' GROUP BY 1 ORDER BY 1 DESC"
}
]
},
{
"title": "Top Rejected Ports",
"targets": [
{
"rawSql": "SELECT dstport, COUNT(*) as count FROM vpc_flow_logs WHERE action='REJECT' GROUP BY dstport ORDER BY count DESC LIMIT 10"
}
]
}
]
}
}
これをGrafanaのAthenaプラグインで接続するだけで、ほぼリアルタイムな可視化ができます。正直、セットアップが簡単なので驚きました。
チーム運用での工夫
責任範囲の明確化
VPCフローログは「全体のネットワークが見える」ってのが両刃の剣なんです。インフラチームの責務なのか、セキュリティチームなのか、各チームが責任を負う部分と他責な部分を整理しておかないと、誰も対応しない状況になっちゃいます。
うちは責任を次のように分離してます:
- セキュリティチーム:REJECT異常、ポートスキャン検知
- インフラチーム:VPC設定エラー、DNS問題対応
- 各サービスチーム:自サービスのセキュリティグループ設定管理
こうすることで、誰が何をやるかが明確になったんですよ。
オンコール体制
VPCフローログの異常って、夜中に大量のREJECTが出ることもあります。ただし、本当に対応が必要な異常と、待てる異常を分けるのが大事。
深夜アラートは「情報通知のみ、翌朝判断」みたいなポリシーにすることで、オンコール疲弊を減らせました。やっぱり誤報が多い時間帯は無理に対応させない方が、長期的な運用品質が上がるんですね。
次のステップ:2026年の検討課題
ここからは、正直「まだ検証中」な領域ですが、個人的には気になってることです。
1. eBPFベースの可視化
VPCフローログより細粒度なネットワーク可視化ができるeBPF。コンテナセキュリティについての記事でも触れてますが、Falco、Ciliumなどのツールでアプリケーションレベルのネットワーク分析ができるようになってます。
2. VPC Latticeとの組み合わせ
EKS環境ではVPC Latticeでサービス間通信を管理するケースが増えてます。VPCフローログだけじゃ見えない部分があるんですよ。
3. ゼロトラスト・マイクロセグメンテーション
固定的なセキュリティグループルール設定じゃなく、動的にトラフィック許可を判断する方向へ進んでる。VPCフローログはそれの「監査ログ」としての位置づけに変わるかもしれません。
まとめ
VPCフローログって「有効化して終わり」だと、ただの費用垂れ流しになるんですよね。自分たちの経験から言うと、実装で重要なのはこの5点です:
- 保存先をCloudWatch LogsからS3に変える → 月15万から3万に削減
- Athenaでクエリを書く際、パーティション指定を必ずする → クエリ費用を1/3に
- Glueで定期的にRawデータを整形 → アクセス性と費用のバランスが改善
- Grafanaで時系列ダッシュボード化し、ベースラインから乖離した異常だけをアラート → False Positiveが1/4に
- チーム間で責任範囲を明確に → 運用の持続性が大きく改善
正直、最初の3ヶ月は手探りで、失敗も多かった。でも1年半運用してきた今、VPCフローログはうちのネットワークセキュリティ監視の「中核」になってます。
みなさんはVPCフローログ、どう運用してます?同じ課題でハマってますか?コメント待ってます。