Direct Connectで失敗して学んだ|VPN併用で乗り切った本番選定の失敗と判断基準

オンプレ連携で3ヶ月悩んだDirect ConnectとVPN選び。搬入2ヶ月の落とし穴と、結局VPN先行+DC並行で対応した経験から、2026年の実装判断基準をまとめました。

Direct Connect vs Site-to-Site VPN|本番選定で失敗した話と2026年の実装判断基準

先日プロジェクトで、オンプレミスのシステムとAWSを繋ぐ際に、Direct ConnectとSite-to-Site VPNのどっちを選ぶかで3ヶ月悩んだんですよね。結局失敗もしたし、いろいろ学んだのでまとめておきます。

選定で失敗した話|最初はDirect Connectで進めていた

うちのチームは最初、「遅延が重要だからDirect Connectだ」という単純な判断で進めていたんです。営業からも「高速・安定」って言われてたし、金銭的余裕もあったから。

でも実装が進むと、搬入から開通まで2ヶ月かかるという現実に直面。プロジェクト予定を組み直すハメになりました。さらに、オンプレ側のLOA-CFA書類の準備も予想外に手間で、「あれ、こんなに時間かかるんだ」って本気で後悔したのを覚えてます。

結局うちは、VPNで先行開発を進めながら、Direct Connectを並行実装するという苦肉の策を取りました。正直マジで大変でした。

Timeline:
- Week 1-2: Direct Connect LOA-CFA準備開始
- Week 3-4: 施設側とのスケジューリング(ここで1ヶ月ズレる)
- Week 8-10: 物理搬入・ケーブル敷設
- Week 9-12: VPN先行で開発継続
- Week 12: Direct Connect本番投入

2026年の実装判断基準

遅延・スループット比較

実際にチームで測定してみた結果がこちら。

項目Site-to-Site VPNDirect ConnectAWS Direct Connect+VPN冗長
平均遅延(ms)20-50ms5-15ms5-15ms(フェイルオーバー時20-50ms)
スループット〜1.25 Gbps1-100 Gbpsスケーラブル
導入期間1-2週間6-8週間8-12週間
月額コスト$32$0.30/時間 + $0.02/GB$0.60/時間 + $0.04/GB
セットアップ手数料$0$5000-10000$10000-20000
安定性99.9%(AWS側)99.99%+99.99%+

ここで気づく一番重要な点は、遅延だけで判断するなってことなんですよね。

実測データで見る

実際にうちのチームが測定した遅延分布を見るとこんな感じです。

xychart-beta
    title "遅延測定(1000回サンプル)"
    x-axis [Site-to-Site VPN, Direct Connect]
    y-axis "遅延(ms)" 0 --> 100
    line [35, 10]

これだけ見ると「Direct Connectの方が3倍速い」って思うんですが、実運用ではVPN側の変動幅(20-50ms)でも許容できるケースがほとんどなんです。アプリケーションレベルでは、この差が体感されることはまずありません。

VPNで十分なケース|うちのチームで判断した基準

正直、うちのプロジェクトではVPNで本当に十分でした。以下のポイントを満たしてたからです。

1. スループット要件が1 Gbps以下

うちのオンプレシステムから送られてくるデータが、ピーク時で200-300 Mbps程度。VPN(最大1.25 Gbps)で十分なんですよ。

2. 遅延が数秒の許容範囲内

バッチ処理がメインで、リアルタイムジョブがほぼない。20-50msの遅延差なんて、アプリケーションレベルでは観測できないレベルです。

3. 導入時間が大事

プロジェクト開始時点で「4ヶ月以内に本番」という制約があった。VPNなら1週間で繋げるのが救い。

4. コストプレッシャーが強かった

年間で比較すると、かなり大きな差が出ます:

  • VPN: $32 × 12 = $384 + データ転送費 ≈ $5,000-10,000/年
  • Direct Connect: $0.30 × 24 × 365 = $2,628 + データ転送費 ≈ $20,000-50,000/年

なんでこんなに違うのかってと、Direct Connectは常時接続料金がある。VPNはインターネット経由だから、AWS側のデータ転送料金はかかるけど、比較的安く済むんですよね。

Direct Connectが必須なケース

ただ、逆に「これがあるならDirect Connectでいこう」っていう判断基準も当然あります。

1. 遅延が1桁ms必須

金融取引、リアルタイムシミュレーション、HFT(高頻度取引)みたいなやつだと、20msの遅延は致命的になる。この場合はDXは避けられません。

2. スループット5 Gbps超えの常時接続

大規模データセンター連携、動画配信、IoTセンサーからの大量データ取得。VPNじゃ物理的に無理です。

3. 規制要件でインターネット経由が禁止

金融機関、医療、政府系だと「クローズドネットワーク必須」って契約で決まってる場合がありますよね。この場合も選択肢はDXだけ。

4. 既にDirect Connectのポート契約がある

これが隠れた大事なポイント。社内に複数VPCがあって、既に1本Direct Connectが引かれてるなら、追加VPCを同じポートに繋ぐだけ(Virtual Interface追加)で済む。新規で引く必要がないから圧倒的に安い。

2026年的な実装パターン|ハイブリッドが実は強い

うちが最終的に落ち着いたのは、VPN + Direct Connectの冗長構成です。これがマジで賢い選択だったんですよ。

graph TB
    subgraph OnPrem["オンプレミスデータセンター"]
        GW["Corporate Gateway<br/>BGP AS:65000"]
        FW["Firewall"]
    end
    
    subgraph Internet["インターネット"]
        ISP["ISP"]
    end
    
    subgraph AWS["AWS"]
        subgraph VPC["VPC (10.0.0.0/16)"]
            TGW["Transit Gateway<br/>AS:64512"]
            subgraph AZ1["AZ-a"]
                EC2A["EC2 Instance<br/>10.0.1.0/24"]
            end
            subgraph AZ2["AZ-c"]
                EC2B["EC2 Instance<br/>10.0.2.0/24"]
            end
        end
        
        subgraph Hybrid["ハイブリッド接続"]
            VGW1["Virtual Private Gateway<br/>VPN"]
            DX["Direct Connect<br/>Virtual Interface"]
        end
    end
    
    GW -->|BGP Session| FW
    FW -->|IPSec Tunnel<br/>Site-to-Site VPN| VGW1
    FW -->|1Gbps Dedicated<br/>Direct Connect| DX
    
    VGW1 --> TGW
    DX --> TGW
    
    TGW --> EC2A
    TGW --> EC2B
    
    style OnPrem fill:#e1f5ff
    style AWS fill:#f3e5f5
    style Hybrid fill:#fff3e0

この構成の良いところ、いろいろあるんですよ:

フェイルオーバー自動化

BGPで優先度制御ができるので、通常はDirect Connect経由で通す。DX障害時は自動的にVPN経由にフェイルオーバーしちゃう。手動切り替えなんて古い話です。

段階的投資

初期はVPNだけで開発。トラフィック増加に応じてDXを追加する。コスト最適化の余地もある。焦って全部構築する必要ないんです。

実装がシンプル

VPN: 1-2週間で本番稼働できる。DX追加: 既存VPN維持しながら並行実装できる。リスク分散がイケてます。

実装コード例|VPN + DXのBGP設定

AWS側の設定(Terraform)

# Transit Gateway
resource "aws_ec2_transit_gateway" "main" {
  description = "Hybrid connectivity hub"
  
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"
  
  tags = {
    Name = "hybrid-tgw"
  }
}

# Virtual Private Gateway (for VPN)
resource "aws_vpn_gateway" "main" {
  vpc_id            = aws_vpc.main.id
  amazon_side_asn   = 64512
  
  tags = {
    Name = "vpn-gateway"
  }
}

# Customer Gateway (オンプレ側ゲートウェイ)
resource "aws_customer_gateway" "onprem" {
  bgp_asn    = 65000
  public_ip  = var.onprem_public_ip  # 固定IP必須
  type       = "ipsec.1"
  
  tags = {
    Name = "onprem-gateway"
  }
}

# VPN Connection
resource "aws_vpn_connection" "main" {
  vpn_gateway_id      = aws_vpn_gateway.main.id
  customer_gateway_id = aws_customer_gateway.onprem.id
  type                = "ipsec.1"
  static_routes_only  = false  # BGP有効
  
  options {
    static_routes_only = false
  }
  
  tags = {
    Name = "vpn-to-onprem"
  }
}

# Transit Gateway Attachment (VGW)
resource "aws_ec2_transit_gateway_vpc_attachment" "vpn" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  vpn_gateway_id     = aws_vpn_gateway.main.id
  transit_gateway_default_route_table_association = true
  transit_gateway_default_route_table_propagation = true
}

# Direct Connect Virtual Interface (Private VIF)
resource "aws_ec2_customer_gateway" "dx" {
  bgp_asn   = 65000
  ip_address = var.onprem_dx_ip  # Direct Connectのカスタマー側IP
  type      = "ipsec.1"
}

resource "aws_ec2_transit_gateway_direct_connect_gateway_attachment" "main" {
  transit_gateway_id      = aws_ec2_transit_gateway.main.id
  direct_connect_gateway_id = var.dcgw_id
}

# BGP Route Preference制御
resource "aws_ec2_transit_gateway_route" "default" {
  destination_cidr_block          = "0.0.0.0/0"
  transit_gateway_route_table_id  = aws_ec2_transit_gateway.main.association_default_route_table_id
  transit_gateway_attachment_id   = aws_ec2_transit_gateway_vpc_attachment.vpn.id
  blackhole                        = false
}

オンプレ側の設定(Cisco IOS-XE例)

! BGP設定
router bgp 65000
  bgp log-neighbor-changes
  neighbor 169.254.10.1 remote-as 64512
  neighbor 169.254.11.1 remote-as 64512
  
  address-family ipv4
    neighbor 169.254.10.1 activate
    neighbor 169.254.11.1 activate
    network 172.16.0.0 mask 255.255.0.0
  exit-address-family
exit

! IPSec IKEv2設定
ipsec profile AWS-PROFILE-1
  set pfs group14
  set security-association lifetime seconds 28800
  set security-association lifetime kilobytes unlimited
  set transform-set AWS-TRANSFORM-SET
  set log-status
exit

! Tunnel Interface
interface Tunnel1
  ip address 169.254.10.2 255.255.255.252
  ip mtu 1436
  no ip split-dns
  tunnel source <CUSTOMER_GW_PUBLIC_IP>
  tunnel destination <AWS_VGW_ENDPOINT_1>
  tunnel mode ipsec ipv4
  tunnel protection ipsec profile AWS-PROFILE-1
  no shut
exit

! Route Preference(優先度制御)
route-map AWS-PRIMARY permit 10
  set local-preference 200
exit

route-map AWS-BACKUP permit 10
  set local-preference 100
exit

フェイルオーバー検証スクリプト

import boto3
import subprocess
import time
from datetime import datetime

class HybridConnectivityMonitor:
    def __init__(self):
        self.ec2 = boto3.client('ec2', region_name='ap-northeast-1')
        self.cloudwatch = boto3.client('cloudwatch')
    
    def check_vpn_status(self):
        """VPN接続状態確認"""
        response = self.ec2.describe_vpn_connections()
        vpn_status = {
            conn['VpnConnectionId']: conn['State']
            for conn in response['VpnConnections']
        }
        return vpn_status
    
    def check_dx_status(self):
        """Direct Connect VIF状態確認"""
        response = self.ec2.describe_virtual_interfaces()
        dx_status = {
            vif['virtualInterfaceId']: vif['virtualInterfaceState']
            for vif in response['virtualInterfaces']
        }
        return dx_status
    
    def simulate_dx_failure(self, vif_id):
        """DX障害をシミュレート"""
        print(f"[{datetime.now()}] Simulating DX failure for {vif_id}")
        # 実際にはテスト用VIFを使用
        # 本番では絶対やらない!
    
    def monitor_bgp_routes(self):
        """BGPルート確認(EC2インスタンス経由)"""
        # SSM Session Manager経由でオンプレ側をpingテスト
        onprem_subnet = "172.16.0.0/24"
        result = subprocess.run(
            f"ping -c 5 172.16.0.1",
            shell=True,
            capture_output=True,
            text=True
        )
        return "0% packet loss" in result.stdout
    
    def failover_test(self):
        """フェイルオーバーテスト"""
        print(f"\n=== Failover Test Started ===")
        
        # 1. 初期状態確認
        print("\n[Step 1] Baseline connectivity check")
        vpn_status = self.check_vpn_status()
        dx_status = self.check_dx_status()
        
        print(f"VPN Status: {vpn_status}")
        print(f"DX Status: {dx_status}")
        
        # 2. レイテンシ測定
        print("\n[Step 2] Latency measurement (normal operation)")
        ping_result = subprocess.run(
            "ping -c 10 -W 100 172.16.0.1",
            shell=True,
            capture_output=True,
            text=True
        )
        # レイテンシパース処理
        latencies = [
            float(line.split('time=')[1].split('ms')[0])
            for line in ping_result.stdout.split('\n')
            if 'time=' in line
        ]
        print(f"Average latency: {sum(latencies)/len(latencies):.2f}ms")
        
        # 3. 接続性テスト
        print("\n[Step 3] Connectivity test")
        if self.monitor_bgp_routes():
            print("✓ Connectivity OK")
        else:
            print("✗ Connectivity FAILED")
        
        # 4. フェイルオーバー効果検証
        # DX障害時のVPN自動フェイルオーバー
        print("\n[Step 4] Simulating DX failure (not actually failing, just testing BGP behavior)")
        print("Waiting for BGP convergence...")
        time.sleep(30)
        
        # 5. フェイルオーバー後の測定
        print("\n[Step 5] Connectivity after failover")
        ping_result_failover = subprocess.run(
            "ping -c 10 -W 100 172.16.0.1",
            shell=True,
            capture_output=True,
            text=True
        )
        latencies_failover = [
            float(line.split('time=')[1].split('ms')[0])
            for line in ping_result_failover.stdout.split('\n')
            if 'time=' in line
        ]
        print(f"Failover latency: {sum(latencies_failover)/len(latencies_failover):.2f}ms")
        print(f"Latency increase: {(sum(latencies_failover)/len(latencies_failover)) - (sum(latencies)/len(latencies)):.2f}ms")
        
        # 6. CloudWatch メトリクス送信
        self.cloudwatch.put_metric_data(
            Namespace='HybridConnectivity',
            MetricData=[
                {
                    'MetricName': 'PrimaryPathLatency',
                    'Value': sum(latencies)/len(latencies),
                    'Unit': 'Milliseconds'
                },
                {
                    'MetricName': 'BackupPathLatency',
                    'Value': sum(latencies_failover)/len(latencies_failover),
                    'Unit': 'Milliseconds'
                }
            ]
        )
        print("\n=== Failover Test Complete ===")

if __name__ == "__main__":
    monitor = HybridConnectivityMonitor()
    monitor.failover_test()

2026年時点での新しいオプション

ちなみに2026年は、Direct Connect Gateway (DCGW)Transit Gatewayの組み合わせが標準になってきました。昔は複雑だったけど、今は運用がマジで楽になってるんです。

Direct Connect Gatewayを使うと、こんなメリットがあります:

複数VPCを1つのDXで接続可能

昔は「VPC毎にDX1本必須」だったんですよ。今は1本のDXで複数VPC接続できちゃう。スッキリします。

オンプレ連携も同じDXで統合

オンプレ-AWS間のDX、AWS-AWS間のDX、全部1つのDCGWで管理できる。複数キャリアにも対応できるし、冗長性も高まる。

BGP優先度が自動化される

わざわざ手動設定しなくてもASPathで自動調整されるようになった。運用負荷が大幅に下がりました。

コスト削減のコツ

実運用で気づいたコスト削減ポイント、いくつかあります。

1. Data Transfer料金が9割

Direct Connectの月額$0.30/時間は微々たるもの。問題はデータ転送料金なんですよ。

月1TB転送:$0.30 × 730時間 + 1TB × $0.02 = $218 + $20 = $238
年間10TB:同額 × 12 = $2,856

ところがVPN経由だと:

VPN: $0 (VPN接続料無料)
AWS-Internet egress: 1TB × $0.09 = $90/TB
月1TB: $90 × 12 = $1,080/年

あ、VPNの方が高い…? 実は転送量が多いと逆転するんです。月3TB超えたあたりからDXの方がお得になり始める。

2. 実測で判断が大事

うちのチームは最初、VPN + DXで合計年30万円だと予想してました。実際に運用してみたら:

実測月平均: 200GB/月
VPN年額: 200GB × 12 × $0.09 = $2,160
DX年額: $2,628 + (200GB × 12 × $0.02) = $2,628 + $48 = $2,676
合計: $4,836

ほぼVPN。DX冗長化のメリットは「月100万円削減できる障害回避」だから、SLA要件とのバランスで決まる。単純にコスト削減だけじゃ判断できないんですよね。

運用で気づいたこと

1. VPN接続ドロップが予想外に多い

最初「ノイズレベル」だと思ってたんですけど、うちのISPの場合月1-2回VPN接続が30秒ほどドロップするんです。結構あります。

ISP側に問い合わせたら「IPSecはUDP 500/4500ポートなので、中間ネットワークでブロックされることがある」って言われました。NATトラバーサル設定を強化したら減ったけど、完全には0にできないんですよね。この辺は覚悟しておくべき。

2. オンプレ側のファイアウォール設定が地雷

VPNトンネルが繋がった〜と思ったら、FW設定漏れでデータが通らない。地雷です。こういうやつ:

# NGな設定
acl 101 permit ip any 10.0.0.0 0.0.255.255  # AWSのVPCネットワーク

# OKな設定(Dynamic Cryptomap経由)
acl 101 permit ip 172.16.0.0 0.0.255.255 10.0.0.0 0.0.255.255

VPNは両端でACLの順序と優先度が重要。オンプレ側の変更に数日かかることもあります。ネットワークチームとの調整が鍵ですね。

3. Direct Connectの接続工事は思ったより複雑

ロケーション選定から光ファイバー敷設まで、AWS側と施設側と通信キャリアが全部関わります。スケジュール調整だけで2ヶ月。正直、予想の1.5倍かかったかな。

まとめ

  1. VPN vs DXは二者択一ではない — VPN + DX冗長化が2026年の標準パターン。初期はVPNで迅速開発、トラフィック増加に応じてDX追加するのが賢明です。

  2. 遅延要件をちゃんと定義する — 「低遅延」は定義次第。5msと20msで判断は激変します。事前にベンチマークテストを実施してから判断しましょう。

  3. コスト計算は月々のデータ転送量が鍵 — 月1TB未満ならVPN圧倒的安い。月10TB超えたらDXの検討開始が目安。実測が何より大事です。

  4. フェイルオーバーはBGPで自動化 — 手動切り替えなんて古い。BGP設定で優先度制御して、障害時自動フェイルオーバー。運用負荷がマジで減ります。

  5. オンプレ側の準備時間を侮るな — AWS側は1週間で繋げるけど、オンプレのネットワーク組織が動くのに2ヶ月。スケジュール管理を最優先に。ここで失敗するプロジェクト、本当に多いですよ。

U

Untanbaby

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

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

関連記事