PrivateLink3年運用で学んだマルチアカウント設計の失敗と正解
VPCピアリングからPrivateLinkに移行して3年。10アカウント超えで直面した設計ミスの実例と、本当に使える実装パターンをまとめました。
PrivateLink設計パターン|マルチアカウント3年運用で見えた落とし穴
うちのチームが VPCピアリングからPrivateLinkに移行して、もう3年になる。正直、最初は「単なるエンドポイント設定」だと思ってた。でもマルチアカウント環境で本番運用してみたら、思いのほか複雑で、設計ミスで何度も痛い目を見た。
今日は、その3年間で学んだ実装パターンと、絶対にハマるポイントを共有したい。
VPCピアリングからPrivateLinkへ。移行で気づいたこと
3年前、うちのインフラは VPCピアリングで全部つながってた。EC2→RDS、Lambda→DynamoDB…メッシュ状につながった環境ね。最初は「これで十分」って思ってたけど、アカウント数が10個を超えたあたりから地獄が始まった。
ピアリング設定が複雑になって、ルートテーブルの管理が手に負えなくなる。セキュリティグループは全部を開く必要があるし、ネットワークトポロジーが可視化できない。データ転送コストも、通信パターンが見えないから最適化できない。
そこでPrivateLinkを導入した。PrivateLinkは、接続を「提供側」と「消費側」に分ける。提供側(プロバイダー)がエンドポイントサービスを公開して、消費側(コンシューマー)がそのエンドポイントに接続する構成だ。
図で見るとこんな感じ。
graph TB
subgraph Provider["プロバイダーアカウント"]
subgraph ProviderVPC["Provider VPC"]
NLB["Network Load Balancer"]
RDS[("RDS")]
NLB --> RDS
end
EPService["Endpoint Service"]
NLB --> EPService
end
subgraph Consumer1["コンシューマーアカウント1"]
subgraph ConsumerVPC1["Consumer VPC"]
VPCEndpoint1["VPC Endpoint"]
Lambda1{"Lambda"}
Lambda1 --> VPCEndpoint1
end
end
subgraph Consumer2["コンシューマーアカウント2"]
subgraph ConsumerVPC2["Consumer VPC"]
VPCEndpoint2["VPC Endpoint"]
Lambda2{"Lambda"}
Lambda2 --> VPCEndpoint2
end
end
EPService -->|PrivateLink接続| VPCEndpoint1
EPService -->|PrivateLink接続| VPCEndpoint2
style Provider fill:#e1f5ff
style Consumer1 fill:#fff3e0
style Consumer2 fill:#fff3e0
複数のコンシューマーアカウントから同じリソースへのアクセスを一元管理できるようになる。NLBがいるから、L4レベルの負荷分散もできる。この仕組みのおかげで、後の運用がグッと楽になった。
PrivateLink設計で絶対ハマるポイント5つ
1. セキュリティグループの設定で地獄を見た
最初の設定ミスはここだ。プロバイダー側のセキュリティグループに「RDS のセキュリティグループを許可」ってだけで済ませちゃった。
// 間違った設定
Ingress: RDS のセキュリティグループ → ポート5432
これだと、コンシューマー側からの接続が全部ブロックされる。NLBのターゲットグループが「unhealthy」のまま。原因を特定するのに3日かかった。
PrivateLinkの場合、NLBのセキュリティグループは「VPCエンドポイント側からのトラフィック」を許可する必要があるんだけど、コンシューマー側のアカウントって、プロバイダーから見えないじゃん。だからセキュリティグループを指定できない。
結局、こう設定した。
// 正解の設定
Ingress:
- CIDR: 0.0.0.0/0 (または VPC CIDR)
Protocol: tcp
Port: 5432
「え、0.0.0.0/0?」って思うよね。気持ちはわかる。でも、NLBはPrivateLinkを経由するから、直接インターネットには露出しない。エンドポイントサービス経由でしかアクセスできない構造になってるから、実質的には許可されたコンシューマー側からだけアクセス可能なんだ。
2. PrivateLink は DNS 名前解決が必須。ハマると本当に厄介
コンシューマー側が VPCエンドポイントに接続する時、IP アドレスじゃなく DNS 名を使う設計が鉄則だ。でもデフォルト設定だと、Route 53 プライベートホストゾーンを使わない場合、名前解決がうまくいかない。
// VPC Endpoint の DNS 設定
{
"PrivateDnsEnabled": true,
"PrivateDnsNameOptions": {
"PrivateDnsOnlyForInboundResolverEndpoint": false,
"PrivateDnsNameState": "enabled"
}
}
これを有効にすると、VPCエンドポイントが AWS が提供するプライベート DNS 名で解決できるようになる。アプリケーションコードは元の RDS エンドポイント名(例:mydb.c9akciq32.us-east-1.rds.amazonaws.com)を使ったままで、自動的に VPCエンドポイント経由でルーティングされる。便利だ。
ただし、「PrivateDnsEnabled」だけじゃダメな場合もある。特にカスタムドメイン名を使う場合、Route 53 プライベートホストゾーン + Route 53 リゾルバーエンドポイントで DNS クエリをハイジャックする必要がある。うちのチームはこれで2週間かかった。正直、ドキュメントにはもっと目立つように書いてほしい。
3. エンドポイントサービスのアクセス許可が複雑
プロバイダー側でエンドポイントサービスを作ったら、「どのアカウント・IAM プリンシパルがアクセスできるか」を明示的に設定する。
// Terraform での設定例
resource "aws_vpc_endpoint_service_allowed_principal" "example" {
vpc_endpoint_service_name = aws_vpc_endpoint_service.example.name
principal_arn = "arn:aws:iam::123456789012:root"
}
「root」を指定するのが簡単だけど、本番環境では IAM ロール単位で許可するべきだ。ただし、コンシューマーアカウント内の VPCエンドポイント作成も IAM 権限で制御されるから、設定が二重三重になる。
うちの場合、こんな感じで3段階になってた。
- プロバイダーのエンドポイントサービスが「コンシューマーアカウントの AWS アカウント ID」を許可
- コンシューマーアカウント内の IAM ロールが「ec2:CreateVpcEndpoint」権限を持つ
- コンシューマー VPC の NACL とセキュリティグループがエンドポイント通信を許可
この3段階全部が揃わないと動かない。最初は「なぜつながらない?」で丸1日潰した。
4. データ転送コストの罠
PrivateLinkを導入した理由の1つが「データ転送コスト削減」だった。でも、実際に運用してみたら、思ったほど下がらなかった。
AWS の料金を比較してみると、こんな感じだ。
| 通信パターン | コスト |
|---|---|
| VPCピアリング(同じリージョン) | 無料 |
| VPCピアリング(クロスリージョン) | 0.02/GB |
| PrivateLink(アクティベーション) | 7.2/月 + 0.006/GB |
| PrivateLink(エンドポイント) | 7.2/月 |
PrivateLinkは「アクティベーション」と「エンドポイント」の両方で課金される。しかも、同じリージョン内でもデータ転送量に応じて課金される。
うちの場合、VPCピアリングからPrivateLinkに移行したら、月額が逆に増えちゃった。理由は、エンドポイント数が多かったから。10個のコンシューマーアカウント × 複数サービス = 20個以上のエンドポイント。毎月 200 ドル以上かかってた。
結局、「コスト削減」というより「管理性」と「セキュリティ」の向上を目的にしたほうが正直だ。
5. マルチリージョン展開で地獄を見た
プロバイダーを us-east-1 で動かしてて、ap-northeast-1 にもコンシューマーがいる場合、どうするか。正直、ここで詰まる。
オプションとしては3つある。
- クロスリージョン VPCピアリング + Transit Gateway
- 各リージョンでエンドポイントサービスを複製
- プロバイダーのリソースを多リージョンレプリケーション
うちは最初、オプション1でやろうとした。でも Transit Gateway のコストと複雑さで、結局オプション2に変えた。各リージョンで RDS レプリカを動かして、ローカルのエンドポイントサービスで提供する形にしたんだ。
これだけで、リージョンごとに月 200 ドル × 2 = 400 ドルのコスト増。マルチリージョン戦略って本当に金かかる。
実装で役立ったベストプラクティス
パターン1:Hub & Spoke モデル
うちが最終的に落ち着いた設計は、中央の「Hub」VPC にプロバイダーサービスをまとめて、複数のスポークアカウントがそこに接続する形だ。
graph TB
subgraph Hub["Hub VPC(プロバイダー)"]
NLB1["NLB - RDS"]
NLB2["NLB - Elasticache"]
RDS[("RDS")]
Cache[("Elasticache")]
NLB1 --> RDS
NLB2 --> Cache
end
subgraph Spoke1["Spoke1(コンシューマー)"]
EP1["VPC Endpoint"]
App1{"アプリ"}
App1 --> EP1
end
subgraph Spoke2["Spoke2(コンシューマー)"]
EP2["VPC Endpoint"]
App2{"アプリ"}
App2 --> EP2
end
subgraph Spoke3["Spoke3(コンシューマー)"]
EP3["VPC Endpoint"]
App3{"アプリ"}
App3 --> EP3
end
Hub -->|PrivateLink| Spoke1
Hub -->|PrivateLink| Spoke2
Hub -->|PrivateLink| Spoke3
style Hub fill:#e1f5ff
style Spoke1 fill:#fff3e0
style Spoke2 fill:#fff3e0
style Spoke3 fill:#fff3e0
これだと、管理画面で「どのアカウントが何にアクセスしているか」が一目瞭然だ。セキュリティグループも NLB 側だけで管理すればいいから、設定ミスも減る。
パターン2:DNS リゾルバーエンドポイントで統一ドメイン管理
コンシューマー側が複数 VPC にまたがる場合、Route 53 リゾルバーエンドポイントを使って DNS クエリを統一管理する。仕組みはこんな感じだ。
アプリケーション
↓
"mydb.internal" を解決
↓
Route 53 リゾルバーエンドポイント
↓
Central Route 53 ホストゾーン
↓
VPC エンドポイント IP を返す
これで、アプリ側は「mydb.internal」だけ知ってればいい。VPC Endpoint の IP が変わっても、DNS ハイジャックで自動的にリダイレクトされる。地味に便利な設定だ。
パターン3:IAM ベースのアクセス制御
エンドポイントサービスのアクセス許可に加えて、コンシューマー側の IAM でも制御する。こうするとセキュリティが格段に上がる。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:DescribeVpcEndpoints",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "ec2:CreateVpcEndpoint",
"Resource": [
"arn:aws:ec2:*:*:vpc/*",
"arn:aws:ec2:*:*:subnet/*",
"arn:aws:ec2:*:*:vpc-endpoint/*"
],
"Condition": {
"StringEquals": {
"ec2:ServiceName": "com.amazonaws.vpce.us-east-1.vpce-svc-xxxxx"
}
}
}
]
}
これで、特定のエンドポイントサービスへの接続だけ許可できる。他のサービスへのアクセスは IAM で自動的にブロックされる。
運用でやってよかったこと
CloudWatch Logs + VPC Flow Logs の組み合わせが本当に助かってる。VPC Flow Logs をオンにして、CloudWatch Insights でクエリすれば、エンドポイント経由の通信が実時間で見える。
fields @timestamp, srcaddr, dstaddr, dstport, action
| filter dstport = 5432
| stats count() by srcaddr
これで「どのコンシューマーが何にどれくらいアクセスしているか」が可視化できる。コスト最適化の判断材料にもなるし、セキュリティインシデント対応の時も役立つ。
NLB のターゲットグループヘルスチェックも細かく設定した。RDS の場合、TCP ポート 5432 への接続確認だけじゃなく、実際にクエリを流して応答時間を測定する。3秒以上かかるターゲットを unhealthy にして、自動的に除外するって感じだ。こうすると、障害の検知が早くなる。
正直な話
PrivateLinkは「銀の弾」じゃない。VPCピアリングから移行したら全部解決する、わけじゃない。むしろ、管理の複雑さが増す。
でも、マルチアカウント環境が10個を超えてくると、ピアリングのメッシュ管理は本当に苦しい。その時点でPrivateLinkへの移行は「必要悪」だと思う。長期的には確実にメリットがある。
うちのチームは3年かけて、このパターンに落ち着いた。新規プロジェクトでネットワーク設計する時は、最初からPrivateLinkを前提に考えるようになった。
まとめ
- セキュリティグループは「0.0.0.0/0」許可が正解。PrivateLink経由でしかアクセスできない仕組みになってるから、実質的には安全だ
- DNS名前解決の設定が地獄。PrivateDnsEnabled と Route 53 リゾルバーエンドポイントの組み合わせが必須。ドキュメント読むときはここに注目してほしい
- コスト削減が目的なら、PrivateLinkは割に合わない。管理性とセキュリティが目的と割り切ったほうがいい
- Hub & Spoke モデルで一元管理。複数のコンシューマー・複数のサービスでも管理画面が楽になる
- VPC Flow Logs + CloudWatch Insights で可視化。本番運用で何が起きてるか把握できるから、トラブル対応も早くなる
次のアクションとしては、既存のVPCピアリング環境がある場合、まずは段階的にPrivateLinkへ移行するロードマップを引いてみてほしい。一度に全部切り替えるのはリスクが高い。段階的にやれば、万が一トラブルが起きても対応しやすい。