背景
私は、研究室のベアメタルサーバーを用いてKubernetesクラスタを構築しており、その中で様々な技術的な検証を行なっています。
例えば、Longhornを用いた分散ストレージの検証を行なったり、VM間通信を行うためにVXLANによるオーバーレイネットワークを構築したりもしました。
参考:
以前はブリッジアダプタやVXLANなど、L2での通信技術を検証していましたが、今回はL3での通信を行う技術として、BGPというルーティングプロトコルを用いた通信を検証してみたいと思います。
BGPについて
概要
BGPは、異なるAS(Autonomous System: 自律システム)間で経路情報をやり取りする時に使われるルーティングプロトコルの一つです。
各ASは、IPセグメントとその宛先となるAS番号をテーブルとして保持しており、このテーブルを自動的に他のASと交換するためのプロトコルがBGPです。
VXLANとの比較
以前VXLANでのネットワーク構築も行ったため、BGPとVXLANの比較を簡単に行います。
なお、本内容については以下の書籍を参考にしています。
VXLANは、仮想的なL2ネットワークをL3ネットワークを超えて構築できる技術であり、場所に依存せずサーバ数の増大に対応でき、サーバの場所を移動したとしてもIPアドレスを変更する必要がありません。
なお、VXLANは通信をカプセル化を行うため、観測する場所によって通信データの中身が変化してしまい、可観測性が低いというデメリットがあります。
一方でBGPはL3ネットワークなので、送信先/送信元が場所によって変化したりせず観測しやすく、BGPはVXLAN等のオーバーレイ技術と比較すると、可観測性が高いというメリットがあります。
また、カプセル化のオーバーヘッドがないというメリットもあります。
一方でBGPのデメリットは、ルータのルーティングテーブルを書き換える管理が必要なので、ルーティングテーブルが肥大化した時に各ノードへの負担が増大するという点です。
また、ホストのサブネットに依存するためサーバが移動するとIP変更が必要になる点もVXLANとの違いとして挙げられます。
BGPを用いたネットワーク構築
構成図
最終的には、以下のようなネットワーク構成で通信を行いたいと考えています。
なお、事前にVirtualBoxにより4つのVMを計2台のホスト上に構築済みで、Kubernetesクラスタの構築を目的にしていたため、名前はk8s-master, k8s-worker-1~3としています。
なお、L2/L3を分離するためにBridgeはIPアドレスを持たずL2レイヤで動くようにしていますが、L3でVM外と通信できるようにするために、vethペアを使いBridgeとL3ルータを仮想ケーブルにより接続しています。Bridge側のインターフェースを veth-br とし、ルータ側のインターフェースを veth-gw としており、L2/L3の境界を明確にしています。
ここでは、L3ゲートウェイである veth-gw の背後にあるVMネットワーク全体( 10.10.10.0/24と10.10.20.0/24 )が各ASのL3ネットワーク範囲となります。
BGPでは、各ASが自分の所有する上記のようなサブネットを広告します。それにより、互いに経路情報を学習することで、ルータ間で異なるサブネット間の到達経路を共有します。
このような広告・学習のプロセスにより、異なるL3ネットワーク間で経路選択・ルーティングが自動化され、VMが属する異なるAS間でもL3レベルでの通信が可能になります。
この通信を実現するための設定を以下に行っていきます。
設定(デフォルトゲートウェイ設定まで)
仮想Bridge br-vm 作成
各VMのネットワークインターフェースを束ね、VM間のL2接続として機能する仮想Bridge br-vm を作成します。
また、ホストOSでL3ルーティングを有効化する設定も追加します。
sudo ip link add br-vm type bridge
sudo ip link set br-vm up
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forward.conf
sudo sysctl --system
仮想BridgeにVMを接続
各VMを、仮想Bridgeである br-vm に接続し、同一L2セグメント上に配置します。
VBoxManage controlvm "k8s-master" poweroff
VBoxManage controlvm "k8s-worker-1" poweroff
VBoxManage modifyvm "k8s-master" --nic2 bridged --bridgeadapter2 "br-vm"
VBoxManage modifyvm "k8s-worker-1" --nic2 bridged --bridgeadapter2 "br-vm"
VBoxManage startvm "k8s-master" --type headless
VBoxManage startvm "k8s-worker-1" --type headless
VBoxManage controlvm "k8s-worker-2" poweroff
VBoxManage controlvm "k8s-worker-3" poweroff
VBoxManage modifyvm "k8s-worker-2" --nic2 bridged --bridgeadapter2 "br-vm"
VBoxManage modifyvm "k8s-worker-3" --nic2 bridged --bridgeadapter2 "br-vm"
VBoxManage startvm "k8s-worker-2" --type headless
VBoxManage startvm "k8s-worker-3" --type headless
veth作成
2つで1組の仮想LANケーブルのように動作するインターフェースである veth を設定します。 このペアを利用し、L2ブリッジ br-vm と L3ゲートウェイ veth-gw を分離したまま接続します。
veth-br 側を br-vm (L2ブリッジ)に接続し、veth-gw 側にL3ゲートウェイとしてのIPアドレスを割り当てます。
sudo ip link add veth-gw type veth peer name veth-br
sudo ip link set veth-br master br-vm
sudo ip link set veth-gw up
sudo ip link set veth-br up
sudo ip addr add 10.10.10.1/24 dev veth-gw
sudo ip link add veth-gw type veth peer name veth-br
sudo ip link set veth-br master br-vm
sudo ip link set veth-gw up
sudo ip link set veth-br up
sudo ip addr add 10.10.20.1/24 dev veth-gw
デフォルトゲートウェイ設定
VMが別サブネットへの通信を行う際、まず自サブネットのゲートウェイである veth-gw にパケットを転送できるように設定します。
sudo vim /etc/netplan/01-bridged.yamlで以下の設定を追加後、sudo netplan apply で反映させました。
network:
version: 2
renderer: networkd
ethernets:
enp0s8:
dhcp4: no
addresses: [10.10.10.101/24]
routes:
- to: 10.10.20.0/24
via: 10.10.10.1 # veth-gw IP
network:
version: 2
renderer: networkd
ethernets:
enp0s8:
dhcp4: no
addresses: [10.10.10.103/24]
routes:
- to: 10.10.20.0/24
via: 10.10.10.1 # veth-gw IP
network:
version: 2
renderer: networkd
ethernets:
enp0s8:
dhcp4: no
addresses: [10.10.20.105/24]
routes:
- to: 10.10.10.0/24
via: 10.10.20.1 # veth-gw IP
network:
version: 2
renderer: networkd
ethernets:
enp0s8:
dhcp4: no
addresses: [10.10.20.107/24]
routes:
- to: 10.10.10.0/24
via: 10.10.20.1 # veth-gw IP
設定後、各VMが別サブネットへの通信を行う際に veth-gw を経由するようになることをルートテーブルで確認します。
$ ip route
default via 10.0.4.2 dev enp0s9 proto dhcp src 10.0.4.15 metric 100
10.10.10.0/24 dev enp0s8 proto kernel scope link src 10.10.10.101
10.10.20.0/24 via 10.10.10.1 dev enp0s8 proto static
ここまでの設定を確認
br-vm に接続されているインターフェースを確認し、veth-br が接続されていることを確認します。
$ bridge link show master br-vm
19: veth-br@veth-gw: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br-vm state forwarding priority 32 cost 2
スイッチ br-vm が正しく起動していることを確認します。
$ ip -br link show br-vm
br-vm UP be:70:0a:f7:2a:98 <BROADCAST,MULTICAST,UP,LOWER_UP>
veth-gw がルータとして機能するためのIPが正しく設定されているかを確認します。
10.10.10.1/24 が問題なく設定されていることが確認できます。
$ ip addr show veth-gw
20: veth-gw@veth-br: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether d2:a4:d0:50:1b:25 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.1/24 scope global veth-gw
valid_lft forever preferred_lft forever
inet6 fe80::d0a4:d0ff:fe50:1b25/64 scope link
valid_lft forever preferred_lft forever
VMにsshし、NIC enp0s8 の状態を確認します。
割り振られているIPアドレスが 10.10.10.101/24 であり、veth-gw と同一サブネットであることが確認できます。
$ ip addr show enp0s8
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:92:62:c2 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.101/24 brd 10.10.10.255 scope global enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe92:62c2/64 scope link
valid_lft forever preferred_lft forever
ここで、VMからデフォルトゲートウェイである veth-gw にpingが通ることを確認します。
$ ping -c1 10.10.10.1
PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=0.970 ms
--- 10.10.10.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.970/0.970/0.970/0.000 ms
BGPによる広告設定
ここまでで各ASが自身のサブネットを持ち、L3で通信できる状態になりました。
次に、各ASが自分の所有するサブネットをBGPで広告し、相互に経路情報を交換できるようにします。
server-1側で、自ASの所有するサブネット 10.10.10.0/24 を広告します。
sudo tee /etc/frr/frr.conf >/dev/null <<'EOF'
frr defaults traditional
hostname nakazato5-1
service integrated-vtysh-config
!
router bgp 65001
bgp router-id 192.168.80.10
neighbor 192.168.80.11 remote-as 65002
!
address-family ipv4 unicast
network 10.10.10.0/24
neighbor 192.168.80.11 route-map ACCEPT-ALL in
neighbor 192.168.80.11 route-map ACCEPT-ALL out
exit-address-family
exit
!
route-map ACCEPT-ALL permit 10
!
EOF
sudo systemctl restart frr
sudo vtysh -c 'write memory'
server-2側で、自ASの所有するサブネット 10.10.20.0/24 を広告します。
sudo tee /etc/frr/frr.conf >/dev/null <<'EOF'
frr defaults traditional
hostname nakazato5-3
service integrated-vtysh-config
!
router bgp 65002
bgp router-id 192.168.80.11
neighbor 192.168.80.10 remote-as 65001
!
address-family ipv4 unicast
network 10.10.20.0/24
neighbor 192.168.80.10 route-map ACCEPT-ALL in
neighbor 192.168.80.10 route-map ACCEPT-ALL out
exit-address-family
exit
!
route-map ACCEPT-ALL permit 10
!
EOF
sudo systemctl restart frr
sudo vtysh -c 'write memory'
また、BGPデーモンを有効化するために、sudo vim /etc/frr/daemons で、bgpd=yes に変更します。
設定後、FRRを再起動します。
sudo systemctl restart frr
広告と学習の確認
BGPセッションの確立状況と経路学習結果を確認していきます。
経路情報の確認
まず、自AS( AS65001 ) が現在認識しているすべての経路情報(自分で広告したネットワーク+他ASから学習したネットワーク) を確認します。
以下の結果から、BGPセッションが確立しており、AS65002 から 10.10.20.0/24 の経路情報を正常に学習できていることがわかります。
これにより、AS65001 配下のVM(k8s-masterなど)が AS65002 配下のVM(k8s-worker-3など)宛てに L3通信できるようになります。
$ sudo vtysh -c 'show ip bgp'
BGP table version is 2, local router ID is 192.168.80.10, 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.10.10.0/24 192.168.80.11 0 65002 65001 i
*> 0.0.0.0 0 32768 i
*> 10.10.20.0/24 192.168.80.11 0 0 65002 i
Displayed 2 routes and 3 total paths
Path Attributeの確認
sudo vtysh -c 'show bgp ipv4 unicast 10.10.20.0/24 json' を実行し、BGP経路ごとの詳細な属性情報を確認します。
なお、出力は長いので、jsonから適宜要素を抽出しながら確認を行なっていきます。
server-1 (AS65001) のBGPルータが、server-2 (AS65002) というピアに対して経路を広告している:
"advertisedTo": {
"192.168.80.11": {
"hostname": "server-2"
}
}
10.10.20.0/24 の経路をserver-2 (AS65002) から学習し、有効かつ最適経路(bestpath)として選んでいる:
"paths": [
{
"aspath": {
"string": "65002",
"segments": [
{
"type": "as-sequence",
"list": [65002]
}
],
"length": 1
},
"origin": "IGP",
"valid": true,
"bestpath": {
"overall": true,
"selectionReason": "First path received"
}
}
]
次ホップを 192.168.80.11(server-2)とすることで、宛先へ到達する:
"nexthops": [
{
"ip": "192.168.80.11",
"hostname": "server-2",
"accessible": true,
"used": true
}
]
経路は外部BGPピアである、192.168.80.11 (AS65002) から学習されたことを示す:
"peer": {
"peerId": "192.168.80.11",
"routerId": "192.168.80.11",
"hostname": "server-2",
"type": "external"
}
ルートテーブルの確認
BGP経由で学習した経路が、Linuxカーネルのルートテーブルに反映されていることを確認します。
ここでは、AS65001 が AS65002 からネットワーク 10.10.20.0/24 の経路情報を学習し、
その結果として 「宛先 10.10.20.0/24 の通信は 192.168.80.11( AS65002 側ルータ)を経由する」 というルートが追加されています。
$ ip route show proto bgp
10.10.20.0/24 nhid 38 via 192.168.80.11 dev eno1 metric 20
通信の流れの整理
ここまでの設定により、各ホストOSがBGPルータとして動作し、互いにサブネット情報を交換する状態になっています。
たとえば、k8s-worker-2 ( 10.10.20.105 ) から k8s-master ( 10.10.10.101 ) へパケットを送信する場合、次のような流れで通信が行われます。
-
k8s-worker-2 → server-2の
veth-gw- k8s-worker-2 は宛先
10.10.10.101が異なるサブネットであるため、デフォルトゲートウェイ10.10.20.1(veth-gw)へパケットを転送する。
- k8s-worker-2 は宛先
-
server-2の
veth-gw→ server-1-
veth-gwは BGP によりAS65001側 (server-1) から10.10.10.0/24の経路情報を学習しており、「宛先10.10.10.0/24は次ホップ192.168.80.10(server-1)である」というルートを保持していいる。 - そのため、パケットは L3 ルーティングにより物理NIC
eno1経由で server-1 へ転送される。
-
-
server-1 → k8s-master
- server-1 に到着したパケットは、
veth-gwが受け取り、宛先10.10.10.101が自サブネット(10.10.10.0/24)であるため、L2転送により直接 k8s-master へ届けられる。
- server-1 に到着したパケットは、
-
k8s-master → k8s-worker-2の応答
- k8s-master からのICMP応答も、同様にBGP経路を用いて逆方向に返送される。
動作確認
pingによる疎通確認
AS65002 配下の k8s-worker-2( 10.10.20.105 ) から AS65001 配下の k8s-master( 10.10.10.101 )に対して pingが通ることから、BGP経由のルーティングが正常に機能していることがわかります。
# k8s-master IPへping
$ ping 10.10.10.101
PING 10.10.10.101 (10.10.10.101) 56(84) bytes of data.
64 bytes from 10.10.10.101: icmp_seq=1 ttl=62 time=1.12 ms
tcpdumpによるパケットの確認
次に、server-1 側で tcpdump を用いて ICMP パケットを観測します。
VXLAN のようなカプセル化が行われておらず、物理NIC(eno1)上でL3ルーティングにより直接転送されていることが確認できます。
$ sudo tcpdump -i eno1 -n icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eno1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:23:31.154815 IP 10.10.20.105 > 10.10.10.101: ICMP echo request, id 1, seq 198, length 64
14:23:31.154982 IP 10.10.10.101 > 10.10.20.105: ICMP echo reply, id 1, seq 198, length 64
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel
まとめ
本記事では、BGPを用いたVM間通信を、実際に動かしながら検証してみました。パケットの中身を見ることでVXLANとの違いについても理解を深めることができ、良かったです。
引き続き、興味のある技術について検証していきたいと考えています。
