ExpressRoute環境で「インターネットへ出られない!」と焦る前に—UDRとBGPの深淵を歩く
現場のSRE諸君、お疲れ様。
「ExpressRouteを繋いだ途端、プライベートサブネットからインターネットへのAPI接続がタイムアウトするようになった」。これはクラウドインフラ運用において、新人からベテランまで一度は踏む「洗礼」のようなものだ。
AzureにおいてExpressRouteを導入すると、オンプレミス環境とクラウド間は広大なL2延伸のような感覚で繋がる。しかし、この「直結」が時にネットワークのルーティングを複雑怪奇にする。今回は、ExpressRoute環境下でインターネットへの出口を制御するための「UDR(User Defined Routes)」と「BGP広報」の泥臭い関係について紐解いていこう。
1. なぜ「デフォルトルート」が競合するのか
まず、根本的な挙動を理解しておこう。ExpressRouteを有効化し、BGP経由でオンプレミス側から 0.0.0.0/0(デフォルトルート)が広報されてくると、Azureの仮想ネットワーク(VNet)内のサブネットは、そのルートを「最優先」と判断する。
結果として、VNet内のVMから外部API(例: api.stripe.com)へパケットを投げようとすると、パケットはインターネットへ直接出るのではなく、オンプレミスのルーターへと「強制送還」される。オンプレ側にインターネット出口があれば良いが、大抵はセキュリティポリシーで遮断されているため、ここで通信がブラックホールに消えるわけだ。
2. UDRによる「強制ルーティング」の設計
この事態を回避し、トラフィックを「Azure Firewall」や「NAT Gateway」へ流し込むには、サブネット単位で 0.0.0.0/0 を上書きするUDRが必須となる。
Azure CLIによるUDR設定例
以下は、インターネット向けトラフィックをAzure Firewall経由で出口へ向かわせるための設定スクリプトだ。
# ルートテーブルの作成
az network route-table create \
--name "rt-private-subnet" \
--resource-group "rg-prod-network" \
--location "japaneast"
# 0.0.0.0/0 を Azure Firewall (プライベートIP: 10.0.1.4) へ飛ばすルールを追加
az network route-table route create \
--resource-group "rg-prod-network" \
--route-table-name "rt-private-subnet" \
--name "route-to-firewall" \
--address-prefix "0.0.0.0/0" \
--next-hop-type "VirtualAppliance" \
--next-hop-ip-address "10.0.1.4" # Azure FirewallのフロントエンドIPを指定
# サブネットへルートテーブルを関連付け
az network vnet subnet update \
--resource-group "rg-prod-network" \
--vnet-name "vnet-prod" \
--name "snet-app" \
--route-table "rt-private-subnet"
ここでの注意点:
Next Hop Type に Internet を選ぶと、UDRがBGPルートよりも優先度が低く扱われる場合がある。確実に制御したい場合は、VirtualAppliance(Azure FirewallやNGFW)を指定するか、NAT Gatewayをサブネットに直接アタッチするのが現代的な定石だ。
3. BGP広報の制御(オンプレ側での対策)
UDRで解決できるのはVNet内だけだが、根本治療として「オンプレからデフォルトルートを流さない」という選択肢もある。
オンプレのルーター(Cisco/Juniper等)で、Azure側へ広報するプレフィックスを制限する。これにより、Azure側が「インターネットへの最短経路はAzureのデフォルトゲートウェイだ」と認識するように仕向けるのだ。
現場で役立つPythonデバッグスクリプト
「今のパケットがどこで止まっているか?」を確認するために、requests を使ってHTTPヘッダーと接続先のIPを確認する簡単なスクリプトを用意した。トラブルシューティング時にサッと叩いてみてほしい。
import requests
import socket
def check_internet_connectivity():
# 接続先API
url = "https://ifconfig.me/ip"
try:
response = requests.get(url, timeout=5)
print(f"接続成功! グローバルIP: {response.text}")
# 実際に名前解決されたIPを表示し、ルートの正当性を確認
hostname = "ifconfig.me"
ip = socket.gethostbyname(hostname)
print(f"解決されたIP: {ip}")
except requests.exceptions.RequestException as e:
print(f"接続失敗: {e}")
# ルーティングが悪いのか、FWでブロックされているのかの切り分けに重要
if __name__ == "__main__":
check_internet_connectivity()
4. 現場の教訓:NAT Gatewayの活用
最近のベストプラクティスとしては、複雑なUDRを組む前に「NAT Gateway」をサブネットへアタッチすることを強く推奨する。
NAT Gatewayをサブネットに関連付けると、そのサブネットからのアウトバウンド通信は、UDRの設定に関わらず自動的にNAT GatewayのパブリックIP経由でインターネットへ出る。これは「ExpressRouteから来るルートよりもNAT Gatewayが優先される」という仕様に基づいているため、非常に安定する。
実務Tips:
- UDRの落とし穴:
0.0.0.0/0を含む広い範囲をUDRで指定すると、オンプレミス向けのトラフィックまでFWに吸い込まれ、レイテンシが増大することがある。ルートテーブルには10.0.0.0/8等のオンプレ側ネットワークをNone(またはVirtualNetworkGateway) として明示的に記述し、0.0.0.0/0と競合させない工夫が必要だ。
最後に
ネットワークトラブルは、パケットの気持ちになることが一番の近道だ。「なぜその経路を通るのか?」「BGPのコスト値は適切か?」「Azureのシステムルートが邪魔をしていないか?」。
これらをパケットのシーケンス図として頭の中に描けるようになれば、君はもう立派なクラウドネットワークエンジニアだ。インフラはコードで管理し、心は常にパケットの行方を追いかけよう。現場からは以上だ。
コメント