[アップデート] AWSサービスのAWS PrivateLink(インターフェース型)がクロスリージョン接続をサポートするようになりました

[アップデート] AWSサービスのAWS PrivateLink(インターフェース型)がクロスリージョン接続をサポートするようになりました

2025.11.20

はじめに

皆様こんにちは、あかいけです。

AWSでは様々なアップデートがありワクワクな日々をお過ごしのことかと思いますが、
2025年11月19日に、AWSサービス向けのAWS PrivateLinkがクロスリージョン接続をサポートするようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/11/aws-privatelink-cross-region-connectivity-aws-services/

実は、約1年前のアップデート(2024年11月26日)で、AWS PrivateLinkのクロスリージョン接続機能自体はすでにリリースされていました。
ただ、そのときは VPCエンドポイントサービス(自社サービスやサードパーティサービス)向け のみの対応でした。

https://dev.classmethod.jp/articles/aws-privatelink-across-region-connectivity/
https://aws.amazon.com/jp/about-aws/whats-new/2024/11/aws-privatelink-across-region-connectivity/

そして今回のアップデートにより、
AWSが提供するサービス(S3、ECR、Lambdaなど)向けのインターフェイス型VPCエンドポイント でもクロスリージョン接続が可能になったというわけです。

2つのクロスリージョン接続の違い

混乱しやすいポイントなので、2つのアップデートの違いを整理します。

項目 2024年11月26日のアップデート 2025年11月19日のアップデート(今回)
対象 VPCエンドポイントサービス AWSサービス向けVPCエンドポイント(インターフェース型)
具体例 自社で構築したNLB経由のサービス
サードパーティサービス
Amazon S3、ECR、Lambda、KMS等の
AWS公式サービス
サービス提供者 自分または他のAWSアカウント AWS
用途 社内システムのクロスリージョン接続
パートナー連携
AWSマネージドサービスへの
クロスリージョン接続

簡単に言うと、2024年のアップデートは「 自分で作ったサービス をクロスリージョンで使えるようにする機能」で、
今回のアップデートは「 AWSが提供しているサービス をクロスリージョンで使えるようにする機能」です。

多くのユーザーにとっては、今回のアップデートの方が直接的な恩恵を受けやすいのではないかと思います。
というわけで今回は、AWSサービス向けインターフェイス型VPCエンドポイントのクロスリージョン接続について、その概要、具体的なメリット、制限事項、そして実際にTerraformで検証する方法をまとめます。

※ ゲートウェイ型VPCエンドポイントは引き続き未対応です

対応サービス

なお、すべてのAWS PrivateLink対応サービスがクロスリージョン接続に対応したわけではありません。
対応サービスの最新情報は以下の公式ドキュメントで確認できます。

https://docs.aws.amazon.com/vpc/latest/privatelink/aws-services-cross-region-privatelink-support.html

また、以下のAWS CLIコマンドで、特定のリージョンからアクセス可能なクロスリージョン対応サービスを確認することもできます。

ap-northeast-1からus-west-2aへクロスリージョン接続可能なサービス名を取得
aws ec2 describe-vpc-endpoint-services \
  --filters Name=service-type,Values=Interface Name=owner,Values=amazon \
  --region ap-northeast-1 \
  --service-region us-east-1 \
  --query ServiceNames
2025/11/20時点の出力例
[
    "com.amazonaws.iam",
    "com.amazonaws.route53",
    "com.amazonaws.us-east-1.ecr.api",
    "com.amazonaws.us-east-1.ecr.dkr",
    "com.amazonaws.us-east-1.ecs",
    "com.amazonaws.us-east-1.ecs-fips",
    "com.amazonaws.us-east-1.kinesis-firehose",
    "com.amazonaws.us-east-1.kinesisanalytics",
    "com.amazonaws.us-east-1.kinesisanalytics-fips",
    "com.amazonaws.us-east-1.kms",
    "com.amazonaws.us-east-1.kms-fips",
    "com.amazonaws.us-east-1.lambda",
    "com.amazonaws.us-east-1.s3"
]

具体的なメリット

AWSサービスへクロスリージョンで接続する場合に、どのようなメリットがあるか思いついたものを記載します。

1. セキュリティの向上

クロスリージョンでのデータ転送が必要な場合でも、インターネットを経由せずにAWSのプライベートネットワーク内で通信が完結します。
これにより、データ漏洩やセキュリティリスクを最小限に抑えることができます。

2. アーキテクチャの簡素化

従来、クロスリージョンでAWSサービスへのプライベート接続を実現するには、以下のような構成が必要でした。

  • VPCピアリング
  • Transit Gateway
  • VPN接続の構成

今回のアップデートにより、
VPCエンドポイントを作成するだけでクロスリージョン接続が可能になり、ネットワーク構成が大幅にシンプルになります。

3. リージョンで提供されていないサービスの利用

特定のリージョンでまだ提供されていないAWSサービスや機能を、別のリージョンから利用できるようになりそうです。
これにより、各リージョンでのサービス提供状況に左右されずに、柔軟なアーキテクチャ設計が可能になります。

ただし現状で対応しているサービスはごく一部のため、今後の対応サービスの増加に伴ってメリットを感じられそうです。

4. データレジデンシー要件への対応

グローバルに分散したシステムを構築する際、データの所在地に関する法規制やコンプライアンス要件に対応する必要があります。

クロスリージョンAWS PrivateLinkを使用することで、データを特定のリージョンに保持しながら、他のリージョンから安全にアクセスできます。

制限事項と注意点

クロスリージョンAWS PrivateLinkを使用する際には、いくつかの制限事項と注意すべき点があります。
この辺りの制約事項についても以下ドキュメントに記載があります。

https://docs.aws.amazon.com/vpc/latest/privatelink/aws-services-cross-region-privatelink-support.html

IAM権限の設定が必要

デフォルトでは、IAMエンティティには別のリージョンのAWSサービスへアクセスする権限がありません。
クロスリージョン接続を許可するには、vpce:AllowMultiRegionという権限専用アクション(permission-only action)を含むIAMポリシーを作成する必要があります。

また、Service Control Policy (SCP)でもこのアクションが拒否されていないことを確認する必要があります。
クロスリージョン接続機能を使用するには、IDポリシーとSCPの両方でこのアクションを許可する必要があります。

なお、VPCエンドポイント作成時にサービスリージョンとして指定できるリージョンを制御したい場合は、ec2:VpceServiceRegion条件キーを使用できます。

リージョンDNSの使用が必須

クロスリージョンでAWSサービスにアクセスする際は、リージョナルDNSを使用する必要があります。ゾーンDNS(Availability Zone固有のDNS)はサポートされていません。

一部のアベイラビリティゾーンは非対応

以下のアベイラビリティゾーンではクロスリージョン接続がサポートされていません。

  • use1-az3 (US East 1)
  • usw1-az2 (US West 1)
  • apne1-az3 (Asia Pacific Tokyo)
  • apne2-az2 (Asia Pacific Seoul)
  • apne2-az4 (Asia Pacific Seoul)

高可用性の考慮

AWS PrivateLinkは、サービスリージョンとコンシューマーリージョンの両方でアベイラビリティゾーン間のフェイルオーバーを管理します。
ただし、リージョン間のフェイルオーバーは管理しません。

高可用性を確保するには、少なくとも2つのアベイラビリティゾーンにクロスリージョン対応のInterfaceエンドポイントを作成することが推奨されます。
なお、プロバイダー(サービス側)とコンシューマー(接続側)で同じアベイラビリティゾーンを使用する必要はありません。

検証してみる

では実際にクロスリージョンでAWS PrivateLink(インターフェース型)を使用する構成をTerraformで実装してみます。
この例では、ap-northeast-1(東京)リージョンのVPCから、us-east-1(バージニア北部)リージョンのAmazon S3へプライベートに接続する構成を作成します。

マネジメントコンソールからの見え方

まずはマネジメントコンソールからの見え方についてです。

VPCエンドポイント作成画面にて以下のように「サービスリージョン」という項目が増えており、

スクリーンショット 2025-11-20 20.28.02

ここで「クロスリージョンエンドポイントを有効にする」をクリックしてリージョンを選択すると、利用できるサービスが表示されます。

スクリーンショット 2025-11-20 20.28.15

また作成したVPCエンドポイントのサービス名はサービスリージョンのものとなります。

スクリーンショット 2025-11-20 20.48.50

Terraformコード

以下は、クロスリージョンPrivateLink接続を検証するためのTerraformコードです。
プライベートサブネットのEC2からPrivateLinkでクロスリージョン接続が利用できる確認します。

main.tf
# ==========================================
# Terraform設定
# ==========================================
terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# ==========================================
# 変数定義
# ==========================================
variable "vpc_cidr" {
  description = "VPCのCIDRブロック"
  type        = string
  default     = "10.0.0.0/16"
}

variable "project_name" {
  description = "プロジェクト名(リソースのタグに使用)"
  type        = string
  default     = "privatelink-cross-region-test"
}

locals {
  # プライベートサブネット定義
  private_subnets = {
    private_1 = {
      cidr_index = 1
      az_index   = 0
    }
    private_2 = {
      cidr_index = 2
      az_index   = 1
    }
  }

  # VPCエンドポイント定義
  vpc_endpoints = {
    ssm         = "com.amazonaws.ap-northeast-1.ssm"
    ssmmessages = "com.amazonaws.ap-northeast-1.ssmmessages"
  }

  # セキュリティグループ定義
  security_groups = {
    vpc_endpoint = {
      description = "Security group for VPC endpoints"
      ingress = [{
        description = "HTTPS from VPC"
        from_port   = 443
        to_port     = 443
        protocol    = "tcp"
        cidr_blocks = [var.vpc_cidr]
      }]
    }
    ec2 = {
      description = "Security group for test EC2 instance"
      ingress     = []
    }
  }
}

# ==========================================
# データソース
# ==========================================
data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

# ==========================================
# VPC
# ==========================================
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name    = "${var.project_name}-vpc"
    Project = var.project_name
  }
}

# ==========================================
# プライベートサブネット
# ==========================================
resource "aws_subnet" "private" {
  for_each = local.private_subnets

  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, each.value.cidr_index)
  availability_zone = data.aws_availability_zones.available.names[each.value.az_index]

  tags = {
    Name    = "${var.project_name}-${each.key}-subnet"
    Project = var.project_name
  }
}

# プライベートサブネット用のルートテーブル
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name    = "${var.project_name}-private-rt"
    Project = var.project_name
  }
}

# プライベートサブネットとルートテーブルの関連付け
resource "aws_route_table_association" "private" {
  for_each = local.private_subnets

  subnet_id      = aws_subnet.private[each.key].id
  route_table_id = aws_route_table.private.id
}

# ==========================================
# セキュリティグループ
# ==========================================
resource "aws_security_group" "main" {
  for_each = local.security_groups

  name        = "${var.project_name}-${each.key}-sg"
  description = each.value.description
  vpc_id      = aws_vpc.main.id

  # すべてのアウトバウンド通信を許可
  egress {
    description = "Allow all outbound"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name    = "${var.project_name}-${each.key}-sg"
    Project = var.project_name
  }
}

# セキュリティグループのIngressルール
resource "aws_security_group_rule" "ingress" {
  for_each = {
    for item in flatten([
      for sg_key, sg in local.security_groups : [
        for idx, rule in sg.ingress : {
          key         = "${sg_key}-${idx}"
          sg_key      = sg_key
          description = rule.description
          from_port   = rule.from_port
          to_port     = rule.to_port
          protocol    = rule.protocol
          cidr_blocks = rule.cidr_blocks
        }
      ]
    ]) : item.key => item
  }

  type              = "ingress"
  security_group_id = aws_security_group.main[each.value.sg_key].id
  description       = each.value.description
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  protocol          = each.value.protocol
  cidr_blocks       = each.value.cidr_blocks
}

# ==========================================
# クロスリージョンVPCエンドポイント
# ==========================================
resource "aws_vpc_endpoint" "s3_cross_region" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.s3"
  vpc_endpoint_type   = "Interface"
  service_region      = "us-east-1"
  subnet_ids          = values(aws_subnet.private)[*].id
  security_group_ids  = [aws_security_group.main["vpc_endpoint"].id]
  private_dns_enabled = true

  timeouts {
    create = "20m"
  }

  tags = {
    Name    = "${var.project_name}-s3-cross-region-endpoint"
    Project = var.project_name
  }
}

# ==========================================
# SSM Session Manager用のVPCエンドポイント
# ==========================================
resource "aws_vpc_endpoint" "interface" {
  for_each = local.vpc_endpoints

  vpc_id              = aws_vpc.main.id
  service_name        = each.value
  vpc_endpoint_type   = "Interface"
  subnet_ids          = values(aws_subnet.private)[*].id
  security_group_ids  = [aws_security_group.main["vpc_endpoint"].id]
  private_dns_enabled = true

  tags = {
    Name    = "${var.project_name}-${each.key}-endpoint"
    Project = var.project_name
  }
}

# ==========================================
# 検証用EC2インスタンスのIAM設定
# ==========================================
resource "aws_iam_role" "ec2_role" {
  name = "ec2-privatelink-test-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "cross_region_policy" {
  name = "cross-region-access-policy"
  role = aws_iam_role.ec2_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = "vpce:AllowMultiRegion"
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "s3:*"
        ]
        Resource = "*"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "ssm_policy" {
  role       = aws_iam_role.ec2_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ec2_profile" {
  name = "ec2-privatelink-test-profile"
  role = aws_iam_role.ec2_role.name
}

# ==========================================
# 検証用EC2インスタンス
# ==========================================
resource "aws_instance" "test_instance" {
  ami                         = data.aws_ami.amazon_linux_2023.id
  instance_type               = "t3.micro"
  subnet_id                   = aws_subnet.private["private_1"].id
  iam_instance_profile        = aws_iam_instance_profile.ec2_profile.name
  vpc_security_group_ids      = [aws_security_group.main["ec2"].id]
  associate_public_ip_address = false

  tags = {
    Name    = "${var.project_name}-test-instance"
    Project = var.project_name
  }
}

# ==========================================
# 出力
# ==========================================
output "s3_cross_region_endpoint_id" {
  description = "クロスリージョンS3 VPCエンドポイントのID"
  value       = aws_vpc_endpoint.s3_cross_region.id
}

output "s3_cross_region_endpoint_dns" {
  description = "S3 VPCエンドポイントのDNSエントリ"
  value       = aws_vpc_endpoint.s3_cross_region.dns_entry
}

output "test_instance_id" {
  description = "検証用EC2インスタンスのID"
  value       = aws_instance.test_instance.id
}

なおクロスリージョン接続のVPCエンドポイントは作成まで結構時間がかかるようで、私の環境では10数分程度かかりました。
デフォルトだとTerraformの10分のタイムアウトに引っかかったので、Terraformで実行する場合はタイムアウト時間を伸ばしてあげたほうが良さそうです。

╷
│ Error: waiting for EC2 VPC Endpoint (com.amazonaws.us-east-1.s3) create: timeout while waiting for state to become 'available, pendingacceptance' (last state: 'pending', timeout: 10m0s)
│ 
│   with aws_vpc_endpoint.s3_cross_region,
│   on main.tf line 197, in resource "aws_vpc_endpoint" "s3_cross_region":
│  197: resource "aws_vpc_endpoint" "s3_cross_region" {
│ 
╵
╷
│ Error: Missing Resource Identity After Create: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource create. This is always a problem with the provider and should be reported to the provider developer
│ 
│   with aws_vpc_endpoint.s3_cross_region,
│   on main.tf line 197, in resource "aws_vpc_endpoint" "s3_cross_region":
│  197: resource "aws_vpc_endpoint" "s3_cross_region" {
│ 
╵

アクセス確認

Terraformで環境を構築した後、AWS Systems Manager Session Managerでプライベートサブネット内のEC2インスタンスに接続します。

# AWS CLIでSession Managerを使用して接続
aws ssm start-session --target i-xxxxxxxxx --region ap-northeast-1

EC2インスタンスに接続できたら、インターネット接続がないことを確認します。
コマンドが失敗するので、プライベート環境であることが確認できます。

# インターネット接続の確認(失敗するはず)
ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2113ms

AWS CLIでus-east-1リージョンを指定すると、VPCエンドポイント経由でus-east-1リージョンのS3にアクセスできることが確認できます。

# S3バケットの一覧を取得(us-east-1リージョン)
$ aws s3 ls --region us-east-1

2025-11-05 09:52:13 xxx-s3
2025-02-12 04:42:27 yyy-s3
2025-02-12 04:42:19 zzz-s3

そして ap-northeast-1 を指定した場合は応答が返ってきません。

$ aws s3 ls --region ap-northeast-1

デバックオプションで見てみると、
ap-northeast-1のエンドポイントを参照しているため、アクセスができていないようです。

2025-11-20 11:53:05,982 - MainThread - botocore.httpsession - DEBUG - Certificate path: /etc/pki/tls/certs/ca-bundle.crt
2025-11-20 11:53:05,982 - MainThread - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): s3.ap-northeast-1.amazonaws.com:443

念の為、名前解決の結果を見てみるとちゃんとサービスリージョンで指定した通り、us-east-1のVPCエンドポイントを参照していることがわかります。

$ nslookup s3.us-east-1.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   s3.us-east-1.amazonaws.com
Address: 10.0.2.8
Name:   s3.us-east-1.amazonaws.com
Address: 10.0.1.246

さいごに

以上、AWSサービスのAWS PrivateLinkがクロスリージョン接続をサポートするようになったアップデートをまとめました。

従来は複雑なネットワーク構成が必要だったクロスリージョンでのプライベート接続が、VPCエンドポイントの作成だけで実現できるようになったのは大きな進化ですね。
特にセキュリティ要件が厳しいシステムや、データレジデンシーへの対応が必要なグローバルアプリケーションの設計において、非常に有用な選択肢になると思います。

ただし、現状で対応しているAWSサービスが少なかったりサポート対象のアベイラビリティゾーンに制限があったり、
いくつか注意すべき点もあるので、実際に導入する際はしっかりと要件を確認した上で採用しましょう。

この記事をシェアする

FacebookHatena blogX

関連記事