15. VPC Endpoints / PrivateLink
プライベートサブネットの中から S3 や DynamoDB、SSM などの AWS API を NAT Gateway 経由ではなく プライベートに直接呼ぶ仕組み。セキュリティとコスト両面のメリット。
なぜ VPC Endpoint を使うか
プライベートサブネットの EC2 から S3 にアップロードしたい時、NAT Gateway 経由でインターネットを通って S3 に到達する のが標準ルート。これには:
- コスト: NAT Gateway は時間 $0.045 + データ処理量 $0.045/GB
- セキュリティ: トラフィックがインターネットに出る(再度 AWS 内に戻るとはいえ経路上で)
- パフォーマンス: NAT Gateway を経由する分のレイテンシ
VPC Endpoint を使えば VPC 内から AWS API へ直接プライベートに接続。NAT 不要、データ転送料は VPC 内(ほぼゼロ)、セキュリティ上もインターネットを通らない。
2 種類の Endpoint
| Gateway endpoint | Interface 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.45 | 0 |
| S3 endpoint | — | 無料 |
| Interface endpoint × 3 | — | 3 × $7.2 = $21.6 (1 AZ) / $43.2 (2 AZ) |
| 合計(2 AZ 想定) | ~$66 | ~$43.2 |
NAT Gateway を完全廃止できれば月 $66 セーブ。ただし「外向きインターネットが必要なケース(パッケージ更新等)」が無いか確認が必要。
PrivateLink
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]
}