月30万円の請求に気づいた。VPCフローログのコスト地獄と実装した対策

本番環境のVPCフローログで月30万円の請求が…。実務で効いた6つの対策と実装コードを紹介。CloudWatchの盲点も解説。

月30万円の請求で気づいた話

先日、AWSの請求書を眺めていたら、謎のVPC Flowlogsの請求が月30万円を超えていた。最初は目を疑った。正直「まさか」という気持ちだった。

うちのチームは本番環境のネットワーク監視を強化するためにVPCフローログを有効化したんだけど、その直後から請求がじわじわと膨らみ始めたんですよ。CloudWatch Logsに全トラフィック流してたんです。全トラフィック。本当にやばい。

調べてみたら、フローログって1秒間に数万件のレコードが生成されるんだ。それがCloudWatchに溜まり続ける。保存期間を設定してなかったから、永遠に課金されてた。本当に馬鹿だった。

実務で痛感したVPCフローログのコスト地獄

VPCフローログの請求構造って、多くのドキュメントに書かれてない。だから実装するときは本当に注意が必要なんだ。

具体的には、以下のポイントで課金が発生する。

要素課金月額目安(100Gbps推移)
CloudWatch Logs取得$0.50/GB約3万円
CloudWatch Logs保存$0.03/GB/月約12万円
S3保存(標準)$0.023/GB/月約8万円
Athena分析$5/TB約2万円
VPCフローログ自体無料0円

わかりますか?ログ量が増えるだけで、あっという間に月30万円を超える。特にCloudWatch Logsの保存料金。これが盲点になりやすいんですよね。

うちは初期設計で、CloudWatchに永久保存してた。つまり、3ヶ月で約90万円の請求が来ていた計算になる。本番環境だけでね。もう目玉が飛び出そうでした。

最初にやったコスト削減:フィルタリングと保存期間の設定

まず取り組んだのは、本当に必要なログだけを取得するという割り切り。正直、これが一番効果的だった。

全トラフィック監視は理想的だけど、実務的には不可能。代わりに「REJECT」だけを拾うフィルタリングに切り替えた。

# Terraform で VPCフローログのフィルタリング設定
resource "aws_flow_log" "rejected_traffic" {
  iam_role_arn    = aws_iam_role.vpc_flowlog.arn
  log_destination = aws_cloudwatch_log_group.vpc_flowlog.arn
  traffic_type    = "REJECT"  # REJECT だけに限定
  vpc_id          = aws_vpc.main.id

  tags = {
    Name = "vpc-flowlog-reject-only"
  }
}

# CloudWatch Logs の保存期間を 7 日に設定
resource "aws_cloudwatch_log_group" "vpc_flowlog" {
  name              = "/aws/vpc/flowlogs"
  retention_in_days = 7  # 以前は永久保存(無期限)

  tags = {
    Name = "vpc-flowlog-7day"
  }
}

これだけで、ログ量が1/20以下に減った。正直驚いた。REJECTだけでも十分に異常を検出できるんだ。ACCEPTトラフィックの監視は、アプリケーションレベルのメトリクスで十分なんですよね。

CloudWatch Logsの保存期間を7日に設定したのも大きい。異常検知なら1週間のログで十分だからね。それ以上古いデータは分析対象にならないし、セキュリティインシデント対応でも大体は数日以内に判明する。

S3へのエクスポートとAthenaでの分析設計

一方で「完全に削除したくない」という要件もあった。セキュリティ監査対応の関係で、過去6ヶ月のログが必要らしい。

そこで戦略を変えた。CloudWatch Logsには7日分だけ保持して、それ以上はS3に自動でエクスポートする仕組みを作った。

# CloudWatch LogsからS3へのエクスポートを自動化(Lambda)

import boto3
import json
from datetime import datetime, timedelta

logs_client = boto3.client('logs')
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    log_group = '/aws/vpc/flowlogs'
    bucket_name = 'vpc-flowlogs-archive'
    
    # 前日のログをエクスポート
    today = datetime.utcnow()
    yesterday = (today - timedelta(days=1)).strftime('%Y%m%d')
    
    from_time = int((today - timedelta(days=1)).timestamp() * 1000)
    to_time = int(today.timestamp() * 1000)
    
    s3_prefix = f"vpc-logs/{yesterday}/"
    
    try:
        response = logs_client.create_export_task(
            logGroupName=log_group,
            fromTime=from_time,
            to=to_time,
            destination=bucket_name,
            destinationPrefix=s3_prefix
        )
        print(f"Export task created: {response['taskId']}")
        return {
            'statusCode': 200,
            'body': json.dumps(f"Exported logs for {yesterday}")
        }
    except Exception as e:
        print(f"Error: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps(f"Export failed: {str(e)}")
        }

これを毎晩実行させれば、CloudWatch Logsの負担が減る。そしてS3には180日分保存。シンプルだけど地味に便利。

S3ライフサイクルポリシーで段階的にコストを削減

S3に保存したVPCフローログも、全部をStandardストレージに置いておくと馬鹿にならない。

実際、6ヶ月分のVPCフローログは約400GBくらいになる。これがずっとStandardだと年20万円以上。そんなの払ってられない。

ライフサイクルポリシーで段階的にストレージクラスを変えた。

{
  "Rules": [
    {
      "Id": "vpc-flowlogs-lifecycle",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "vpc-logs/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER_IR"
        }
      ],
      "Expiration": {
        "Days": 180
      }
    }
  ]
}

結果、月額のS3保存料金が8万円 → 2万円に下がった。4倍の削減だ。Glacier Instant Retrievalを使うと、いざという時にすぐに取り出せるし、コストも安い。完璧。

Athenaでの異常検知を実装した話

さて、S3に溜まったログを有効活用するために、Athenaで分析する仕組みも作った。

元々は「ログが溜まってるだけ」だったけど、これで初めて活用できる形になった。

-- VPCフローログの外部テーブルをAthenaで作成
CREATE EXTERNAL TABLE vpc_flowlogs (
    version INT,
    account_id STRING,
    interface_id STRING,
    srcaddr STRING,
    dstaddr STRING,
    srcport INT,
    dstport INT,
    protocol INT,
    packets BIGINT,
    bytes BIGINT,
    windowstart BIGINT,
    windowend BIGINT,
    action STRING,
    tcpflags INT,
    start BIGINT,
    `end` BIGINT,
    region STRING,
    vpc_id STRING,
    subnet_id STRING,
    instance_id STRING,
    interface_type STRING,
    eni_id STRING,
    pkt_srcaddr STRING,
    pkt_dstaddr STRING,
    region_name STRING,
    vpc_name STRING
)
PARTITIONED BY (
    year STRING,
    month STRING,
    day STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ' '
LOCATION 's3://vpc-flowlogs-archive/vpc-logs/'
TBLPROPERTIES ("skip.header.line.count" = "1")

こういうクエリで異常検知してるんだ。

-- 通常と異なるトラフィックパターンを検出
SELECT 
    dstaddr,
    dstport,
    COUNT(*) as reject_count,
    SUM(bytes) as total_bytes,
    COUNT(*) * 100.0 / 
        (SELECT COUNT(*) FROM vpc_flowlogs 
         WHERE action = 'REJECT' 
         AND day = DATE_FORMAT(CURRENT_DATE, '%Y%m%d')) as reject_percentage
FROM vpc_flowlogs
WHERE action = 'REJECT'
    AND day = DATE_FORMAT(CURRENT_DATE, '%Y%m%d')
GROUP BY dstaddr, dstport
HAVING COUNT(*) > 1000
ORDER BY reject_count DESC;

毎日このクエリを回して、異常なトラフィックパターンをチェック。本番環境で不正アクセスや設定ミスを早期発見できるようになった。

Athenaの分析コストは、こういう定期クエリを毎日回しても月3000円程度。CloudWatchと比べたら圧倒的に安い。

実装で気づいた落とし穴

CloudWatch Insights との使い分け

VPCフローログの分析でよくやりがちなのが、CloudWatch Insightsで全てを賄おうとすること。これ、やめた方がいいよ。

Insightsは高速だけど、スキャン対象が大きくなると月10万円超えることもある。Athenaなら同じクエリが数百円で済む。使い分けが超大事。

緊急時の「今すぐ確認したい」はInsights。日次の「トレンド分析」はAthena。この使い分けが正解なんですよね。

フィルタリングのバランス

REJECT だけにすると、セキュリティグループの誤設定で正常なトラフィックが落ちてる場合に気づきにくくなる。

うちは結局「REJECT + UDP (ポート 53 除外)」という形で、一定量のACCEPTトラフィックも拾うようにした。これでDNS解決失敗とか設定ミスに気づきやすくなった。

# より実務的なフィルタリング
resource "aws_flow_log" "security_monitoring" {
  iam_role_arn    = aws_iam_role.vpc_flowlog.arn
  log_destination = aws_cloudwatch_log_group.vpc_flowlog.arn
  vpc_id          = aws_vpc.main.id

  log_format = "$${version} $${account-id} $${interface-id} $${srcaddr} $${dstaddr} $${srcport} $${dstport} $${protocol} $${packets} $${bytes} $${windowstart} $${windowend} $${action} $${tcpflags} $${start} $${end} $${region} $${vpc-id} $${subnet-id} $${instance-id} $${interface-type}"
  
  traffic_type = "ALL"
}

実は、VPCフローログはフィルタリング機能がクエリ側で制御できる。だからCloudWatch Logsに全部流して、インジェスト段階で絞る方が柔軟だったりする。ここはチームのリスク許容度で判断すべきポイント。

6ヶ月の運用で月30万円が月5万円になった

今は本当に安定してる。

  • CloudWatch Logs:月7日分のみ → 月500円
  • S3ストレージ:180日分、ライフサイクル自動化 → 月2万円
  • Athena分析(毎日実行) → 月3000円
  • VPCフローログ自体 → 無料

合計:月25,300円

元々は月30万円だったから、月274,700円の削減だ。年間で330万円浮いた。

ただし、気をつけてほしいのは「全部削減できる」わけではないってこと。うちも検討期間を含めて3ヶ月かかった。セキュリティ監査の要件確認、ログ保持期間の正当性の説明、異常検知ロジックの検証…細かい部分が多いんだ。

でも、最初から「月30万円かかる」と理解していれば、こんなに長く放置することはなかった。実装当初は「ネットワーク監視を強化するんだ」という理想で進めて、コスト面に目を向けなかった。その代償は大きかった。

まとめ

VPCフローログのコスト最適化は、実装時の意識決定で9割決まる。以下の3つを最初から押さえておけば、後悔しない。

1. ログレベルの選定 全トラフィックではなく、REJECT または重要なポート群に限定する。保存期間はCloudWatch Logs 7日、S3アーカイブ180日の組み合わせが現実的だ。

2. ストレージ階層化 S3ライフサイクルで30日→STANDARD_IA、90日→Glacier Instantを自動化。スクリプトで自動エクスポート処理も組む。面倒だけど、この投資は絶対に回収できる。

3. 分析ツールの使い分け 緊急時はCloudWatch Insights、日次トレンドはAthena。Athenaの方が長期保持ログ分析では圧倒的にコスト効率がいい。

VPCフローログは本番運用に欠かせない。でも「全トラフィック永久保存」みたいな運用は、まじでオススメしない。コストと実用性のバランスを取ることが、地味だけど重要な仕事だと気づいた。

ちなみに、いま同じく本番環境で運用してるCloudWatch vs Datadogとの組み合わせについても考え直してる。そっちの話はまた別で書きたい。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事