初めての Direct Connect 〜 Route 53 Resolver を添えて 〜

初めての Direct Connect 〜 Route 53 Resolver を添えて 〜

Clock Icon2025.06.20

はじめに

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

先日、ご縁があって Direct Connect の検証を実施してきました!
本記事では検証した結果をまとめています。

また今回のオンプレミス側の環境は、NTT 東日本様の Managed SD-WAN を利用させていただきました。
検証環境をご提供いただきありがとうございます、この場を借りてお礼を申し上げます。🙇🙇🙇

https://business.ntt-east.co.jp/service/sd-wan/
https://business.ntt-east.co.jp/service/cloudgateway/

なお今回はたまたまご縁があり、特別に検証環境をご提供いただけました。
そのため基本的には、一般向けに検証環境の提供はされていないことをご了承ください。

ネットワーク構成

まず構成は以下の通りです。
(Route 53 Resolver も含めた構成は後ほどご紹介します)

architecture-base.drawio

検証アカウントに直接 DirectConnect を接続するのではなく、
オンプレミス - 別AWSアカウントのDXGW - 検証AWSアカウントのVGW という流れになっています。
(お恥ずかしながら、この検証をするまで VGW へ別アカウントの DXGW アタッチできることを知りませんでした…。)

なお今回は私は検証 AWS アカウント側の作業のみ実施しております。
別 AWS アカウント側 (DXGW など) の設定も含めた、作業全体の流れについてはよろしければ以下をご参照ください。

https://dev.classmethod.jp/articles/gx-gateway-multi-account/
https://dev.classmethod.jp/articles/dxgw-share-20220127-ver/

事前準備

当日検証するまでに実施した作業です。

AWS 側設定

まずは検証アカウント側にリソースを作成しました。
構成図上だと以下赤枠内のリソースです。

architecture-base-aws.drawio

今回は Terraform でまとめて作成しています。

main.tf
provider "aws" {
  region = var.aws_region
}

data "aws_ami" "amazonlinux_2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-2023*-kernel-*-x86_64"]
  }
}

data "http" "ipv4_icanhazip" {
  url = "http://ipv4.icanhazip.com/"
}

locals {
  app_name  = "directconnect-test"
  global_ip = chomp(data.http.ipv4_icanhazip.body)
}

variable "aws_region" {
  type    = string
  default = "ap-northeast-1"
}

variable "on_premises_cidr" {
  type    = string
}

variable "dx_gateway_id" {
  type    = string
}

variable "dx_gateway_owner_account_id" {
  type    = string
}

# ネットワーク
resource "aws_vpc" "main" {
  cidr_block           = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${local.app_name}-vpc"
  }
}

resource "aws_subnet" "private_1a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.10.1.0/24"
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "${local.app_name}-private-1a-subnet"
  }
}

resource "aws_subnet" "private_1c" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.10.2.0/24"
  availability_zone = "ap-northeast-1c"
  tags = {
    Name = "${local.app_name}-private-1c-subnet"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.10.3.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
  tags = {
    Name = "${local.app_name}-public-subnet"
  }
}

# ルートテーブル
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = var.on_premises_cidr
    gateway_id = aws_vpn_gateway.vgw.id
  }

  tags = {
    Name = "${local.app_name}-private-rt"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "${local.app_name}-public-rt"
  }
}

resource "aws_route_table_association" "private_1a" {
  subnet_id      = aws_subnet.private_1a.id
  route_table_id = aws_route_table.private.id
}

resource "aws_route_table_association" "private_1c" {
  subnet_id      = aws_subnet.private_1c.id
  route_table_id = aws_route_table.private.id
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

## Gateway
resource "aws_vpn_gateway" "vgw" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${local.app_name}-vgw"
  }
}

resource "aws_dx_gateway_association_proposal" "proposal" {
  dx_gateway_id               = var.dx_gateway_id
  associated_gateway_id       = aws_vpn_gateway.vgw.id
  allowed_prefixes            = [aws_vpc.main.cidr_block]
  dx_gateway_owner_account_id = var.dx_gateway_owner_account_id
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${local.app_name}-igw"
  }
}

## EC2
resource "aws_instance" "private_ec2" {
  ami                         = data.aws_ami.amazonlinux_2023.id
  instance_type               = "t3.micro"
  subnet_id                   = aws_subnet.private_1a.id
  associate_public_ip_address = false
  vpc_security_group_ids      = [aws_security_group.private_ec2_sg.id]
  iam_instance_profile        = aws_iam_instance_profile.main.name
  tags = {
    Name = "${local.app_name}-private-ec2"
  }
}

resource "aws_instance" "public_ec2" {
  ami                         = data.aws_ami.amazonlinux_2023.id
  instance_type               = "t3.micro"
  subnet_id                   = aws_subnet.public.id
  associate_public_ip_address = true
  vpc_security_group_ids      = [aws_security_group.public_ec2_sg.id]
  iam_instance_profile        = aws_iam_instance_profile.main.name
  tags = {
    Name = "${local.app_name}-public-ec2"
  }
}

# セキュリティグループ
resource "aws_security_group" "private_ec2_sg" {
  name   = "private-ec2-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = [var.on_premises_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.app_name}-private-ec2-sg"
  }
}

resource "aws_security_group" "public_ec2_sg" {
  name   = "public-ec2-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["${local.global_ip}/32"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.app_name}-public-ec2-sg"
  }
}

# IAM
resource "aws_iam_role" "main" {
  name = "${local.app_name}-ssm-role-for-ec2"

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

  tags = {
    Name = "${local.app_name}-ssm-role-for-ec2"
  }
}

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

resource "aws_iam_instance_profile" "main" {
  name = "${local.app_name}-ssm-instance-profile"
  role = aws_iam_role.main.name
}

オンプレミス 側設定

次はオンプレミス側の設定です、
構成図上だと以下赤枠内のリソースです。

architecture-base-onpremise.drawio

なおオンプレミス側のルーター等の設定は NTT 東日本様にてご対応いただきました。
ありがとうございました!🙇🙇🙇

ローカル端末 設定

今回は DHCP サーバーが存在しない環境だったため、
ローカル端末 (Mac) でルーティングを設定する必要がありました。

ルーティング設定

システム設定 > ネットワーク から Ethernet ポートの設定をします。
設定項目は以下の通りです、デフォルトだと自動になっているので手動にして設定ます。

  • IP アドレス - ローカル CIDR の中から適当に選ぶ
  • サブネットマスク - ローカル CIDR のサブネットマスク
  • ルーター - デフォルトゲートウェイ (ルータの IP)

Pasted image 20250618161328

Cloudflare WARP

こちらは利用している場合限定ですが、
Cloudflare WARP を無効化する必要がありました。

https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/

スクリーンショット 2025-06-10 11.47.23

どう言うことかと言うと、
Cloudflare WARP を有効化した状態だと以下のようにデフォルトルートが 2 つある状態となります。
この状態だと DirectConnect 向けのデフォルトルート (1 行目) の経路に到達できませんでした。

% netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags               Netif Expire
default            192.168.251.201    UGScg               en0
default            link#19            UCSIg               utun4

2 行目のデフォルトルートが Cloudflare WARP のデフォルトルートなので、無効化すると消えます。
この状態だと DirectConnect 向けのデフォルトルート (1 行目) の経路に行けました。

% netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags               Netif Expire
default            192.168.251.201    UGScg               en0

何らかの設定により Cloudflare WARP を有効化した状態でも DirectConnect 側のルートに到達できそうな気もします
が、今回は時間が限られていたので無効化することにしました。

追記:
Split Tunnels を設定することで、
特定のドメインもしくはIPアドレスに関して、Cloudflareネットワークを経由せず、直接通信できるようです。

https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/route-traffic/split-tunnels/

ただあくまで仮説なので、この部分は次回リベンジで検証しようと思います。
中村さん、情報連携ありがとうございます!

検証内容

インターネット / Direct Connect 比較

まずはインターネットと Direct Connect の経路を比較してみました。

インターネット

インターネット経由で AWS に接続する経路です。
なお今回は Managed SD-WAN のセキュアインターネットを利用しています。
https://business.ntt-east.co.jp/service/sd-wan/

architecture-connect-internet.drawio

  • 通信経路

18ホップと多段階を経由しており、SD-WANのセキュアインターネット → プロバイダ → AWS の経路で到達していることがわかります。
なお途中の* * *はICMPを応答しないルーターです。

% traceroute -I 52.194.243.124
traceroute to 52.194.243.124 (52.194.243.124), 64 hops max, 48 byte packets
 1  192.168.251.201 (192.168.251.201)  0.751 ms  0.278 ms  0.178 ms
 2  52.194.243.124 (52.194.243.124)  4.140 ms  4.506 ms  4.665 ms
 3  10.128.XXX.XXX (10.128.XXX.XXX)  5.069 ms  4.850 ms  4.904 ms
 4  10.128.YYY.YYY (10.128.YYY.YYY)  5.637 ms  5.913 ms  5.609 ms
 5  211.132.176.129 (211.132.176.129)  6.851 ms  6.303 ms  6.604 ms
 6  100.88.63.5 (100.88.63.5)  6.246 ms  6.202 ms  6.378 ms
 7  * * *
 8  211.9.234.1 (211.9.234.1)  7.483 ms  6.987 ms  6.678 ms
 9  211.9.234.57 (211.9.234.57)  23.823 ms  17.691 ms  7.533 ms
10  101.203.88.207 (101.203.88.207)  7.887 ms  7.885 ms  8.051 ms
11  52.93.66.84 (52.93.66.84)  9.118 ms  10.403 ms  9.746 ms
12  15.230.152.113 (15.230.152.113)  9.406 ms  8.889 ms  9.218 ms
13  * * *
14  * * *
15  * * *
16  * * *
17  * * *
18  52.194.243.124 (52.194.243.124)  9.240 ms  9.594 ms  9.125 ms
  • レイテンシー / パケットロス

500パケットで0%のパケットロス、
また平均9.327msで標準偏差0.412msのため、安定したパフォーマンスと言えますね。
またmtr結果の2ホップ目で4.7msのため、ネットワーク品質も良好そうです。

% ping -c 500 52.194.243.124
PING 52.194.243.124 (52.194.243.124): 56 data bytes

--- 52.194.243.124 ping statistics ---
500 packets transmitted, 500 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 8.494/9.327/14.083/0.412 ms
% sudo mtr -r -c 500 52.194.243.124
Password:
Start: 2025-06-10T15:42:59+0900
HOST: HL01678.local               Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|-- 192.168.251.201            0.0%   500    1.0   0.7   0.4   8.5   0.4
  2.|-- 52.194.243.124             0.0%   500    4.7   4.7   4.2  13.0   0.5

Direct Connect

Managed SD-WAN から Direct Connect(クラウドゲートウェイクロスコネクト) を経由して AWS に接続する経路です。

architecture-connect-directconnect.drawio

  • 通信経路

6ホップで途中がローカルIPのみのため、SD-WAN → Direct Connect → VGW のルートを通っていることがわかります。
インターネット経由と比較してホップ数が大幅に削減され、その分パケットロスなどの障害点も減少していると言えそうです。

% traceroute 10.10.1.236
traceroute to 10.10.1.236 (10.10.1.236), 64 hops max, 40 byte packets
 1  ip-192-168-251-201 (192.168.251.201)  1.166 ms  0.482 ms  0.233 ms
 2  ip-10-10-1-236 (10.10.1.236)  4.494 ms  4.676 ms  4.264 ms
 3  ip-10-128-XXX-XXX (10.128.XXX.XXX)  4.751 ms  5.170 ms  5.231 ms
 4  ip-10-128-YYY-YYY (10.128.YYY.YYY)  6.883 ms  6.290 ms  6.272 ms
 5  ip-10-128-ZZZ-ZZZ (10.128.ZZZ.ZZZ)  6.836 ms  7.036 ms  7.015 ms
 6  ip-10-10-1-236 (10.10.1.236)  10.048 ms  9.948 ms  9.412 ms
  • レイテンシー / パケットロス

平均9.873msで、ほぼ誤差ですがインターネット経由よりも若干高い結果でした、
これは地理的要因(Direct Connect拠点経由の物理的距離)が影響しているかもしれません。
なおパケットロスは0%なので、専用線としての安定性が担保されていることがわかります。

% ping -c 500 10.10.1.236
PING 10.10.1.236 (10.10.1.236): 56 data bytes

--- 10.10.1.236 ping statistics ---
500 packets transmitted, 500 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 9.106/9.873/38.271/1.359 ms
% sudo mtr -r -c 500 10.10.1.236
Password:
Start: 2025-06-10T16:07:44+0900
HOST: ip-192-168-251-222.ap-north Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|-- ip-192-168-251-201.ap-nor  0.0%   500    0.6   0.7   0.4   4.7   0.3
  2.|-- ip-10-10-1-236.ap-northea  0.0%   500    4.8   4.7   4.0  11.8   0.5

比較してみた所感

というわけで簡単ですが比べてみた所感は以下の通りです、
なお本当に正確に計測する場合はもっと時間をかける必要があるので、今回の結果はあくまでご参考程度にお願いします。
(本当はiperf3で帯域を計測したかったのですが、うまくいかないので今回は割愛しました…。)

  1. レイテンシー面での意外な結果 - Direct Connectの方が若干レイテンシーが高い(9.873ms / 9.327ms)
  2. 経路の単純化 - 専用線の方がホップ数が少ないため、結果的にパケットロスなどの障害点が減少してそう
  3. 安定性 - 両方とも0%パケットロスで優秀、しかしDirect Connectは専用線としての帯域保証がある
  4. セキュアインターネットの品質 - インターネット経由でも十分なパフォーマンスを実現している
  5. 総合評価 - レイテンシーでは差がないものの、帯域保証・セキュリティ・可用性の観点ではDirect Connectが有利そう

Direct Connect 経由で VPC エンドポイントへアクセス

次にローカル端末から Direct Connect を経由して AWS へ接続し、
VPC エンドポイントを経由して AWS サービスへアクセスする検証です。

構成

構成は記事冒頭のものをちょっと更新して、以下の通りです。
赤線が名前解決する際のルートで、
青線が名前解決後に AWS サービスへアクセスするルートです。

architecture-dns.drawio

AWS 側のプライベートサブネットの VPC エンドポイント (インターフェース) を作成し、
VPC エンドポイント向けのホストゾーンを作成します。
そしてローカルから名前解決するために、Route53 Resolver Inbound Endpoint を作成します。

AWS 側

まず記事冒頭の Terraform コードと同じディレクトリで、以下を実行します。
ここで VPC エンドポイント、インバウンドエンドポイント、ホストゾーンの作成を行なっています。

dns1.tf
# S3 VPCエンドポイント
resource "aws_vpc_endpoint" "s3_interface" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.${var.aws_region}.s3"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_1a.id, aws_subnet.private_1c.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
  private_dns_enabled = false
  tags = {
    Name = "${local.app_name}-s3-endpoint"
  }
}

# DynamoDB VPCエンドポイント
resource "aws_vpc_endpoint" "dynamodb_interface" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.${var.aws_region}.dynamodb"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_1a.id, aws_subnet.private_1c.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
  private_dns_enabled = false
  tags = {
    Name = "${local.app_name}-dynamodb-endpoint"
  }
}

# VPCエンドポイント用 セキュリティグループ
resource "aws_security_group" "endpoint_sg" {
  name   = "${local.app_name}-vpc-endpoint-security-group"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.main.cidr_block, var.on_premises_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.app_name}-vpc-endpoint-security-group"
  }
}

# Route 53 Resolver用のセキュリティグループ
resource "aws_security_group" "resolver_sg" {
  name   = "${local.app_name}-route53-resolver-security-group"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 53
    to_port     = 53
    protocol    = "tcp"
    cidr_blocks = [var.on_premises_cidr]
  }

  ingress {
    from_port   = 53
    to_port     = 53
    protocol    = "udp"
    cidr_blocks = [var.on_premises_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.app_name}-route53-resolver-security-group"
  }
}

# Route 53 Resolver インバウンドエンドポイント
resource "aws_route53_resolver_endpoint" "inbound" {
  name      = "${local.app_name}-inbound-resolver-endpoint"
  direction = "INBOUND"

  security_group_ids = [aws_security_group.resolver_sg.id]

  ip_address {
    subnet_id = aws_subnet.private_1a.id
  }

  ip_address {
    subnet_id = aws_subnet.private_1c.id
  }

  tags = {
    Name = "${local.app_name}-inbound-resolver-endpoint"
  }
}

# VPCエンドポイント用のプライベートホストゾーン
resource "aws_route53_zone" "s3_endpoint" {
  name = "s3.${var.aws_region}.amazonaws.com"

  vpc {
    vpc_id = aws_vpc.main.id
  }

  depends_on = [aws_vpc_endpoint.s3_interface]
  tags = {
    Name = "${local.app_name}-s3-endpoint-private-zone"
  }
}

resource "aws_route53_zone" "dynamodb_endpoint" {
  name = "dynamodb.${var.aws_region}.amazonaws.com"

  vpc {
    vpc_id = aws_vpc.main.id
  }

  depends_on = [aws_vpc_endpoint.dynamodb_interface]
  tags = {
    Name = "${local.app_name}-dynamodb-endpoint-private-zone"
  }
}

次に以下の Terraform コードを実行します。
ここでは ホストゾーンに VPC エンドポイントのローカル IP を登録しています。
(本当は上のコードとまとめたかったんですが、toset で VPC エンドポイントの IP を参照している関係で、分けて実行しています)

dns2.tf
# VPCエンドポイント作成後にデプロイ

# S3エンドポイントENIのIPアドレス取得
data "aws_network_interface" "s3_eni" {
  for_each = toset(aws_vpc_endpoint.s3_interface.network_interface_ids)
  id       = each.value
}

# DynamoDBエンドポイントENIのIPアドレス取得
data "aws_network_interface" "dynamodb_eni" {
  for_each = toset(aws_vpc_endpoint.dynamodb_interface.network_interface_ids)
  id       = each.value
}

# VPCエンドポイントのDNSレコード(S3)
resource "aws_route53_record" "s3_endpoint" {
  zone_id = aws_route53_zone.s3_endpoint.zone_id
  name    = "s3.${var.aws_region}.amazonaws.com"
  type    = "A"
  ttl     = "300"
  records = [for eni in data.aws_network_interface.s3_eni : eni.private_ip]
}

# S3サブドメイン用のワイルドカードレコード
resource "aws_route53_record" "s3_endpoint_wildcard" {
  zone_id = aws_route53_zone.s3_endpoint.zone_id
  name    = "*.s3.${var.aws_region}.amazonaws.com"
  type    = "A"
  ttl     = "300"
  records = [for eni in data.aws_network_interface.s3_eni : eni.private_ip]
}

# VPCエンドポイントのDNSレコード(DynamoDB)
resource "aws_route53_record" "dynamodb_endpoint" {
  zone_id = aws_route53_zone.dynamodb_endpoint.zone_id
  name    = "dynamodb.${var.aws_region}.amazonaws.com"
  type    = "A"
  ttl     = "300"
  records = [for eni in data.aws_network_interface.dynamodb_eni : eni.private_ip]
}

# DynamoDBサブドメイン用 ワイルドカードレコード
resource "aws_route53_record" "dynamodb_endpoint_wildcard" {
  zone_id = aws_route53_zone.dynamodb_endpoint.zone_id
  name    = "*.dynamodb.${var.aws_region}.amazonaws.com"
  type    = "A"
  ttl     = "300"
  records = [for eni in data.aws_network_interface.dynamodb_eni : eni.private_ip]
}

ローカル端末

次に システム設定 > ネットワーク から Ethernet ポートの名前解決の設定をします。
ここには作成した Route53 Inbound Endpoint のローカル IP アドレスを設定します。

Pasted image 20250618164846

名前解決結果

結果は以下のとおりで、
プライベートホストゾーンに設定したドメイン名で、VPC エンドポイントの IP アドレスが返却されているので名前解決できていることがわかります。

% nslookup s3.ap-northeast-1.amazonaws.com
Server:         10.10.1.92
Address:        10.10.1.92#53

Non-authoritative answer:
Name:   s3.ap-northeast-1.amazonaws.com
Address: 10.10.2.11
Name:   s3.ap-northeast-1.amazonaws.com
Address: 10.10.1.48

% nslookup dynamodb.ap-northeast-1.amazonaws.com
Server:         10.10.1.92
Address:        10.10.1.92#53

Non-authoritative answer:
Name:   dynamodb.ap-northeast-1.amazonaws.com
Address: 10.10.1.85
Name:   dynamodb.ap-northeast-1.amazonaws.com
Address: 10.10.2.10
% dig s3.ap-northeast-1.amazonaws.com         

; <<>> DiG 9.10.6 <<>> s3.ap-northeast-1.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43406
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;s3.ap-northeast-1.amazonaws.com. IN    A

;; ANSWER SECTION:
s3.ap-northeast-1.amazonaws.com. 300 IN A       10.10.1.48
s3.ap-northeast-1.amazonaws.com. 300 IN A       10.10.2.11

;; Query time: 19 msec
;; SERVER: 10.10.1.92#53(10.10.1.92)
;; WHEN: Tue Jun 10 16:20:06 JST 2025
;; MSG SIZE  rcvd: 92

% dig dynamodb.ap-northeast-1.amazonaws.com

; <<>> DiG 9.10.6 <<>> dynamodb.ap-northeast-1.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8141
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;dynamodb.ap-northeast-1.amazonaws.com. IN A

;; ANSWER SECTION:
dynamodb.ap-northeast-1.amazonaws.com. 300 IN A 10.10.1.85
dynamodb.ap-northeast-1.amazonaws.com. 300 IN A 10.10.2.10

;; Query time: 15 msec
;; SERVER: 10.10.1.92#53(10.10.1.92)
;; WHEN: Tue Jun 10 16:20:18 JST 2025
;; MSG SIZE  rcvd: 98

AWS CLI コマンド

次に AWS CLI 実際にVPC エンドポイントを利用してAWS サービスにアクセスできるか確認します。
結果は以下のとおりで、問題なくAWS サービスにアクセスできていることがわかります。

  • S3
# S3 バケット作成
% aws s3api create-bucket \
  --bucket directconnect-test-bucket \
  --region ap-northeast-1 \
  --create-bucket-configuration LocationConstraint=ap-northeast-1
{
    "Location": "http://directconnect-test-bucket.s3.amazonaws.com/"
}

# ファイルアップロード
% echo "Hello S3" > hello.txt
aws s3 cp hello.txt s3://directconnect-test-bucket/;
upload: ./hello.txt to s3://directconnect-test-bucket/hello.txt

% aws s3 ls
2025-06-10 16:28:59 directconnect-test-bucket

% aws s3 ls s3://directconnect-test-bucket/
2025-06-10 16:30:02          9 hello.txt

# ファイルダウンロード
% aws s3 cp s3://directconnect-test-bucket/hello.txt ./hello2.txt
download: s3://directconnect-test-bucket/hello.txt to ./hello2.txt

# ファイル削除
% aws s3 rm s3://directconnect-test-bucket/hello.txt
delete: s3://directconnect-test-bucket/hello.txt

# S3 バケット削除
% aws s3api delete-bucket --bucket directconnect-test-bucket
% aws s3 ls
  • DynamoDB
# DynamoDB テーブル作成
% aws dynamodb create-table \
  --table-name directconnect-test-table \
  --attribute-definitions AttributeName=UserId,AttributeType=S \
  --key-schema AttributeName=UserId,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --region ap-northeast-1
{
    "TableDescription": {
        "AttributeDefinitions": [
            {
                "AttributeName": "UserId",
                "AttributeType": "S"
            }
        ],
        "TableName": "directconnect-test-table",
        "KeySchema": [
            {
                "AttributeName": "UserId",
                "KeyType": "HASH"
            }
        ],
        "TableStatus": "CREATING",
        "CreationDateTime": "2025-06-10T16:32:12.429000+09:00",
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 5,
            "WriteCapacityUnits": 5
        },
        "TableSizeBytes": 0,
        "ItemCount": 0,
        "TableArn": "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:table/directconnect-test-table",
        "TableId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "DeletionProtectionEnabled": false
    }
}

# テーブル一覧取得
% aws dynamodb list-tables --region ap-northeast-1               
{
    "TableNames": [
        "directconnect-test-table"
    ]
}

# テーブルにアイテム追加
% aws dynamodb put-item \
  --table-name directconnect-test-table \
  --item '{"UserId": {"S": "user123"}, "Name": {"S": "Taro"}, "Age": {"N": "30"}}' \
  --region ap-northeast-1

% aws dynamodb get-item \
  --table-name directconnect-test-table \
  --key '{"UserId": {"S": "user123"}}' \
    --region ap-northeast-1
{
    "Item": {
        "UserId": {
            "S": "user123"
        },
        "Age": {
            "N": "30"
        },
        "Name": {
            "S": "Taro"
        }
    }
}

# テーブルからアイテム削除
% aws dynamodb delete-item \
  --table-name directconnect-test-table \
  --key '{"UserId": {"S": "user123"}}' \
  --region ap-northeast-1

% aws dynamodb get-item \
  --table-name directconnect-test-table \
  --key '{"UserId": {"S": "user123"}}' \
    --region ap-northeast-1

# テーブル削除
% aws dynamodb delete-table \
  --table-name directconnect-test-table \
  --region ap-northeast-1
{
    "TableDescription": {
        "TableName": "directconnect-test-table",
        "TableStatus": "DELETING",
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 5,
            "WriteCapacityUnits": 5
        },
        "TableSizeBytes": 0,
        "ItemCount": 0,
        "TableArn": "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:table/directconnect-test-table",
        "TableId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "DeletionProtectionEnabled": false
    }
}

% aws dynamodb list-tables --region ap-northeast-1                                       
{
    "TableNames": []
}

(おまけ) CloudShell から DirectConnect 経由でローカル端末へアクセス

なんと VPC 内に作成した CloudShell から DirectConnect を経由して、
ローカル端末へアクセスできます!

architecture-cloudshell.drawio

VPC 内に作成した CloudShell には ENI が付与されるので、繋がるのは当たり前ではあるのですが、
ちょっと感動したので載せておきます。
なおその他にも VPC 内の様々なリソースに接続する例については、よろしければ以下の記事をご参照ください。

https://dev.classmethod.jp/articles/cloudshell-vpc-environment-connect-to-ec2-instances-rds-db-instances/

CloudShell 設定

設定は超簡単で、
マネジメントコンソールの CloudShell の画面のアクションから、
「Create VPC environment (max 2)」を選択して、

スクリーンショット 2025-06-18 16.58.21

Name と 今回利用している VPC、サブネット、セキュリティーグループを入力して、
Create するだけです。

スクリーンショット 2025-06-18 16.58.29

疎通結果は以下の通りで、
途中に追加しているローカルIPからDirect Connect を経由していることがわかります。

  • 通信経路
$ traceroute 192.168.251.222
traceroute to 192.168.251.222 (192.168.251.222), 30 hops max, 60 byte packets
 1  169.254.252.1 (169.254.252.1)  0.552 ms * *
 2  ip-10-128-XXX-XXX.ap-northeast-1.compute.internal (10.128.XXX.XXX)  3.115 ms  3.099 ms  3.160 ms
 3  ip-10-128-YYY-YYY.ap-northeast-1.compute.internal (10.128.YYY.YYY)  4.936 ms  5.657 ms  5.224 ms
 4  ip-10-128-ZZZ-ZZZ.ap-northeast-1.compute.internal (10.128.ZZZ.ZZZ)  4.447 ms  4.433 ms  4.418 ms
 5  ip-10-128-AAA-AAA.ap-northeast-1.compute.internal (10.128.AAA.AAA)  4.988 ms  4.971 ms  4.768 ms
 6  ip-192-168-251-222.ap-northeast-1.compute.internal (192.168.251.222)  8.923 ms  9.058 ms  9.118 ms
 7  ip-192-168-251-222.ap-northeast-1.compute.internal (192.168.251.222)  9.069 ms  9.038 ms *
  • 疎通
$ ping -c 20 192.168.251.222
PING 192.168.251.222 (192.168.251.222) 56(84) bytes of data.

--- 192.168.251.222 ping statistics ---
20 packets transmitted, 20 received, 0% packet loss, time 19031ms
rtt min/avg/max/mdev = 9.178/9.549/9.793/0.164 ms

さいごに

以上、Direct Connect で色々検証してみたでした。
これから Direct Connect を触る際の参考となれば幸いです。

なお Direct Connect をしっかり触るのはほぼ初めてだったこともあり、初歩的な検証が中心となってしまったため、
また機会があればリベンジしてみたいと思います!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.