★★★ 上級

15. VPC Endpoints / PrivateLink

プライベートサブネットの中から S3 や DynamoDB、SSM などの AWS API を NAT Gateway 経由ではなく プライベートに直接呼ぶ仕組み。セキュリティとコスト両面のメリット。

なぜ VPC Endpoint を使うか

プライベートサブネットの EC2 から S3 にアップロードしたい時、NAT Gateway 経由でインターネットを通って S3 に到達する のが標準ルート。これには:

VPC Endpoint を使えば VPC 内から AWS API へ直接プライベートに接続。NAT 不要、データ転送料は VPC 内(ほぼゼロ)、セキュリティ上もインターネットを通らない。

2 種類の Endpoint

Gateway endpointInterface endpoint
対応サービスS3 と DynamoDB のみほとんどの AWS サービス
仕組みルートテーブルにルート追加ENI を作って DNS を上書き
料金無料$0.01/時間/AZ + データ処理 $0.01/GB
用途S3 / DynamoDB の VPC 内利用SSM、ECR、Logs、Secrets Manager 等

Gateway endpoint(S3 / DynamoDB)

必ず使うべき。無料で得しかない

data "aws_region" "current" {}

resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.${data.aws_region.current.name}.s3"
  vpc_endpoint_type = "Gateway"

  route_table_ids = concat(
    [aws_route_table.private.id],
    [for rt in aws_route_table.public : rt.id]
  )

  tags = { Name = "s3-gateway" }
}

resource "aws_vpc_endpoint" "dynamodb" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.${data.aws_region.current.name}.dynamodb"
  vpc_endpoint_type = "Gateway"

  route_table_ids = [aws_route_table.private.id]
}

Interface endpoint

各 AZ にプライベート ENI(Elastic Network Interface)を作成し、Route 53 DNS で AWS の API ドメインを ENI に向けます。

resource "aws_security_group" "vpc_endpoint" {
  name_prefix = "vpc-endpoint-"
  vpc_id      = aws_vpc.main.id
}

resource "aws_vpc_security_group_ingress_rule" "endpoint_https" {
  security_group_id = aws_security_group.vpc_endpoint.id
  cidr_ipv4         = aws_vpc.main.cidr_block
  from_port         = 443
  to_port           = 443
  ip_protocol       = "tcp"
}

# 必要そうな endpoint をまとめて
locals {
  interface_endpoints = [
    "ssm",                    # SSM Session Manager
    "ssmmessages",            # SSM Session Manager
    "ec2messages",            # SSM
    "ecr.api",                # ECR API
    "ecr.dkr",                # ECR Docker registry
    "logs",                   # CloudWatch Logs
    "secretsmanager",         # Secrets Manager
    "kms",                    # KMS
    "sts",                    # STS(IAM ロール用)
  ]
}

resource "aws_vpc_endpoint" "interface" {
  for_each = toset(local.interface_endpoints)

  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.${data.aws_region.current.name}.${each.key}"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [for s in aws_subnet.private : s.id]
  security_group_ids  = [aws_security_group.vpc_endpoint.id]
  private_dns_enabled = true   # AWS の DNS 名で接続できる

  tags = { Name = each.key }
}

private_dns_enabled = true がポイント。これで secretsmanager.ap-northeast-1.amazonaws.com のような AWS の標準ドメインが VPC 内 ENI の IP に解決されるようになる。アプリ側のコード変更は不要。

コスト比較

例: プライベートサブネットの ECS タスクが S3 と Secrets Manager と CloudWatch Logs を使う場合(月)。

NAT Gateway 経由VPC Endpoint 経由
NAT GW 時間料$33 / 1 GW不要 (or 縮小)
NAT データ処理10 GB なら $0.450
S3 endpoint無料
Interface endpoint × 33 × $7.2 = $21.6 (1 AZ) / $43.2 (2 AZ)
合計(2 AZ 想定)~$66~$43.2

NAT Gateway を完全廃止できれば月 $66 セーブ。ただし「外向きインターネットが必要なケース(パッケージ更新等)」が無いか確認が必要。

VPC Endpoint を 第三者のサービス(Datadog、Snowflake、自社の別 VPC のサービス)に向ける場合は PrivateLink を使う。

resource "aws_vpc_endpoint" "datadog" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxxxxxx"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [for s in aws_subnet.private : s.id]
  security_group_ids  = [aws_security_group.vpc_endpoint.id]
}
最低限のチェックリスト ① S3 と DynamoDB の Gateway endpoint は 必ず作る(無料)。② プライベートサブネットの EC2/ECS が頻繁に呼ぶサービス(SSM、ECR、Logs、Secrets Manager)の Interface endpoint を検討。③ NAT Gateway のデータ処理量が高いサービスから順に置き換え。