はじめに:夜間障害の現場で、私たちは何を見ているのか
「おい、また本番環境の特定マイクロサービスから、外部の決済APIへのリクエストがタイムアウトしているぞ。セキュリティグループもルートテーブルも完璧なはずなのに、いったいどこでパケットがドロップしているんだ……!」
真夜中のアラート対応。冷や汗をかきながらAWSコンソールを叩くシニアエンジニアの背中を、あなたも見たことがあるかもしれません。クラウドネイティブな世界では、私たちは物理的なケーブルやスイッチに触れることができません。仮想VPCの海を泳ぐパケットの生死を握るのは、ロジックとログだけです。
ここで最強の武器となるのが VPCフローログ(VPC Flow Logs) です。
「あぁ、あれね。S3に吐き出してAthenaで集計するやつでしょ?」と思ったそこのあなた。それだけでは、この機能が持つ本当のポテンシャルを半分も見落としています。VPCフローログは、単なる死活監視の監査証拠(Audit Trail)ではありません。パブリックサブネットとプライベートサブネットの境界、そしてInternet Gateway(IGW)という「関所」を通過するすべてのパケットの生態系を暴く、最高にエキサイティングなメタデータなのです。
今回は、ネットワークの基礎から、実務で使える生々しいログ解析のテクニックまで、現場の知見を総動員して徹底的に解説します。
—
1. VPCフローログのメカニズムとパケットの旅
まずは、VPCフローログがどのように生成されるのか、その裏側の挙動を解剖しましょう。
よくある誤解として、「VPCフローログを有効にすると、パケットのペイロード(中身のデータ)がすべてキャプチャされてS3に保存される」と思っている人がいますが、それは間違いです。もしそんなことをしたら、ストレージ代が爆発し、プライバシー規制(GDPRや個人情報保護法)に引っかかります。
VPCフローログが記録するのは、パケットの「メタデータ(通信の履歴書)」です。
正確には、AWSのハイパーバイザー層(ENI: Elastic Network Interfaceのレイヤー)で一定時間(デフォルトの集約間隔は1分、最短で10秒も可能)に観測されたトラフィックをサンプリングし、IPFIX(IP Flow Information Export)に近い形式でまとめて出力しています。
パブリック・プライベート・IGWを跨ぐ通信のライフサイクル
次のようなよくある3層アーキテクチャを想像してください。
[ 外部インターネット ]
│
(Internet Gateway / IGW) ─── ★ここでIGW境界の拒否/許可をキャプチャ
│
[ パブリックサブネット (ALB) ]
│
(NAT Gateway or 内製プロキシ)
│
[ プライベートサブネット (Web/API App) ]
1. インバウンド方向:外部クライアントからパブリックサブネットのALBに向かう通信が、Internet Gateway に到達します。もしここでNetwork ACLやセキュリティグループ、あるいはAWS WAF等で弾かれた場合、VPCフローログには REJECT という非情なステータスが刻まれます。
2. アウトバウンド方向:プライベートサブネットにあるバックエンドAPIが、外部のSaaSやWeb APIへリクエストを飛ばす際、パケットはNAT GatewayやIGWを経由して外に出ていきます。
ここで重要なのは、「どこでログを有効にするか」です。
ENI単位、サブネット単位、あるいはVPC単位でフローログを有効にできますが、トラブルシューティングの初動では、問題の切り分けのためにサブネット単位、あるいはIGWのアタッチされているVPC全体で有効にしておくのが鉄則です。
—
2. ログフォーマットの深掘りと「絶対に知るべき」フィールド
AWSのデフォルト・フローログ・フォーマットは、次のようなCSV形式の文字列です。
2 123456789012 eni-0123456789abcdef0 10.0.1.100 192.0.2.50 54321 443 6 5 320 1672531200 1672531260 ACCEPT OK
一見すると暗号のようですが、シニアSREの眼には、これが一つの美しいドラマのように映ります。実務で特に注目すべき主要なフィールドをピックアップして解説します。
| フィールド名 | サンプル値 | 現場での解釈と重要性 |
| :— | :— | :— |
| version | 2 | フローログのバージョン。拡張フォーマット(version 3〜5)では、パケットの方向(pkt-src-addr等)も取れます。 |
| account-id | 123456789012 | マルチアカウント環境でS3バケットに集約する際、どのAWSアカウントからの通信か一発で特定できます。 |
| interface-id| eni-... | どのENIを通過したか。EC2、RDS、ALB、ENI型NAT Gatewayのどれに紐づくかが分かります。 |
| srcaddr / dstaddr | 10.0.1.100 / 192.0.2.50 | 送信元と宛先のIPアドレス。プライベートIPとパブリックIPの変換(NAT)前後を追うのに不可欠です。 |
| srcport / dstport| 54321 / 443 | ポート番号。Web API開発であれば、443 (HTTPS) や 80 (HTTP) 以外に変なポートが空いていないか監視します。 |
| protocol | 6 | IANAプロトコル番号。6 は TCP、17 は UDP、1 は ICMP です。 |
| packets / bytes | 5 / 320 | 送受信されたパケット数とバイト数。DDoSやデータ漏洩の兆候を検知する重要なメトリクスになります。 |
| start / end | Unix Epoch | フローの開始・終了時刻。集約間隔(1分など)の間に発生した通信のタイムスタンプです。 |
| action | ACCEPT / REJECT | 最重要。 セキュリティグループやNetwork ACLによって許可されたか、拒否されたかを示します。 |
| log-status | OK / NODATA / SKIPDATA | ログ自体の健全性。NODATA が続く場合、そのENIに全くトラフィックがないか、ログの吐き出しに失敗しています。 |
バージョン5(v5)で追加されたモダンなフィールド
もし可能であれば、フォーマットはカスタム(あるいは最新バージョン)に設定し、以下のフィールドを追加することをお勧めします。
traffic-path:パケットがどのようにルーティングされたか(例:IGWを経由したか、VPC Peering経由か)pkt-src-addr/pkt-dst-addr:NAT等で書き換えられる前の、真のパケット送信元・宛先IP
これがあると、複雑なルーティング環境でのパケット迷子を一撃で解決できます。
—
3. 実践:VPCフローログの有効化とパケット検証
それでは、実際に手を動かして環境を整え、ログを収集できるようにしましょう。
今回はTerraformを用いて、特定VPCのフローログをCloudWatch Logs、およびS3バケットへ出力するインフラストラクチャのコード例を示します。
TerraformによるVPCフローログの設定例
# フローログの出力先となるS3バケットの定義
resource "aws_s3_bucket" "flow_log_bucket" {
bucket = "my-company-prod-vpc-flow-logs-bucket"
force_destroy = true
tags = {
Environment = "production"
ManagedBy = "Terraform"
}
}
# S3バケットの暗号化設定(セキュリティ要件の基本)
resource "aws_s3_bucket_server_side_encryption_configuration" "flow_log_encryption" {
bucket = aws_s3_bucket.flow_log_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# フローログ用のIAMロール
resource "aws_iam_role" "flow_log_role" {
name = "vpc-flow-log-to-s3-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
}
]
})
}
# IAMポリシーの定義(S3への書き込み権限)
resource "aws_iam_role_policy" "flow_log_policy" {
name = "vpc-flow-log-to-s3-policy"
role = aws_iam_role.flow_log_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"s3:CreateBucket",
"s3:PutObject",
"s3:GetBucketLocation"
]
Effect = "Allow"
Resource = [
"${aws_s3_bucket.flow_log_bucket.arn}",
"${aws_s3_bucket.flow_log_bucket.arn}/*"
]
}
]
})
}
# VPCフローログ自体のリソース定義
resource "aws_flow_log" "main_vpc_flow_log" {
iam_role_arn = aws_iam_role.flow_log_role.arn
log_destination = aws_s3_bucket.flow_log_bucket.arn
traffic_type = "ALL" # ACCEPTだけでなくREJECTも含めてすべて記録
vpc_id = "vpc-0123456789abcdef0" # 対象のVPC ID
log_format = "$version $account-id $interface-id $srcaddr $dstaddr $srcport $dstport $protocol $packets $bytes $start $end $action $log-status $subnet-id"
tags = {
Name = "prod-vpc-flow-log"
}
}
—
4. Web API開発者のためのトラブルシューティング手法
インフラエンジニアだけでなく、Web APIを設計・開発するエンジニアにとっても、VPCフローログを読めることは強力な武器になります。よくあるトラブルシナリオをどう解決するか、実務の現場における手順を見ていきましょう。
シナリオ:「外部の決済APIへリクエストが届かない(タイムアウトする)」
自社のプライベートサブネットにあるECS/Lambdaから、外部のサードパーティAPI(例: https://api.example-payment.com/v1/charge)へHTTPSリクエストを送ったところ、レスポンスが返ってこずにタイムアウトエラーになるケースです。
1. CloudWatch Logs Insights / Athenaでのクエリ発行
Amazon AthenaやCloudWatch Logs Insightsを使って、該当するプライベートIPや宛先ポート(443)のログを検索します。
CloudWatch Logs Insightsであれば、以下のようなクエリが即座に役立ちます。
fields @timestamp, srcaddr, dstaddr, srcport, dstport, protocol, action, packets, bytes
| filter dstport = 443 and action = "REJECT"
| sort @timestamp desc
| limit 20
2. 原因の切り分け
- パターンA:
actionがREJECTになっている場合 - 原因:セキュリティグループの egress(アウトバウンド)ルールが絞られすぎていて、外部への
443ポートの通信がブロックされています。あるいは、Network ACLでプライベートサブネットからのアウトバウンドが制限されています。 - パターンB:ログ自体が一切出力されない、あるいは
ACCEPTになっているのに返ってこない場合 - 原因(ログなし):そもそもアプリケーションが外に向かってパケットを飛ばせていません。ルートテーブルに
0.0.0.0/0の宛先としてNAT Gateway(またはインターネットGW)へのルートが存在するか確認してください。 - 原因(ACCEPTなのに不通):NAT Gatewayの向こう側、つまり相手方のファイアウォールやAWSの外部でパケットがドロップしています。あるいは、セキュリティグループのステートフルな特性を勘違いし、インバウンドの戻りパケットの経路でルーティングループが発生している可能性があります。
Pythonによる簡易的な疎通・ログ突合スクリプトの活用
開発現場では、ローカルから特定のAPIエンドポイントのIPを特定し、フローログ上の挙動と突き合わせるために、次のような簡易的なPythonスクリプトをサクッと書いて検証することもあります。
import socket
import sys
def resolve_target_api(hostname):
"""
外部APIのホスト名からIPアドレスを解決し、
インフラチームへVPCフローログ調査を依頼するための情報を出力する
"""
try:
ip_address = socket.gethostbyname(hostname)
print(f"[+] ホスト名: {hostname}")
print(f"[+] 解決されたIPアドレス: {ip_address}")
print(" -> このIP宛ての dstaddr および dstport=443 のログをVPCフローログから検索してください。")
except socket.gaierror as e:
print(f"[-] 名前解決に失敗しました: {e}", file=sys.stderr)
if __name__ == "__main__":
target = "api.example-payment.com"
resolve_target_api(target)
現場のエンジニアが「なんとなく設定を変えてみる(お祈りデプロイ)」ではなく、このように「パケットがどこを通り、どこで拒否されたか(REJECT)」というファクトベースで会話できるようになると、障害解決のスピードは圧倒的に跳ね上がります。
—
5. おわりに:ログを制する者はクラウドネットワークを制す
今回は、VPCフローログの基礎から、パブリック・プライベート・IGWを跨ぐ通信のトラッキング、Terraformによる実装、そして実務でのトラブルシューティング手法までを一気に解説しました。
クラウドのインフラはブラックボックスに見えがちですが、VPCフローログという「確かな足跡」を正しく読み解くスキルがあれば、見えないパケットの流れが手に取るように見えてきます。深夜の障害対応で「よし、ここがREJECTされている原因はこれだ!」とピンポイントで突き止められた瞬間の快感は、シニアSREならではの醍醐味です。
ぜひ、あなたのプロジェクトでもVPCフローログの収集と、いざという時のクエリ環境(AthenaやInsights)を整えておいてください。あなたのシステムの信頼性は、今日から確実に一段階引き上げられるはずです。
コメント