🎉

複数の Hub-spoke アーキテクチャで spoke-to-spoke を実現する (VNet Peering + VPN 利用)

2022/07/29に公開

複数の Hub-spoke を VNet Peering + VPN で spoke-to-spoke を実現する

今回は hub-spoke の hub 同士を VNet Peering で接続してしまい、そのうえで Private IP を使用した VPN で接続します。
この方法でも spoke-to-spoke の接続を確立することができます。

アーキテクチャの概要図はこのような感じです。

multiple Hub-spoke connected via VPN on VNet Peering

メリットとしては Internet VPN 版と異なり、完全に閉域での接続となっています。
latency については後程別途比較してみようかと思いますが、論理的には最短距離でつなげるはずです。

デメリットとしては他と同じく VPN Gateway の SKU により帯域が制限されます。
具体的にどのような結果となるかは後程 microsoft/ethr を試した結果を追加します。
イメージとしては spoke - hub まではそれなりに帯域が広いですが、spoke - hub - hub まで来るとガクッと落ちます。


Bicep ファイルはこちらです。

main.bicep
main.bicep
param location01 string = 'eastasia'
param location02 string = 'eastasia'

param kvName string
param kvRGName string
param secretName string

param s2svpnSecretName string

param isInitialDeploy bool = false
var useExisting = !isInitialDeploy

resource kv 'Microsoft.KeyVault/vaults@2021-10-01' existing = {
  name: kvName
  scope: resourceGroup(kvRGName)
}

resource hub00 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-hub00'
  location: location01
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: '10.0.100.0/24'
        }
      }
      {
        name: 'GatewaySubnet'
        properties: {
          addressPrefix: '10.0.200.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

resource spoke10 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-spoke10'
  location: location01
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.10.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.10.0.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

module peering_hub0010 '../lib/vnet-peering.bicep' = {
  name: 'peering-hub00-spoke10'
  params: {
    vnet01Name: hub00.name
    vnet02Name: spoke10.name
    useRemoteGateways: true
    remoteGatewayName: vpngw01.outputs.vpngwName
  }
}

resource spoke20 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-spoke20'
  location: location01
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.20.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.20.0.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

module peering_hub0020 '../lib/vnet-peering.bicep' = {
  name: 'peering-hub00-spoke20'
  params: {
    vnet01Name: hub00.name
    vnet02Name: spoke20.name
    useRemoteGateways: true
    remoteGatewayName: vpngw01.outputs.vpngwName
  }
}

var vpngw01Name = 'vpngw-hub01'
module vpngw01 '../lib/vpngw_single.bicep' = {
  name: vpngw01Name
  params: {
    location: location01
    vnetName: hub00.name
    gatewayName: vpngw01Name
    bgpAsn: 65150
    enablePrivateIpAddress: true
    useExisting: useExisting
  }
}

module conn_vpngw01_vpngw02 '../lib/connection-vpngw.bicep' = {
  name: 'conn-${vpngw01Name}-${vpngw02Name}'
  params: {
    vpnGateway01Name: vpngw01.outputs.vpngwName
    vpnGateway02Name: vpngw02.outputs.vpngwName
    psk: kv.getSecret(s2svpnSecretName)
    enableBgp: true
    enablePrivateIpAddress: true
  }
}

var bast01Name = 'bast-hub00'
module bast01 '../lib/bastion.bicep' = {
  name: bast01Name
  params: {
    location: location01
    bastionName: bast01Name
    vnetName: hub00.name
  }
}

var vm01Name = 'vm-hub00'
module vm_loc01 '../lib/ws2019.bicep' = {
  name: vm01Name
  params: {
    location: location01
    adminPassword: kv.getSecret(secretName)
    subnetId: hub00::defaultsubnet.id
    vmName: vm01Name
  }
}

var vm02Name = 'vm-spoke10'
module vm_spoke01 '../lib/ws2019.bicep' = {
  name: vm02Name
  params: {
    location: location01
    adminPassword: kv.getSecret(secretName)
    subnetId: spoke10::defaultsubnet.id
    vmName: vm02Name
  }
}

var vm03Name = 'vm-spoke20'
module vm_spoke02 '../lib/ws2019.bicep' = {
  name: vm03Name
  params: {
    location: location01
    adminPassword: kv.getSecret(secretName)
    subnetId: spoke20::defaultsubnet.id
    vmName: vm03Name
  }
}

resource hub100 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-hub100'
  location: location02
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.100.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.100.0.0/24'
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: '10.100.100.0/24'
        }
      }
      {
        name: 'GatewaySubnet'
        properties: {
          addressPrefix: '10.100.200.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

module peering_hub00100 '../lib/vnet-peering.bicep' = {
  name: 'peering-hub00-hub100'
  params: {
    vnet01Name: hub00.name
    vnet02Name: hub100.name
  }
}

resource spoke110 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-spoke110'
  location: location02
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.110.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.110.0.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

module peering_hub100110 '../lib/vnet-peering.bicep' = {
  name: 'peering-hub100-spoke110'
  params: {
    vnet01Name: hub100.name
    vnet02Name: spoke110.name
    useRemoteGateways: true
    remoteGatewayName: vpngw02.outputs.vpngwName
  }
}

resource spoke120 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-spoke120'
  location: location02
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.120.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.120.0.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

module peering_hub100120 '../lib/vnet-peering.bicep' = {
  name: 'peering-hub100-spoke120'
  params: {
    vnet01Name: hub100.name
    vnet02Name: spoke120.name
    useRemoteGateways: true
    remoteGatewayName: vpngw02.outputs.vpngwName
  }
}

var vpngw02Name = 'vpngw-hub02'
module vpngw02 '../lib/vpngw_single.bicep' = {
  name: vpngw02Name
  params: {
    location: location02
    vnetName: hub100.name
    gatewayName: vpngw02Name
    bgpAsn: 65151
    enablePrivateIpAddress: true
    useExisting: useExisting
  }
}

var bast02Name = 'bast-hub100'
module bast02 '../lib/bastion.bicep' = {
  name: bast02Name
  params: {
    location: location02
    bastionName: bast02Name
    vnetName: hub100.name
  }
}

var vm11Name = 'vm-hub100'
module vm_loc02 '../lib/ws2019.bicep' = {
  name: vm11Name
  params: {
    location: location02
    adminPassword: kv.getSecret(secretName)
    subnetId: hub100::defaultsubnet.id
    vmName: vm11Name
  }
}

var vm12Name = 'vm-spoke110'
module vm_spoke11 '../lib/ws2019.bicep' = {
  name: vm12Name
  params: {
    location: location02
    adminPassword: kv.getSecret(secretName)
    subnetId: spoke110::defaultsubnet.id
    vmName: vm12Name
  }
}

var vm13Name = 'vm-spoke120'
module vm_spoke12 '../lib/ws2019.bicep' = {
  name: vm13Name
  params: {
    location: location02
    adminPassword: kv.getSecret(secretName)
    subnetId: spoke120::defaultsubnet.id
    vmName: vm13Name
  }
}

Internet VPN 版との差分が最小限に抑えられており、個人的に大満足です。
例えばこういったところが変わっています。

module vpngw01 '../lib/vpngw_single.bicep' = {
  name: vpngw01Name
  params: {
    location: location01
    vnetName: hub00.name
    gatewayName: vpngw01Name
    bgpAsn: 65150
    enablePrivateIpAddress: true
    useExisting: useExisting
  }
}

enablePrivateIpAddress パラメータを利用して VPN Gateway に Private IP アドレスを有効化しています。
useExisting パラメータについては 2 回目以降、VPN Gateway に関しては内部的に existing キーワードを使うための工夫です。
デプロイのコマンドをやや変えることで 2 回目以降を時短にします。
-p パラメータは複数書いてもいいのが面白いです。

  • 1 回目: az deployment group create -g multi-hub-vpn02 -f .\main.bicep -p `@parameter.json -p isInitialDeploy=true
  • 2 回目以降: az deployment group create -g multi-hub-vpn02 -f .\main.bicep -p `@parameter.json
Microsoft (有志)

Discussion