はじめに
現在の Azure には従来のハブアンドスポークな仮想ネットワークと、Virtual WAN で構成された仮想ネットワークの2つが存在しています。とすると、従来のハブアンドスポークな仮想ネットワークに接続しているスポークな仮想ネットワークと、Virtual WAN に接続しているスポークネットワーク間で通信したくなることもあるでしょう。

通信したい2つのスポーク仮想ネットワーク
両者のスポークが1つずつであれば、スポーク同士を直接 VNET ピアリングすればよいです。ですが、ハブアンドスポーク構成と Virtual WAN を採用しているという事は、それぞれのネットワークには多くのスポーク仮想ネットワークが存在しているでしょう。通信したいスポーク同士を都度 VNET ピアリングで接続するのは骨が折れます。やはりこここは2つのネットワークを接続してしまいたいところです。
とはいえ、やったことがないので実際に試してみました。実際に環境を作成する際に利用した Bicep は以下の通りです。細かい設定値はテンプレートを参照ください。
Bicepテンプレート
param adminUsername string
param adminPassword string
resource hub 'Microsoft.Network/virtualNetworks@2024-05-01' = {
name: 'hub'
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'GatewaySubnet'
properties: {
addressPrefix: '10.0.0.0/24'
routeTable: {
id: hubGwRt.id
}
}
}
{
name: 'AzureFirewallSubnet'
properties: {
addressPrefix: '10.0.1.0/25'
routeTable: {
id: hubFwRt.id
}
}
}
{
name: 'AzureFirewallManagementSubnet'
properties: {
addressPrefix: '10.0.1.128/25'
}
}
]
}
}
resource hubFwRt 'Microsoft.Network/routeTables@2024-07-01' = {
name: 'hubRouteTable'
location: resourceGroup().location
properties: {
routes: [
{
name: 'default'
properties: {
addressPrefix: '0.0.0.0/0'
nextHopType: 'Internet'
}
}
]
}
}
resource hubGwRt 'Microsoft.Network/routeTables@2024-07-01' = {
name: 'hubGwRouteTable'
location: resourceGroup().location
properties: {
routes: [
{
name: 'default'
properties: {
addressPrefix: '10.1.0.0/24'
nextHopType: 'VirtualAppliance'
nextHopIpAddress: '10.0.1.4'
}
}
]
}
}
resource hubErGw 'Microsoft.Network/virtualNetworkGateways@2024-07-01' = {
name: 'hubErGw'
location: resourceGroup().location
properties: {
sku: {
name: 'Standard'
tier: 'Standard'
}
gatewayType: 'ExpressRoute'
ipConfigurations: [
{
name: 'hubErGwIpConfig'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: '${hub.id}/subnets/GatewaySubnet'
}
}
}
]
}
dependsOn: [
hubFirewall
]
}
resource hubVpnGwPip1 'Microsoft.Network/publicIPAddresses@2024-07-01' = {
name: 'hubVpnGwPip1'
location: resourceGroup().location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
publicIPAllocationMethod: 'Static'
}
}
resource hubVpnGwPip2 'Microsoft.Network/publicIPAddresses@2024-07-01' = {
name: 'hubVpnGwPip2'
location: resourceGroup().location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
publicIPAllocationMethod: 'Static'
}
}
resource hubVpnGw 'Microsoft.Network/virtualNetworkGateways@2024-07-01' = {
name: 'hubVpnGw'
location: resourceGroup().location
properties: {
sku: {
name: 'VpnGw1'
tier: 'VpnGw1'
}
enableBgp: false
activeActive: true
gatewayType: 'Vpn'
vpnType: 'RouteBased'
ipConfigurations: [
{
name: 'hubVpnGwIpConfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: '${hub.id}/subnets/GatewaySubnet'
}
publicIPAddress: {
id: hubVpnGwPip1.id
}
}
}
{
name: 'hubVpnGwIpConfig2'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: '${hub.id}/subnets/GatewaySubnet'
}
publicIPAddress: {
id: hubVpnGwPip2.id
}
}
}
]
}
dependsOn: [
hubFirewall
]
}
resource hubVpnLgw1 'Microsoft.Network/localNetworkGateways@2024-07-01' = {
name: 'hubVpnLgw1'
location: resourceGroup().location
properties: {
localNetworkAddressSpace: {
addressPrefixes: [
'192.168.0.0/16'
]
}
gatewayIpAddress: vwanVpnGw.properties.ipConfigurations[0].publicIpAddress
}
}
resource hubVpnLgw2 'Microsoft.Network/localNetworkGateways@2024-07-01' = {
name: 'hubVpnLgw2'
location: resourceGroup().location
properties: {
localNetworkAddressSpace: {
addressPrefixes: [
'192.168.0.0/16'
]
}
gatewayIpAddress: vwanVpnGw.properties.ipConfigurations[1].publicIpAddress
}
}
resource hubVpnGwConn1 'Microsoft.Network/connections@2024-07-01' = {
name: 'hubVpnGwConn1'
location: resourceGroup().location
properties: {
connectionType: 'IPsec'
localNetworkGateway2: {
id: hubVpnLgw1.id
}
virtualNetworkGateway1: {
id: hubVpnGw.id
}
sharedKey: adminPassword
enableBgp: false
connectionProtocol: 'IKEv2'
}
}
resource hubVpnGwConn2 'Microsoft.Network/connections@2024-07-01' = {
name: 'hubVpnGwConn2'
location: resourceGroup().location
properties: {
connectionType: 'IPsec'
localNetworkGateway2: {
id: hubVpnLgw2.id
}
virtualNetworkGateway1: {
id: hubVpnGw.id
}
sharedKey: adminPassword
enableBgp: false
connectionProtocol: 'IKEv2'
}
}
resource spoke 'Microsoft.Network/virtualNetworks@2024-05-01' = {
name: 'spoke'
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'10.1.0.0/24'
]
}
subnets: [
{
name: 'default'
properties: {
addressPrefix: '10.1.0.0/25'
routeTable: {
id: spokeRt.id
}
}
}
]
}
}
resource spokeVmNic 'Microsoft.Network/networkInterfaces@2024-07-01' = {
name: 'spokeVmNic'
location: resourceGroup().location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: '${spoke.id}/subnets/default'
}
}
}
]
}
}
resource spokeVm 'Microsoft.Compute/virtualMachines@2024-07-01' = {
name: 'spokeVm'
location: resourceGroup().location
properties: {
hardwareProfile: {
vmSize: 'Standard_B1s'
}
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-jammy'
sku: '22_04-lts-gen2'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'StandardSSD_LRS'
}
}
}
osProfile: {
computerName: 'spokeVm'
adminUsername: adminUsername
adminPassword: adminPassword
}
networkProfile: {
networkInterfaces: [
{
id: spokeVmNic.id
}
]
}
diagnosticsProfile: {
bootDiagnostics: {
enabled: true
}
}
}
}
resource hub2spokePeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2024-07-01' = {
name: 'hub-to-spoke'
parent: hub
properties: {
allowVirtualNetworkAccess: true
allowForwardedTraffic: true
allowGatewayTransit: true
useRemoteGateways: false
remoteVirtualNetwork: {
id: spoke.id
}
}
dependsOn: [
hubErGw
hubVpnGw
]
}
resource spoke2hubPeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2024-07-01' = {
name: 'spoke-to-hub'
parent: spoke
properties: {
allowVirtualNetworkAccess: true
allowForwardedTraffic: true
allowGatewayTransit: false
useRemoteGateways: true
remoteVirtualNetwork: {
id: hub.id
}
}
dependsOn: [
hubErGw
hubVpnGw
]
}
resource spokeRt 'Microsoft.Network/routeTables@2024-07-01' = {
name: 'spokeRouteTable'
location: resourceGroup().location
properties: {
disableBgpRoutePropagation: true
routes: [
{
name: 'default'
properties: {
addressPrefix: '0.0.0.0/0'
nextHopType: 'VirtualAppliance'
nextHopIpAddress: hubFirewall.properties.ipConfigurations[0].properties.privateIPAddress
}
}
]
}
}
resource fwpolicy 'Microsoft.Network/firewallPolicies@2024-07-01' = {
name: 'fwPolicy'
location: resourceGroup().location
properties: {
sku: {
tier: 'Standard'
}
threatIntelMode: 'Alert'
}
}
resource fwPolicyNetworkRuleCollection 'Microsoft.Network/firewallPolicies/ruleCollectionGroups@2024-07-01' = {
name: 'networkRuleCollection'
parent: fwpolicy
properties: {
priority: 100
ruleCollections: [
{
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
name: 'networkRuleCollection'
priority: 100
action: {
type: 'Allow'
}
rules: [
{
ruleType: 'NetworkRule'
ipProtocols: [
'Any'
]
name: 'allowAll'
description: 'Allow all traffic'
sourceAddresses: [
'*'
]
destinationAddresses: [
'*'
]
destinationPorts: [
'*'
]
}
]
}
]
}
}
resource hubFwPip 'Microsoft.Network/publicIPAddresses@2024-07-01' = {
name: 'hubFwPip'
location: resourceGroup().location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
publicIPAllocationMethod: 'Static'
}
}
resource hubFwMgmtPip 'Microsoft.Network/publicIPAddresses@2024-07-01' = {
name: 'hubFwMgmtPip'
location: resourceGroup().location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
publicIPAllocationMethod: 'Static'
}
}
resource hubFirewall 'Microsoft.Network/azureFirewalls@2024-05-01' = {
name: 'hubFirewall'
location: resourceGroup().location
properties: {
sku: {
name: 'AZFW_VNet'
tier: 'Standard'
}
firewallPolicy: {
id: fwpolicy.id
}
ipConfigurations: [
{
name: 'firewallIpConfig'
properties: {
publicIPAddress: {
id: hubFwPip.id
}
subnet: {
id: '${hub.id}/subnets/AzureFirewallSubnet'
}
}
}
]
managementIpConfiguration: {
name: 'managementIpConfig'
properties: {
subnet: {
id: '${hub.id}/subnets/AzureFirewallManagementSubnet'
}
publicIPAddress: {
id: hubFwMgmtPip.id
}
}
}
}
}
// vwan
resource vwanSpoke 'Microsoft.Network/virtualNetworks@2024-05-01' = {
name: 'vwanSpoke'
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'192.168.1.0/24'
]
}
subnets: [
{
name: 'default'
properties: {
addressPrefix: '192.168.1.0/24'
}
}
]
}
}
resource vwanSpokeVmNic 'Microsoft.Network/networkInterfaces@2024-07-01' = {
name: 'vwanSpokeVmNic'
location: resourceGroup().location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: '${vwanSpoke.id}/subnets/default'
}
}
}
]
}
}
resource vwanSpokeVm 'Microsoft.Compute/virtualMachines@2024-07-01' = {
name: 'vwanSpokeVm'
location: resourceGroup().location
properties: {
hardwareProfile: {
vmSize: 'Standard_B1s'
}
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-jammy'
sku: '22_04-lts-gen2'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'StandardSSD_LRS'
}
}
}
osProfile: {
computerName: 'vwanSpokeVm'
adminUsername: adminUsername
adminPassword: adminPassword
}
networkProfile: {
networkInterfaces: [
{
id: vwanSpokeVmNic.id
}
]
}
diagnosticsProfile: {
bootDiagnostics: {
enabled: true
}
}
}
}
resource vwan 'Microsoft.Network/virtualWans@2024-07-01' = {
name: 'vwan'
location: resourceGroup().location
properties: {
allowBranchToBranchTraffic: true
disableVpnEncryption: false
type: 'Standard'
}
}
resource vhub 'Microsoft.Network/virtualHubs@2024-07-01' = {
name: 'vhub'
location: resourceGroup().location
properties: {
virtualWan: {
id: vwan.id
}
virtualRouterAutoScaleConfiguration: {
minCapacity: 2
}
addressPrefix: '192.168.0.0/24'
}
}
resource vwanErGw 'Microsoft.Network/expressRouteGateways@2024-07-01' = {
name: 'vwanErGw'
location: resourceGroup().location
properties: {
virtualHub: {
id: vhub.id
}
autoScaleConfiguration: {
bounds: {
min: 1
}
}
}
}
resource vwanVpnGw 'Microsoft.Network/vpnGateways@2024-07-01' = {
name: 'vwanVpnGw'
location: resourceGroup().location
properties: {
virtualHub: {
id: vhub.id
}
bgpSettings: {
asn: 65515
}
vpnGatewayScaleUnit: 1
}
dependsOn: [
vwanErGw
]
}
resource vwanVpnSite1 'Microsoft.Network/vpnSites@2024-07-01' = {
name: 'hubVpnGw1'
location: resourceGroup().location
properties: {
deviceProperties: {
deviceModel: 'Azure VPN Gateway'
deviceVendor: 'Microsoft'
linkSpeedInMbps: 100
}
virtualWan: {
id: vwan.id
}
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
'10.1.0.0/16'
]
}
vpnSiteLinks: [
{
name: 'hubVpnGw1Link'
properties: {
ipAddress: hubVpnGwPip1.properties.ipAddress
linkProperties: {
linkProviderName: 'Microsoft'
linkSpeedInMbps: 100
}
}
}
]
}
}
resource vwanVpnSite2 'Microsoft.Network/vpnSites@2024-07-01' = {
name: 'hubVpnGw2'
location: resourceGroup().location
properties: {
deviceProperties: {
deviceModel: 'Azure VPN Gateway'
deviceVendor: 'Microsoft'
linkSpeedInMbps: 100
}
virtualWan: {
id: vwan.id
}
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
'10.1.0.0/16'
]
}
vpnSiteLinks: [
{
name: 'hubVpnGw2Link'
properties: {
ipAddress: hubVpnGwPip2.properties.ipAddress
linkProperties: {
linkProviderName: 'Microsoft'
linkSpeedInMbps: 100
}
}
}
]
}
}
resource vwanFirewall 'Microsoft.Network/azureFirewalls@2024-05-01' = {
name: 'vwanFirewall'
location: resourceGroup().location
properties: {
sku: {
name: 'AZFW_Hub'
tier: 'Standard'
}
firewallPolicy: {
id: fwpolicy.id
}
hubIPAddresses: {
publicIPs: {
count: 1
}
}
virtualHub: {
id: vhub.id
}
}
dependsOn: [
vwanErGw
vwanVpnGw
]
}
resource vhub2vwanSpokeConnection 'Microsoft.Network/virtualHubs/hubVirtualNetworkConnections@2024-07-01' = {
name: 'vhub-to-vwanSpoke'
parent: vhub
properties: {
remoteVirtualNetwork: {
id: vwanSpoke.id
}
enableInternetSecurity: true
}
dependsOn: [
vwanErGw
]
}
resource vhubRoutingIntent 'Microsoft.Network/virtualHubs/routingIntent@2024-07-01' = {
name: 'vhubRoutingIntent'
parent: vhub
properties: {
routingPolicies: [
{
name: 'PublicTraffic'
destinations: [
'Internet'
]
nextHop: vwanFirewall.id
}
{
name: 'PrivateTraffic'
destinations: [
'PrivateTraffic'
]
nextHop: vwanFirewall.id
}
]
}
}
組めない構成
思いつく最初の方法は、ハブ仮想ネットワークを仮想ハブに接続する方法です。ですが、この構成は組めませんでした。

できない構成
仮想仮想ネットワークゲートウェイが存在する仮想ネットワークと仮想ハブを接続しようとすると次のエラーがでます。

ポータル上のエラー
仮想ハブに仮想ネットワーク接続を作る際に allowRemoteVnetToUseHubVnetGateways を false にできればよさそうですが、PowerShell のコマンドには該当の設定を false にするオプションが見当たりません。「API 直叩きならなんとかなるか?」と以下の様に試してみたものの false で設定してもエラーになってしまいました。
$hub = Get-AzVirtualNetwork -ResourceGroupName test0808vwan -Name hub
$rt1 = Get-AzVHubRouteTable -ResourceGroupName test0808vwan -VirtualHubName vhub -Name "defaultRouteTable"
$body = @"
{
"properties": {
"remoteVirtualNetwork": {
"id": "$($hub.id)"
},
"allowRemoteVnetToUseHubVnetGateways": false,
"allowHubToRemoteVnetTransit": true,
"enableInternetSecurity": false,
"routingConfiguration": {
"associatedRouteTable": {
"id": "$($rt1.Id)"
},
"propagatedRouteTables": {
"labels": [
"default"
],
"ids": [
{
"id": "$($rt1.Id)"
}
]
},
"vnetRoutes": {
"staticRoutesConfig": {
"vnetLocalRouteOverrideCriteria": "Equal"
},
"staticRoutes": [
{
"name": "route1",
"addressPrefixes": [
"10.1.0.0/24",
],
"nextHopIpAddress": "10.0.1.4"
}
]
}
}
}
}
StatusCode : 400
Content : {
"error": {
"code": "RemoteVnetAlreadyHasGateways",
"message": "HubVirtualNetworkConnection /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test0808vwan/providers/Microsoft.Network/virtualHubs/vhub/hubVirtualNetworkConnections/vhub-to-hub cannot have AllowRemoteVnetToUseHubVnetGateways flag set to true, because remote virtual network /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test0808vwan/providers/Microsoft.Network/virtualNetworks/hub already has a gateway /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/TEST0808VWAN/providers/Microsoft.Network/virtualNetworkGateways/HUBERGW configured. Remove gateway from the remote virtual network and retry.",
"details": []
}
"@
API のプロパティをよく見てみると「allowRemoteVnetToUseHubVnetGateways」は廃止になっていました。万策尽きた感じです。
HubVirtualNetworkConnectionProperties
代替案
ハブ同士を VNET ピアリングで直結できないのであれば、VPN Gateway を利用して S2S VPN で接続します。互いに相手を一つの VPN 拠点として扱うイメージです。

できる構成
「普通のハブに S2S VPN で拠点を接続する」作業と「仮想ハブに S2S VPN で拠点を接続する」作業をやるだけですので、特殊な設定は不要です。注意したほうが良い点は以下の3点です
- 静的ルーティングを使う
- VPN Gateway を Active/Active にする
- GatewaySubnet を /27 以上にする
静的ルーティングを使う
S2S VPN を設計するときの悩みが、BGP を利用するかどうかです。今回の構成では静的ルートを採用しました。というのも、BGP では上手く動作しませんでした。なぜだか 普通のハブの VPN Gateway が、仮想ハブから学習した経路を仮想ネットワークに広報してくれず…
また、設計的にも静的ルートの方が良い点があります。BGP を利用するには普通のハブの VPN Gateway の AS 番号を65515以外にものにする必要があります。Virtual WAN 側の VPN Gateway の AS 番号が65515で固定なためです。普通のハブの VPN Gateway の AS 番号を65515以外のものにしてしまうと、Route Server が使えなかったり、ExpressRoute とのトランジットルートができなくなったりと「できないこと」が生じます。
ExpressRoute と VPN の間のトランジット ルーティングの場合は、Azure VPN Gateway の ASN が 65515 に設定されている必要があります。
https://learn.microsoft.com/ja-jp/azure/expressroute/how-to-configure-coexisting-gateway-portal
Azure VPN ゲートウェイは、アクティブ-アクティブ モードで構成し、ASN が 65515 に設定されている必要があります。 ルート サーバーと通信するために VPN ゲートウェイで BGP を有効にする必要はありません。
https://learn.microsoft.com/ja-jp/azure/route-server/expressroute-vpn-support
普通のハブの VPN Gateway の構成
普通のハブの VPN Gateway は Active/Active にする必要があります。
アクティブ/アクティブ モード - 必ずアクティブ/アクティブ モードを選択してください。 この設定は、VPN Gateway 仮想ネットワーク ゲートウェイが Virtual WAN に接続するために必要です。
https://learn.microsoft.com/ja-jp/azure/virtual-wan/connect-virtual-network-gateway-vwan
そのため、普通のハブの VPN Gateway が持つ2つのパブリック IP アドレスを、仮想ハブの VPN サイトとして登録することになります。
resource vwanVpnSite1 'Microsoft.Network/vpnSites@2024-07-01' = {
name: 'hubVpnGw1'
location: resourceGroup().location
properties: {
deviceProperties: {
deviceModel: 'Azure VPN Gateway'
deviceVendor: 'Microsoft'
linkSpeedInMbps: 100
}
virtualWan: {
id: vwan.id
}
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
'10.1.0.0/16'
]
}
vpnSiteLinks: [
{
name: 'hubVpnGw1Link'
properties: {
ipAddress: hubVpnGwPip1.properties.ipAddress
linkProperties: {
linkProviderName: 'Microsoft'
linkSpeedInMbps: 100
}
}
}
]
}
}
resource vwanVpnSite2 'Microsoft.Network/vpnSites@2024-07-01' = {
name: 'hubVpnGw2'
location: resourceGroup().location
properties: {
deviceProperties: {
deviceModel: 'Azure VPN Gateway'
deviceVendor: 'Microsoft'
linkSpeedInMbps: 100
}
virtualWan: {
id: vwan.id
}
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
'10.1.0.0/16'
]
}
vpnSiteLinks: [
{
name: 'hubVpnGw2Link'
properties: {
ipAddress: hubVpnGwPip2.properties.ipAddress
linkProperties: {
linkProviderName: 'Microsoft'
linkSpeedInMbps: 100
}
}
}
]
}
}
GatewaySubnet を /27 以上にする
普通のハブには ExpressRoute Gateway が存在するケースが多いと思います。その場合、普通のハブに VPN Gateway を追加するためには、GatewaySubnet のサイズが /27 またはより短いプレフィックスである必要があります。
ゲートウェイ サブネットは /27 またはそれより短いプレフィックス (/26、/25 など) でなければなりません。そうでないと、ExpressRoute 仮想ネットワーク ゲートウェイを追加したときに、エラー メッセージが表示されます。
動作確認
普通のハブのスポーク仮想ネットワーク上に起動した仮想マシンの有効なルートは次の通りです。通信を Azure Firewall 経由にするために、BGP 伝搬を無効にしたルートテーブルを適用しているので、VPN Gateway の先に存在する仮想ハブ側のアドレス帯は有効なルートに乗ってきません。
略> $rts = Get-AzEffectiveRouteTable -ResourceGroupName "test0813vwan3" -NetworkInterfaceName "spokeVmNic"
略> $rts | ft AddressPrefix, NextHopIpAddress, NextHopType ,State
AddressPrefix NextHopIpAddress NextHopType State
------------- ---------------- ----------- -----
{10.1.0.0/24} {} VnetLocal Active
{10.0.0.0/16} {} VNetPeering Active
{0.0.0.0/0} {} Internet Invalid
{0.0.0.0/0} {10.0.1.4} VirtualAppliance Active
仮想ハブ側のスポーク仮想ネットワーク上に起動した仮想マシンの有効なルートは次の通りです。ルーティングインテントを有効化しているので、0.0.0.0/0と10.0.0.0/8、172.16.0.0/12、192.168.0.0/16の4つのアドレス帯が、Azure Firewall の IP アドレスである 192.168.0.132に向いています。
> $rts = Get-AzEffectiveRouteTable -ResourceGroupName "test0813vwan3" -NetworkInterfaceName "vwanspokeVmNic"
> $rts | ft AddressPrefix, NextHopIpAddress, NextHopType ,State
AddressPrefix NextHopIpAddress NextHopType State
------------- ---------------- ----------- -----
{192.168.1.0/24} {} VnetLocal Active
{192.168.0.0/24} {} VNetPeering Active
{192.168.0.0/16} {192.168.0.132} VirtualNetworkGateway Active
{0.0.0.0/0} {192.168.0.132} VirtualNetworkGateway Active
{10.0.0.0/8} {192.168.0.132} VirtualNetworkGateway Active
{172.16.0.0/12} {192.168.0.132} VirtualNetworkGateway Active
仮想ハブの Default のルートテーブルは次の通りです。ルーティングインテントを有効化しているので、4つのアドレス帯宛ての通信が Azure Firewall に吸い込まれていることが分かります。
> Get-AzVhubEffectiveRoutes -virtualWanResourceType "RouteTable" -resourceId "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test0813vwan3/providers/Microsoft.Network/virtualHubs/vhub/hubRouteTables/defaultRouteTable" -vhubId "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test0813vwan3/providers/Microsoft.Network/virtualHubs/vhub"
addressPrefixes : {0.0.0.0/0}
nextHops : azureFirewalls/vwanFirewall
nextHopType : Azure Firewall
routeOrigin : virtualHubs/vhub/hubRouteTables/defaultRouteTable
asPath :
addressPrefixes : {10.0.0.0/8}
nextHops : azureFirewalls/vwanFirewall
nextHopType : Azure Firewall
routeOrigin : virtualHubs/vhub/hubRouteTables/defaultRouteTable
asPath :
addressPrefixes : {172.16.0.0/12}
nextHops : azureFirewalls/vwanFirewall
nextHopType : Azure Firewall
routeOrigin : virtualHubs/vhub/hubRouteTables/defaultRouteTable
asPath :
addressPrefixes : {192.168.0.0/16}
nextHops : azureFirewalls/vwanFirewall
nextHopType : Azure Firewall
routeOrigin : virtualHubs/vhub/hubRouteTables/defaultRouteTable
asPath :
仮想ハブ内の Azure Firewall のルーティングは次の通りです。VPN サイトで静的に設定したアドレス2つが仮想ハブ内の VPN ゲートウェイに向いていることが分かります。
Get-AzVhubEffectiveRoutes -virtualWanResourceType "AzureFirewalls" -resourceId "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test0813vwan3/providers/Microsoft.Network/azureFirewalls/vwanFirewall" -vhubId "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test0813vwan3/providers/Microsoft.Network/virtualHubs/vhub"
addressPrefixes : {10.0.0.0/16}
nextHops : vpnGateways/vwanVpnGw
nextHopType : VPN_S2S_Gateway
routeOrigin : vpnGateways/vwanVpnGw
asPath :
addressPrefixes : {10.1.0.0/16}
nextHops : vpnGateways/vwanVpnGw
nextHopType : VPN_S2S_Gateway
routeOrigin : vpnGateways/vwanVpnGw
asPath :
addressPrefixes : {192.168.1.0/24}
nextHops : virtualHubs/vhub/hubVirtualNetworkConnections/vhub-to-vwanSpoke
nextHopType : Virtual Network Connection
routeOrigin : virtualHubs/vhub/hubVirtualNetworkConnections/vhub-to-vwanSpoke
asPath :
addressPrefixes : {0.0.0.0/0}
nextHops :
nextHopType : Internet
routeOrigin :
asPath :
自作関数Get-AzVhubEffectiveRoutesの詳細
function Get-AzVhubEffectiveRoutes {
param (
[Parameter(Mandatory=$true)][string]$virtualWanResourceType,
[Parameter(Mandatory=$true)][string]$resourceId,
[Parameter(Mandatory=$true)][string]$vhubId
)
$body = @{
"VirtualWanResourceType"=$virtualWanResourceType
"ResourceId"=$resourceId
} | convertto-json
$url = $vhubId + "/effectiveRoutes?api-version=2022-05-01"
write-host $url
$res = Invoke-AzRest -Path $url -Method POST -Payload $body
$operationUrl = $res.Headers.GetValues('Azure-AsyncOperation')
$operationUrl = $operationUrl[0] -replace "https://management.azure.com",""
do {
$routes = Invoke-Azrest -path $operationUrl -method Get
} until ( ($routes.Content | convertfrom-json).status -ne "InProgress" )
($routes.Content | convertfrom-json).properties.output.value | Select-Object addressPrefixes, `
@{Label="nextHops"; Expression={[regex]::Match($_.nextHops,"Microsoft.Network/(.*)").Groups[1].value}}, `
@{Label="nextHopType"; Expression={$_.nextHopType}}, `
@{Label="routeOrigin"; Expression={[regex]::Match($_.routeOrigin,"Microsoft.Network/(.*)").Groups[1].value}}, `
@{Label="asPath"; Expression={$_.asPath}} | fl *
}
2つのスポーク間の仮想マシン間で SSH を試すと無事に通ります。大成功。
# 仮想ハブ側の仮想マシンから普通のハブ側に SSH
username@vwanSpokeVm:~$ ssh username@10.1.0.4
# SSH 接続後に ss コマンドを叩くと、仮想ハブ側の仮想マシンから SSH されていることが分かる
username@spokeVm:~$ ss -tnla
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
ESTAB 0 52 10.1.0.4:22 192.168.1.4:50816
LISTEN 0 128 [::]:22 [::]:*
普通のハブの Azure Firewall と仮想ハブの Azure Firewall のログに上記の SSH 通信が記録されていますので、ちゃんと両方の Firewall を経由してそうです。
> $query = "AZFWNetworkRule | where SourcePort == '50816' | extend fw=split(_ResourceId, '/')[-1]| project TimeGenerated,fw,Action,SourceIp,SourcePort,DestinationIp,DestinationPort,Protocol"
> $result = Invoke-AzOperationalInsightsQuery -WorkspaceId "bd6681ca-a981-464a-91e8-3f28d39f91fa" -Query $query
> $result.Results
TimeGenerated : 2025-08-13T14:36:59.930295Z
fw : vwanfirewall
Action : Allow
SourceIp : 192.168.1.4
SourcePort : 50816
DestinationIp : 10.1.0.4
DestinationPort : 22
Protocol : TCP
TimeGenerated : 2025-08-13T14:36:59.980775Z
fw : hubfirewall
Action : Allow
SourceIp : 192.168.1.4
SourcePort : 50816
DestinationIp : 10.1.0.4
DestinationPort : 22
Protocol : TCP
おわりに
普通のハブアンドスポークな仮想ネットワークと Virtual WAN を利用したハブアンドスポークな仮想ネットワークを S2S VPN で接続してみました。「そりゃそうだろ」という話ではありますが普通につながりました。
Note
- 当サイトは個人のブログです。このブログに示されている見解や意見は個人的なものであり、所属組織の見解や意見を表明するものではありません。
- 公開情報を踏まえて正確な情報を掲載するよう努めますが、その内容の完全性や正確性、有用性、安全性、最新性について一切保証しません。
- 添付文章やリンク先などを含む本サイトの内容は作成時点でのものであり、予告なく変更される場合があります。