🐈

Azure Route Server と FRRouting の間で BGP ピアを張る

2022/08/16に公開

TL;DR

  • Azure Route Server と NVA っぽいやつで BGP を張り隊
  • NVA の代わりに FRRouting をインストールした Ubuntu Server を使ってみる
  • 全体的に Bicep で自動化したので待ってれば完了します

Update log

  • 全体的に # (見出し1) から ## (見出し2) に変更 - 2024/07/14
  • TL;DR とかの追加 - 2024/07/14

はじめに

さて、だいぶマニアックなこちらの記事へようこそ。
Azure Route Server (ARS) を使う場合には Network Virtual Appliances (NVA) が必要となりますが、その NVA として FRRouting をインストールした Ubuntu Server を利用してみます。

初回に書いたころからは Bicep を少し更新しているのでこちらも合わせてご覧ください。

Ubunutu Server 22.04 版も作りました

OS のバージョンが変わっただけでほかに何も変わっていないですが、こちらの repos も参考にどうぞ。

https://github.com/skmkzyk/bicep-templates/tree/main/20230514_ars-frrouting-ubuntu2204

cloud-init を使って自動化しました

以下の directory にあるので参考にしていただければと。
FRRouting を Ubuntu Server 20.04 にインストールし NVA 化したうえで、Azure Route Server (ARS) と peer が張れる所までは自動化されています。

https://github.com/skmkzyk/bicep-templates/tree/main/20220816_frrouting-ars-cloud-init

インフラの準備

ここは適当に Bicep で一発ですね、ちゃちゃっとやってしまいましょう。
以下の Bicep ファイルで以下の内容を作ってくれます。
自作の module については下のほうに置いておきます。

  • VNet
  • Bastion
  • Azure Route Server
  • Azure VM
main.bicep
main.bicep
param location01 string = 'eastasia'

param sshKeyRGName string

param isInitialDeploy bool = false
var useExisting = !isInitialDeploy

param publicKeyName string

resource public_key 'Microsoft.Compute/sshPublicKeys@2022-03-01' existing = {
  name: publicKeyName
  scope: resourceGroup(sshKeyRGName)
}

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'
        }
      }
      {
        name: 'RouteServerSubnet'
        properties: {
          addressPrefix: '10.0.210.0/24'
        }
      }
    ]
  }

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

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

var rs01Name = 'rs-hub00'
module rs01 '../lib/route-server.bicep' = {
  name: rs01Name
  params: {
    location: location01
    routeServerName: rs01Name
    vnetName: hub00.name
    useExisting: useExisting
    bgpConnections: [
      {
        name: vm_hub00.name
        ip: vm_hub00.outputs.privateIP
        asn: '65001'
      }
    ]
  }
}

var vm01Name = 'vm-hub00'
module vm_hub00 '../lib/ubuntu2004.bicep' = {
  name: vm01Name
  params: {
    location: location01
    keyData: public_key.properties.publicKey
    subnetId: hub00::defaultsubnet.id
    vmName: vm01Name
  }
}

NVA の作成

Azure Route Server は BGP で経路を受け取ってそれを Virtual Network Gateway や VNet に影響させるコンポーネントであり、BGP が喋れる何らかの NVA を用意する必要があります。
チュートリアル: Azure Route Server とネットワーク仮想アプライアンスの間のピアリングを構成する では Quagga を使っているようですが、ここでは FRRouting を使っていきます。

NVA として使用するのは上記 Bicep で vm-hub00 と名前を付けた Ubuntu Server 20.04 LTS を利用します。
今回 Bastion を利用しているので、手元の Windows Terminal から Bastion を利用して接続してみます。
--target-resource-id の指定が長ったらしいのですが、最近の Azure Portal は Azure VM を選択してから、右上の JSON View をクリックすると Resource ID をシュッと出してくれます。

az network bastion ssh --name "bast-hub00" `
  --resource-group "simple-ars-nva" `
    --target-resource-id "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxxxxxxxxx/providers/Microsoft.Compute/virtualMachines/vm-hub00" `
    --auth-type "ssh-key" `
    --username "xxxxxxxx" `
    --ssh-key "C:\Users\kasakemi\.ssh\id_rsa"

まずはとりあえず sudo apt updatesudo apt upgrade とした後に、sudo reboot しておきます。

そのあとは、公式の手順 である FRR Debian repository に沿って進めていきます。
記事作成時点での script 配下のとおりです。
いつか使えなくなるかもしれないので一応折りたたんでおきまして、基本は公式の手順を見てください。

frr install
# add GPG key
curl -s https://deb.frrouting.org/frr/keys.asc | sudo apt-key add -

# possible values for FRRVER: frr-6 frr-7 frr-8 frr-stable
# frr-stable will be the latest official stable release
FRRVER="frr-stable"
echo deb https://deb.frrouting.org/frr $(lsb_release -s -c) $FRRVER | sudo tee -a /etc/apt/sources.list.d/frr.list

# update and install FRR
sudo apt update && sudo apt install frr frr-pythontools

よくあるパターンですが、GPG 鍵の追加、パッケージがおいてある場所の追加、sudo apt update した後に sudo apt install という感じですね。

FRRouting の設定

FRRouting のインストールまでが終わったので、実際に設定を入れていきます。
FRRouting は Cisco ライクな CLI を持っているのでこれを利用します。

その前に、BGP や OSPF などのプロトコルはそれぞれ機能の有効化/無効化を選ぶようになっており、規定ではすべて無効化されています。
/etc/frr/daemons を編集し、bgpd=no となっている個所を bgpd=yes と編集したのちに、systemctl restart frr で daemon を再起動します。

sudo sed -i.org 's/bgpd=no/bgpd=yes/' /etc/frr/daemons
sudo systemctl restart frr

そのあと、めんどうなので sudo -s で上がった後に vtysh と叩くと FRRouting の CLI で設定が可能になります。

root@vm-hub00:~# vtysh

Hello, this is FRRouting (version 8.3).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

vm-hub00#

実際の設定の入力は conf t を入力した後からです。

vm-hub00# conf t
vm-hub00(config)#

入力する内容は大まかにこんな感じです。
いくつかの点に気を付けて編集すれば大まかにはコピペで入力できるかと思います。

ip route 10.0.210.0/24 10.0.0.1
!
router bgp 65001
 neighbor 10.0.210.4 remote-as 65515
 neighbor 10.0.210.4 ebgp-multihop 255
 neighbor 10.0.210.5 remote-as 65515
 neighbor 10.0.210.5 ebgp-multihop 255
 !
 address-family ipv4 unicast
  neighbor 10.0.210.4 soft-reconfiguration inbound
  neighbor 10.0.210.4 route-map rmap-bogon-asns in
  neighbor 10.0.210.4 route-map rmap-azure-asns out
  neighbor 10.0.210.5 soft-reconfiguration inbound
  neighbor 10.0.210.5 route-map rmap-bogon-asns in
  neighbor 10.0.210.5 route-map rmap-azure-asns out
 exit-address-family
exit
!
bgp as-path access-list azure-asns seq 5 permit _65515_
bgp as-path access-list bogon-asns seq 5 permit _0_
bgp as-path access-list bogon-asns seq 10 permit _23456_
bgp as-path access-list bogon-asns seq 15 permit _1310[0-6][0-9]_|_13107[0-1]_
bgp as-path access-list bogon-asns seq 20 deny _65515_
bgp as-path access-list bogon-asns seq 25 permit ^65
!
route-map rmap-bogon-asns deny 5
 match as-path bogon-asns
exit
!
route-map rmap-bogon-asns permit 10
exit
!
route-map rmap-azure-asns deny 5
 match as-path azure-asns
exit
!
route-map rmap-azure-asns permit 10
exit
!
  • ip route 10.0.210.0/24 10.0.0.1
    Azure Route Server は RouteServerSubnet という別の subnet にあるため、経路が解決可能となるよう Static Route を入れておきます。
  • router bgp 65001
    この 65001 という数字は 64512 から 65534 の間で、ある程度好きにきめられます。
    これは Private AS Number と呼ばれている、内部ネットワークで自由に使える IP アドレスの AS 番号版という感じです。
  • neighbor 10.0.210.4 remote-as 65515
    ここが BGP の一番基本的な設定です。
    remote-as は BGP の通信相手の AS 番号を書きますが、Azure Route Server は常に 65515 で動いています。
  • neighbor 10.0.210.4 ebgp-multihop 255
    こまけぇ話は置いておきますが、これがないと BGP で受け取った経路が inactive となってしまいます。
    詳しくはググっていただければと思いますが、とりあえず connected な範囲じゃない BGP peer に対してはこれがいるんです、はい。
    Azure ならではの事情もあります。
  • neighbor 10.0.210.4 soft-reconfiguration inbound
    これはなくてもいいです、あった方がトラブルシュートに役に立つやつです。
    これを入れると show ip bgp nei x.x.x.x received-routes で結果がとれるようになります。
  • neighbor 10.0.210.4 route-map rmap-bogon-asns inneighbor 10.0.210.4 route-map rmap-azure-asns out
    bgp ebgp-requires-policy に記載がありますが、RFC8212 準拠するためにはなんらかの policy を掛けてあげる必要があり、せっかくならということで少しは意味あるものにしています。
    前者については Bogon ASN filter policy configuration example を参考にさせていただきました。
    だいぶ乱暴な気がするんだよなこれ。
    Azure 内部で使っている ASN 65515 を例外扱いしています。

動作確認

うまくいけば show ip bgp nei x.x.x.x で Establish が見えるかなと思います。

vm-hub00# show ip bgp nei 10.0.210.4
BGP neighbor is 10.0.210.4, remote AS 65515, local AS 65001, external link
  BGP version 4, remote router ID 10.0.210.4, local router ID 10.0.0.4
  BGP state = Established, up for 00:46:19
  Last read 00:00:11, Last write 00:00:19
  Hold time is 180, keepalive interval is 60 seconds
  Configured conditional advertisements interval is 60 seconds

show ip bgp summary で簡単なものがまとめてみれます。
Azure Route Server の 2 つの IP アドレスに対して、Up の時間が増えていれば大まかには大丈夫でしょう。

vm-hub00# show ip bgp sum

IPv4 Unicast Summary (VRF default):
BGP router identifier 10.0.0.4, local AS number 65001 vrf-id 0
BGP table version 7
RIB entries 1, using 192 bytes of memory
Peers 2, using 1447 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
10.0.210.4      4      65515        54        53        0    0    0 00:45:12            1        0 N/A
10.0.210.5      4      65515        55        53        0    0    0 00:45:14            1        0 N/A

Total number of neighbors 2

show ip bgp nei x.x.x.x received-routes で「受け取っている」経路が見れます。
Azure Route Server は規定で「VNet のアドレス範囲全体」を経路広報してきます。

vm-hub00# show ip bgp nei 10.0.210.4 received-routes
BGP table version is 7, local router ID is 10.0.0.4, vrf id 0
Default local pref 100, local AS 65001
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.0.0/16      10.0.210.4                             0 65515 i

Total number of prefixes 1

show ip bgp nei x.x.x.x advertised-routes で「広報している」経路が見れます。
が、この設定のままでは何も経路広報するものはないので何も出ないかと思います。

vm-hub00# show ip bgp nei 10.0.210.4 advertised-routes
vm-hub00#

例えば、ip route 10.0.0.0/8 10.0.0.1 を追加した後に、router bgp 65001address-family ipv4redistribute static を追加すると Static Route で設定した経路が BGP で経路広報されていきます。

vm-hub00# show ip bgp nei 10.0.210.4 advertised-routes
BGP table version is 9, local router ID is 10.0.0.4, vrf id 0
Default local pref 100, local AS 65001
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.0.0/8       0.0.0.0                  0         32768 ?
*> 10.0.210.0/24    0.0.0.0                  0         32768 ?

Total number of prefixes 2

見てのとおり 10.0.210.0/24 は不要な経路です。
簡単に prefix-filter でこれを除いておきます。

こんな感じで追加の config を入れておきます。

ip prefix-list PRE01 seq 5 deny 10.0.210.0/24
ip prefix-list PRE01 seq 10 permit any
!
router bgp 65001
 address-family ipv4 unicast
  redistribute static
  neighbor 10.0.210.4 prefix-list PRE01 out
  neighbor 10.0.210.5 prefix-list PRE01 out
 exit-address-family
exit
!

これにより、10.0.0.0/8 だけが経路広報されるように変わります。

vm-hub00# show ip bgp nei 10.0.210.4 advertised-routes
BGP table version is 9, local router ID is 10.0.0.4, vrf id 0
Default local pref 100, local AS 65001
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.0.0/8       0.0.0.0                  0         32768 ?

Total number of prefixes 1

まとめ

この記事はここまでとします。
実際の利用方法についてはまた別の記事にしようかと思います。

今回の真の目的は Bastion を使った SSH のメモと、FRRouting のインストール方法のメモでしたw

参考

  • チュートリアル: Azure Route Server とネットワーク仮想アプライアンスの間のピアリングを構成する

https://learn.microsoft.com/azure/route-server/tutorial-configure-route-server-with-quagga

  • FRRouting

https://frrouting.org/

  • FRR Debian repository

https://deb.frrouting.org/

  • Quagga

https://quagga.net/


module として利用している Bicep ファイルを置いておきます。

bastion.bicep
bastion.bicep
param location string = 'eastasia'
param bastionName string = 'bast01'
param vnetName string

resource vnet 'Microsoft.Network/virtualNetworks@2022-01-01' existing = {
  name: vnetName

  resource azureBastionSubnet 'subnets' existing = {
    name: 'AzureBastionSubnet'
  }
}

resource pip 'Microsoft.Network/publicIPAddresses@2022-01-01' = {
  name: 'pip-${bastionName}'
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAllocationMethod: 'Static'
  }
}

resource bast01 'Microsoft.Network/bastionHosts@2022-01-01' = {
  name: bastionName
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    scaleUnits: 2
    enableTunneling: true
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: { id: vnet::azureBastionSubnet.id }
          publicIPAddress: { id: pip.id }
        }
      }
    ]
  }
}
route-server.bicep
route-server.bicep
param location string
param routeServerName string
param vnetName string
param useExisting bool = false
param bgpConnections array = []

resource vnet 'Microsoft.Network/virtualNetworks@2022-01-01' existing = {
  name: vnetName
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2022-01-01' existing = {
  parent: vnet
  name: 'RouteServerSubnet'
}

resource pip 'Microsoft.Network/publicIPAddresses@2022-01-01' = {
  name: 'pip-${routeServerName}'
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAllocationMethod: 'Static'
  }
}

resource rs 'Microsoft.Network/virtualHubs@2022-01-01' = if (!useExisting) {
  name: routeServerName
  location: location
  properties: {
    sku: 'Standard'
    allowBranchToBranchTraffic: true
  }

  resource ipconfig 'ipConfigurations' = if (!useExisting) {
    name: 'ipconfig'
    properties: {
      publicIPAddress: { id: pip.id }
      subnet: { id: subnet.id }
    }
  }
}

@batchSize(1)
resource bgp_conn 'Microsoft.Network/virtualHubs/bgpConnections@2022-01-01' = [for peer in bgpConnections: {
  parent: rs
  name: peer.name
  properties: {
    peerIp: peer.ip
    peerAsn: peer.asn
  }
  dependsOn: [
    rs::ipconfig
  ]
}]
ubuntu2004.bicep
ubuntu2004.bicep
param location string = 'eastasia'
param subnetId string
param vmName string
param adminUsername string = 'xxxxxxxx'
param keyData string
param enableNetWatchExtention bool = false

var vmNameSuffix = replace(vmName, 'vm-', '')

resource nic 'Microsoft.Network/networkInterfaces@2022-01-01' = {
  name: 'nic-${vmNameSuffix}'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: subnetId
          }
          privateIPAllocationMethod: 'Dynamic'
        }
      }
    ]
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: 'Standard_B2ms'
    }
    storageProfile: {
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
        deleteOption: 'Delete'
      }
      imageReference: {
        publisher: 'canonical'
        offer: '0001-com-ubuntu-server-focal'
        sku: '20_04-lts-gen2'
        version: 'latest'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
          properties: {
            deleteOption: 'Delete'
          }
        }
      ]
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      linuxConfiguration: {
        disablePasswordAuthentication: true
        ssh: {
          publicKeys: [
            {
              path: '/home/${adminUsername}/.ssh/authorized_keys'
              keyData: keyData
            }
          ]
        }
      }
    }
    diagnosticsProfile: {
      bootDiagnostics: {
        enabled: true
      }
    }
  }

  resource netWatchExt 'extensions' = if (enableNetWatchExtention) {
    name: 'AzureNetworkWatcherExtension'
    location: location
    properties: {
      autoUpgradeMinorVersion: true
      publisher: 'Microsoft.Azure.NetworkWatcher'
      type: 'NetworkWatcherAgentLinux'
      typeHandlerVersion: '1.4'
    }
  }
}

resource shutdown 'Microsoft.DevTestLab/schedules@2018-09-15' = {
  name: 'shutdown-computevm-${vmName}'
  location: location
  properties: {
    status: 'Enabled'
    taskType: 'ComputeVmShutdownTask'
    dailyRecurrence: {
      time: '00:00'
    }
    timeZoneId: 'Tokyo Standard Time'
    targetResourceId: vm.id
    notificationSettings: {
      status: 'Disabled'
    }
  }
}

output vmName string = vm.name
output privateIP string = nic.properties.ipConfigurations[0].properties.privateIPAddress
Microsoft (有志)

Discussion