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を経由するから、直接インターネットには露出しない。エンドポイントサービス経由でしかアクセスできない構造になってるから、実質的には許可されたコンシューマー側からだけアクセス可能なんだ。

コンシューマー側が 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段階になってた。

  1. プロバイダーのエンドポイントサービスが「コンシューマーアカウントの AWS アカウント ID」を許可
  2. コンシューマーアカウント内の IAM ロールが「ec2:CreateVpcEndpoint」権限を持つ
  3. コンシューマー 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つある。

  1. クロスリージョン VPCピアリング + Transit Gateway
  2. 各リージョンでエンドポイントサービスを複製
  3. プロバイダーのリソースを多リージョンレプリケーション

うちは最初、オプション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へ移行するロードマップを引いてみてほしい。一度に全部切り替えるのはリスクが高い。段階的にやれば、万が一トラブルが起きても対応しやすい。

U

Untanbaby

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

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

関連記事