AWS VPN BGP Transit Gateway routing networking GCP

AWS Site-to-Site VPN z wieloma połączeniami: naprawa asymetrycznego routingu i hairpinningu ruchu

Naprawa asymetrycznego routingu i hairpinningu ruchu przy wielu równoległych połączeniach AWS Site-to-Site VPN do GCP Cloud VPN.

·
Dodałeś drugie lub trzecie połączenie VPN dla zwiększenia przepustowości, ale teraz ruch podąża nieprzewidywalnymi ścieżkami. Część żądań wychodzi jednym tunelem, a odpowiedzi wracają innym. Zapory stanowe (stateful) odrzucają te asymetryczne przepływy, powodując przerywanie i timeout. Poniższy runbook pokazuje, jak kontrolować routing między równoległymi połączeniami VPN.

Objawy

Po dodaniu wielu połączeń VPN między AWS a GCP pojawiają się przerywane problemy z łącznością:

Skoki opóźnień dla konkretnych podsieci, podczas gdy inne działają prawidłowo:

# Some destinations have stable latency
ping -c 10 10.128.1.10
# rtt min/avg/max = 22.1/23.4/24.8 ms

# Others spike unpredictably
ping -c 10 10.128.2.10
# rtt min/avg/max = 22.5/89.3/245.1 ms

Zapora stanowa (Security Groups, NACLe lub zapora GCP) odrzuca pakiety:

# VPC Flow Logs show REJECT for return traffic
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --filter-pattern "REJECT" \
  --start-time $(date -u -d '1 hour ago' +%s)000
2 123456789012 eni-0abc123 10.128.2.10 10.0.1.50 443 52431 6 5 300 1720000000 1720000060 REJECT OK

Żądania kończą się timeout sporadycznie - typowo 10-30% strat:

for i in $(seq 1 20); do
  curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" http://10.128.2.10:8080/health
  sleep 1
done
200 0.045s
200 0.044s
000 30.001s  # timeout
200 0.046s
000 30.001s  # timeout
200 0.043s

Tablica routingu Transit Gateway pokazuje ścieżki o równym koszcie:

aws ec2 search-transit-gateway-routes \
  --transit-gateway-route-table-id tgw-rtb-0abc123 \
  --filters "Name=route-search.exact-match,Values=10.128.0.0/16" \
  --output table
CIDR            Type     State    Attachments
10.128.0.0/16   propagated  active  tgw-attach-vpn1, tgw-attach-vpn2, tgw-attach-vpn3

Przyczyna

Asymetryczny routing przy równoległych połączeniach VPN występuje z powodu problemów z kontrolą routingu:

  1. Niezgodne wartości BGP MED/local preference między połączeniami. Gdy wiele połączeń VPN ogłasza te same prefiksy z równymi wartościami MED (Multi-Exit Discriminator), Transit Gateway nie ma preferencji i rozkłada obciążenie na wszystkie połączenia. Pakiety żądania i odpowiedzi mogą podążać różnymi ścieżkami.

  2. ECMP rozkłada żądanie/odpowiedź na różne tunele. AWS Transit Gateway domyślnie używa ECMP (Equal-Cost Multi-Path). Hash bazuje na źródłowym/docelowym IP i porcie. Gdy hash różni się między pakietami wychodzącymi i przychodzącymi (częste przy NAT lub zmianach portów), ruch rozdziela się między tunele.

  3. Tablica routingu Transit Gateway ma ścieżki o równym koszcie bez preferencji. Wszystkie załączniki VPN propagują te same trasy z identycznymi metrykami. Transit Gateway traktuje je jako równoważne i rozdziela przepływy bez przypisania.

  4. GCP Cloud Router ogłasza te same prefiksy na wszystkich tunelach bez AS path prepending. Jeśli GCP ogłasza 10.128.0.0/16 identycznie na wszystkich tunelach VPN, strona AWS widzi wiele równoważnych ścieżek i nie może ustalić hierarchii primary/backup.

  5. Security Groups/NACLe widzą ruch z nieoczekiwanego IP tunelu źródłowego. Gdy ruch powrotny przychodzi innym tunelem, adres źródłowy (endpoint tunelu) różni się od tego, czego oczekuje śledzenie stanowe. Śledzenie połączeń na Security Groups może odrzucić te pakiety jako nieprawidłowe.

Rozwiązanie

Krok 1: Zdefiniowanie połączeń primary i backup

Zdecyduj, które połączenie VPN jest primary, a które backup. Na przykład:

  • vpn-connection-1 (przez endpoint 203.0.113.1) = primary
  • vpn-connection-2 (przez endpoint 203.0.113.2) = secondary
  • vpn-connection-3 (przez endpoint 203.0.113.3) = tertiary/backup

Krok 2: Ustawienie wartości BGP MED na GCP Cloud Router

Skonfiguruj różne wartości MED per tunel na GCP Cloud Router. Niższy MED = wyższa preferencja:

# Primary tunnel - lowest MED (most preferred)
gcloud compute routers update-bgp-peer my-cloud-router \
  --peer-name aws-primary \
  --region us-central1 \
  --advertisement-mode custom \
  --set-advertisement-ranges "10.128.0.0/16=100"

# Secondary tunnel - higher MED
gcloud compute routers update-bgp-peer my-cloud-router \
  --peer-name aws-secondary \
  --region us-central1 \
  --advertisement-mode custom \
  --set-advertisement-ranges "10.128.0.0/16=200"

# Backup tunnel - highest MED
gcloud compute routers update-bgp-peer my-cloud-router \
  --peer-name aws-backup \
  --region us-central1 \
  --advertisement-mode custom \
  --set-advertisement-ranges "10.128.0.0/16=300"

Krok 3: Konfiguracja AS path prepending na tunelach backup

AS path prepending jest bardziej uniwersalnie respektowanym atrybutem BGP niż MED. Wydłuż AS path na tunelach backup, aby uczynić je mniej preferowanymi:

Na GCP Cloud Router użyj polityk routingu do prepending:

# Create a route policy for the backup peer
gcloud compute routers add-route-policy my-cloud-router \
  --region us-central1 \
  --policy-name backup-prepend \
  --policy-type export \
  --terms '[{
    "priority": 1,
    "match": {"prefixes": [{"prefix": "10.128.0.0/16"}]},
    "actions": {"prependAsPath": ["65010", "65010", "65010"]}
  }]'

# Apply the policy to the backup peer
gcloud compute routers update-bgp-peer my-cloud-router \
  --peer-name aws-backup \
  --region us-central1 \
  --export-policies backup-prepend

To sprawia, że ścieżka backup wydaje się 3 hopy AS dłuższa, zapewniając, że AWS zawsze preferuje ścieżki primary i secondary.

Krok 4: Weryfikacja preferencji tras na Transit Gateway

Sprawdź, jak trasy są instalowane w tablicy routingu Transit Gateway:

aws ec2 search-transit-gateway-routes \
  --transit-gateway-route-table-id tgw-rtb-0abc123 \
  --filters "Name=route-search.exact-match,Values=10.128.0.0/16" \
  --query 'Routes[*].{CIDR:DestinationCidrBlock,Attachments:TransitGatewayAttachments[*].TransitGatewayAttachmentId,Type:Type}' \
  --output json

Jeśli ECMP nie jest pożądany, wyłącz go na Transit Gateway:

aws ec2 modify-transit-gateway \
  --transit-gateway-id tgw-0abc123def456 \
  --options "VpnEcmpSupport=disable"

Z wyłączonym ECMP Transit Gateway używa tylko najlepszej ścieżki na podstawie atrybutów BGP (długość AS path, MED, origin).

Jeśli chcesz ECMP dla przepustowości, ale symetryczny routing dla sesji, zostaw ECMP włączony, ale upewnij się, że oba kierunki (AWS do GCP i GCP do AWS) używają tych samych kryteriów wyboru ścieżki.

Krok 5: Ustawienie preferencji tras po stronie AWS

Na konfiguracji AWS VPN ustaw różne preferencje lokalne dla znanych tras z każdego połączenia. Wykonuje się to przez trasy statyczne w tablicy routingu Transit Gateway lub manipulację BGP na customer gateway:

# Add a static route with higher priority pointing to primary VPN
aws ec2 create-transit-gateway-route \
  --transit-gateway-route-table-id tgw-rtb-0abc123 \
  --destination-cidr-block 10.128.0.0/16 \
  --transit-gateway-attachment-id tgw-attach-vpn1

Dla preferencji opartej na BGP, skonfiguruj opcje tunelu VPN, aby preferować jeden tunel:

aws ec2 modify-vpn-tunnel-options \
  --vpn-connection-id vpn-0abc123def456 \
  --vpn-tunnel-outside-ip-address 203.0.113.1 \
  --tunnel-options '{
    "Phase1EncryptionAlgorithms": [{"Value": "AES256"}],
    "Phase2EncryptionAlgorithms": [{"Value": "AES256-GCM-16"}]
  }'

Krok 6: Konfiguracja niestandardowych ogłoszeń tras per tunel na GCP

Zamiast ogłaszać ten sam /16 na wszystkich tunelach, rozdziel prefiksy między tunelami dla deterministycznego routingu:

# Primary tunnel advertises all subnets
gcloud compute routers update-bgp-peer my-cloud-router \
  --peer-name aws-primary \
  --region us-central1 \
  --advertisement-mode custom \
  --set-advertisement-ranges "10.128.0.0/16"

# Secondary tunnel advertises only specific subnets (for failover)
gcloud compute routers update-bgp-peer my-cloud-router \
  --peer-name aws-secondary \
  --region us-central1 \
  --advertisement-mode custom \
  --set-advertisement-ranges "10.128.1.0/24,10.128.2.0/24"

To zapewnia, że bardziej szczegółowe trasy (/24) są dostępne tylko przez konkretne tunele, zapobiegając asymetrycznym ścieżkom dla tych podsieci.

Krok 7: Naprawa reguł zapory stanowej

Jeśli asymetrycznego routingu nie da się całkowicie wyeliminować (np. podczas failover), upewnij się, że Security Groups i NACLe przepuszczają ruch ze wszystkich IP endpointów tuneli:

# Allow return traffic from all VPN tunnel endpoints
aws ec2 authorize-security-group-ingress \
  --group-id sg-0abc123 \
  --protocol -1 \
  --cidr 10.128.0.0/16

# Check NACL rules allow traffic in both directions
aws ec2 describe-network-acls \
  --filters "Name=association.subnet-id,Values=subnet-0abc123" \
  --query 'NetworkAcls[0].Entries[*].{Rule:RuleNumber,CIDR:CidrBlock,Action:RuleAction,Egress:Egress}' \
  --output table

Na GCP upewnij się, że reguły firewalla akceptują ruch niezależnie od tunelu, którym przyszedł:

gcloud compute firewall-rules create allow-aws-all-tunnels \
  --network my-vpc \
  --allow all \
  --source-ranges 10.0.0.0/8 \
  --direction INGRESS \
  --priority 1000

Krok 8: Weryfikacja stanu tablicy routingu

Potwierdzenie, że trasy Transit Gateway pokazują właściwą preferencję:

aws ec2 get-transit-gateway-route-table-associations \
  --transit-gateway-route-table-id tgw-rtb-0abc123 \
  --output table

aws ec2 get-transit-gateway-route-table-propagations \
  --transit-gateway-route-table-id tgw-rtb-0abc123 \
  --output table

Sprawdzenie tras BGP otrzymanych na GCP Cloud Router:

gcloud compute routers get-status my-cloud-router \
  --region us-central1 \
  --format="yaml(result.bgpPeerStatus[].{name:name,numLearnedRoutes:numLearnedRoutes,advertisedRoutes:advertisedRoutes})"

Walidacja

Weryfikacja, że traceroute pokazuje spójną ścieżkę:

# Run 5 times, path should be identical each time
for i in $(seq 1 5); do
  echo "--- Attempt $i ---"
  traceroute -n 10.128.2.10
  sleep 5
done

Wszystkie próby powinny pokazywać te same hopy pośrednie (ta sama ścieżka tunelowa).

Test stabilności opóźnień:

ping -c 100 10.128.2.10 | tail -1

Oczekiwany wynik: spójny średni czas z niskim odchyleniem standardowym (np. rtt min/avg/max/mdev = 22.1/23.4/24.8/0.9 ms).

Weryfikacja braku odrzuceń stanowych:

# Check VPC Flow Logs for REJECTs to/from GCP subnets
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --filter-pattern "10.128 REJECT" \
  --start-time $(date -u -d '15 minutes ago' +%s)000 \
  --query 'events[*].message' \
  --output text

Oczekiwany wynik: brak wyników (brak odrzuconego ruchu).

Test łączności na poziomie aplikacji:

# 100 requests with no timeouts expected
for i in $(seq 1 100); do
  code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://10.128.2.10:8080/health)
  if [ "$code" != "200" ]; then
    echo "FAIL at attempt $i: HTTP $code"
  fi
done
echo "Test complete"

Oczekiwany wynik: wszystkie 100 żądań zwraca 200, zero strat.

Weryfikacja, że dystrybucja ruchu odpowiada zamierzeniu:

# Check CloudWatch metrics per VPN connection
for vpn in vpn-0abc123 vpn-0def456 vpn-0ghi789; do
  echo "--- $vpn ---"
  aws cloudwatch get-metric-statistics \
    --namespace "AWS/VPN" \
    --metric-name TunnelDataOut \
    --dimensions Name=VpnId,Value=$vpn \
    --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
    --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
    --period 3600 \
    --statistics Sum \
    --query 'Datapoints[0].Sum'
done

Primary powinien przenosić większość ruchu, secondary mniej, backup blisko zera (chyba że primary uległ awarii).

Jerzy Kopaczewski

Potrzebujesz pomocy z łącznością multi-cloud?

Umów darmową 30-minutową rozmowę. Zdiagnozujemy problem z połączeniem i zarekomendujemy właściwe rozwiązanie.