今回は「Linuxで動かしながら学ぶTCP/IPネットワーク入門」に載せようか悩んで、結局は載せなかった内容のひとつを扱う。 Linux の Network Namespace を使って作った 2 台のルータを、Keepalived (VRRP) を使ってホットスタンバイで冗長化する構成を組んでみる。 つまり、2 台のうち 1 台のルータに障害が起きても、残りの 1 台が役務を引き継いで通信を継続できる状況を再現する。
使った環境は次のとおり。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.4 LTS Release: 20.04 Codename: focal $ uname -srm Linux 5.4.0-107-generic aarch64
もくじ
下準備
あらかじめ、必要なパッケージをインストールしておく。
$ sudo apt-get -y install keepalived iproute2 iputils-ping tcpdump
構築するネットワークについて
これから組んでいくネットワークは次のような論理構成になる。
この中で rt1
と rt2
がルータで、Keepalived の VRRP を使って冗長化する。
冗長化はマスター・バックアップ構成で、マスターのルータは仮想 IP アドレス (以下 VIP) を自身のネットワークインターフェイスに持つ。
図では IP アドレスの 192.0.2.254
と 198.51.100.254
が VIP を表している。
物理構成もあった方が分かりやすいはずだけど、描くのがちょっと大変そうなので今回は省略する。
ネットワークを構築する
まずは必要な Network Namespace を作成する。
名前は論理構成図と対応していて、ht*
がホスト、sw*
がスイッチ、rt*
がルータを表している。
$ sudo ip netns add ht1 $ sudo ip netns add sw1 $ sudo ip netns add rt1 $ sudo ip netns add rt2 $ sudo ip netns add sw2 $ sudo ip netns add ht2
続いて、veth インターフェイスのペアを作成する。 こちらも、名前は論理構成図と対応する。
$ sudo ip link add ht1-veth0 type veth peer name ht1-sw1 $ sudo ip link add ht2-veth0 type veth peer name ht2-sw2 $ sudo ip link add rt1-veth0 type veth peer name rt1-sw1 $ sudo ip link add rt1-veth1 type veth peer name rt1-sw2 $ sudo ip link add rt2-veth0 type veth peer name rt2-sw1 $ sudo ip link add rt2-veth1 type veth peer name rt2-sw2
作成したネットワークインターフェイスを Network Namespace に所属させていく。
$ sudo ip link set ht1-veth0 netns ht1 $ sudo ip link set ht2-veth0 netns ht2 $ sudo ip link set ht1-sw1 netns sw1 $ sudo ip link set ht2-sw2 netns sw2 $ sudo ip link set rt1-veth0 netns rt1 $ sudo ip link set rt1-veth1 netns rt1 $ sudo ip link set rt1-sw1 netns sw1 $ sudo ip link set rt1-sw2 netns sw2 $ sudo ip link set rt2-veth0 netns rt2 $ sudo ip link set rt2-veth1 netns rt2 $ sudo ip link set rt2-sw1 netns sw1 $ sudo ip link set rt2-sw2 netns sw2
Network Namespace に所属させたインターフェイスの状態を UP にする。
$ sudo ip netns exec ht1 ip link set ht1-veth0 up $ sudo ip netns exec ht2 ip link set ht2-veth0 up $ sudo ip netns exec sw1 ip link set ht1-sw1 up $ sudo ip netns exec sw2 ip link set ht2-sw2 up $ sudo ip netns exec rt1 ip link set rt1-veth0 up $ sudo ip netns exec rt1 ip link set rt1-veth1 up $ sudo ip netns exec sw1 ip link set rt1-sw1 up $ sudo ip netns exec sw2 ip link set rt1-sw2 up $ sudo ip netns exec rt2 ip link set rt2-veth0 up $ sudo ip netns exec rt2 ip link set rt2-veth1 up $ sudo ip netns exec sw1 ip link set rt2-sw1 up $ sudo ip netns exec sw2 ip link set rt2-sw2 up
今回は 3 つ以上のネットワークインターフェイスが同じネットワークセグメントに所属するため、ブリッジが必要になる。 Linux Bridge として用意する。
$ sudo ip netns exec sw1 ip link add dev sw1-br0 type bridge $ sudo ip netns exec sw1 ip link set sw1-br0 up $ sudo ip netns exec sw2 ip link add dev sw2-br0 type bridge $ sudo ip netns exec sw2 ip link set sw2-br0 up
ブリッジにネットワークインターフェイスを接続する。
$ sudo ip netns exec sw1 ip link set ht1-sw1 master sw1-br0 $ sudo ip netns exec sw1 ip link set rt1-sw1 master sw1-br0 $ sudo ip netns exec sw1 ip link set rt2-sw1 master sw1-br0 $ sudo ip netns exec sw2 ip link set ht2-sw2 master sw2-br0 $ sudo ip netns exec sw2 ip link set rt1-sw2 master sw2-br0 $ sudo ip netns exec sw2 ip link set rt2-sw2 master sw2-br0
インターフェイスに IP アドレスを付与する。
$ sudo ip netns exec ht1 ip address add 192.0.2.1/24 dev ht1-veth0 $ sudo ip netns exec ht2 ip address add 198.51.100.1/24 dev ht2-veth0 $ sudo ip netns exec rt1 ip address add 192.0.2.251/24 dev rt1-veth0 $ sudo ip netns exec rt1 ip address add 198.51.100.251/24 dev rt1-veth1 $ sudo ip netns exec rt2 ip address add 192.0.2.252/24 dev rt2-veth0 $ sudo ip netns exec rt2 ip address add 198.51.100.252/24 dev rt2-veth1
ルータとして動作するようにカーネルのパラメータを設定する。
$ sudo ip netns exec rt1 sysctl net.ipv4.ip_forward=1 $ sudo ip netns exec rt2 sysctl net.ipv4.ip_forward=1
ホストのデフォルトルートを設定する。 ただし、ここで設定するデフォルトルートの IP アドレスは VIP になる。 つまり、現時点では何処にも設定されていない。 VIP は、この後に設定する Keepalived がマスターになったタイミングで自動的に付与する。
$ sudo ip netns exec ht1 ip route add default via 192.0.2.254 $ sudo ip netns exec ht2 ip route add default via 198.51.100.254
下準備が終わったので、まずはセグメントに閉じた疎通を確認しておく。
$ sudo ip netns exec ht1 ping -c 3 192.0.2.251 PING 192.0.2.251 (192.0.2.251) 56(84) bytes of data. 64 bytes from 192.0.2.251: icmp_seq=1 ttl=64 time=0.141 ms 64 bytes from 192.0.2.251: icmp_seq=2 ttl=64 time=0.181 ms 64 bytes from 192.0.2.251: icmp_seq=3 ttl=64 time=0.179 ms --- 192.0.2.251 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2072ms rtt min/avg/max/mdev = 0.141/0.167/0.181/0.018 ms $ sudo ip netns exec ht1 ping -c 3 192.0.2.252 PING 192.0.2.252 (192.0.2.252) 56(84) bytes of data. 64 bytes from 192.0.2.252: icmp_seq=1 ttl=64 time=0.132 ms 64 bytes from 192.0.2.252: icmp_seq=2 ttl=64 time=0.151 ms 64 bytes from 192.0.2.252: icmp_seq=3 ttl=64 time=0.098 ms --- 192.0.2.252 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2098ms rtt min/avg/max/mdev = 0.098/0.127/0.151/0.021 ms $ sudo ip netns exec ht2 ping -c 3 198.51.100.251 PING 198.51.100.251 (198.51.100.251) 56(84) bytes of data. 64 bytes from 198.51.100.251: icmp_seq=1 ttl=64 time=0.113 ms 64 bytes from 198.51.100.251: icmp_seq=2 ttl=64 time=0.174 ms 64 bytes from 198.51.100.251: icmp_seq=3 ttl=64 time=0.163 ms --- 198.51.100.251 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2071ms rtt min/avg/max/mdev = 0.113/0.150/0.174/0.026 ms $ sudo ip netns exec ht2 ping -c 3 198.51.100.252 PING 198.51.100.252 (198.51.100.252) 56(84) bytes of data. 64 bytes from 198.51.100.252: icmp_seq=1 ttl=64 time=0.132 ms 64 bytes from 198.51.100.252: icmp_seq=2 ttl=64 time=0.162 ms 64 bytes from 198.51.100.252: icmp_seq=3 ttl=64 time=0.167 ms --- 198.51.100.252 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2088ms rtt min/avg/max/mdev = 0.132/0.153/0.167/0.015 ms
Keepalived でルータを冗長化する
では、ここからルータを冗長化する作業に入る。
まずは rt1
で動かす Keepalived の設定ファイルを用意する。
Keepalived 自体が Network Namespace に対応しているため、先頭の部分で動作する Network Namespace として rt1
を指定している。
ポイントは vrrp_sync_group
を使っているところ。
こうすると、グループ内のいずれかの vrrp_instance
に障害が起きたとき、同期して状態を切り替えることができる。
$ cat << 'EOF' > rt1-keepalived.conf net_namespace rt1 vrrp_sync_group VirtualGroup1 { group { VirtualInstance1 VirtualInstance2 } } vrrp_instance VirtualInstance1 { state BACKUP interface rt1-veth0 virtual_router_id 1 priority 20 advert_int 1 nopreempt authentication { auth_type PASS auth_pass passwd } virtual_ipaddress { 192.0.2.254/24 } } vrrp_instance VirtualInstance2 { state BACKUP interface rt1-veth1 virtual_router_id 1 priority 20 advert_int 1 nopreempt authentication { auth_type PASS auth_pass passwd } virtual_ipaddress { 198.51.100.254/24 } } EOF
上記の設定ファイルを使って Keepalived を起動する。
$ sudo keepalived -nlP -p /var/run/keepalived1 -f rt1-keepalived.conf Sun Apr 3 00:07:54 2022: Starting Keepalived v2.0.19 (10/19,2019) Sun Apr 3 00:07:54 2022: WARNING - keepalived was build for newer Linux 5.4.166, running on Linux 5.4.0-107-generic #121-Ubuntu SMP Thu Mar 24 16:07:22 UTC 2022 Sun Apr 3 00:07:54 2022: Command line: 'keepalived' '-nlP' '-p' '/var/run/keepalived1' '-f' 'rt1-keepalived.conf' Sun Apr 3 00:07:54 2022: Opening file 'rt1-keepalived.conf'. Sun Apr 3 00:07:54 2022: Changing syslog ident to Keepalived_rt1 Sun Apr 3 00:07:54 2022: Starting VRRP child process, pid=1897 Sun Apr 3 00:07:54 2022: Registering Kernel netlink reflector Sun Apr 3 00:07:54 2022: Registering Kernel netlink command channel Sun Apr 3 00:07:54 2022: Opening file 'rt1-keepalived.conf'. Sun Apr 3 00:07:54 2022: Registering gratuitous ARP shared channel Sun Apr 3 00:07:54 2022: (VirtualInstance1) Entering BACKUP STATE (init) Sun Apr 3 00:07:54 2022: (VirtualInstance2) Entering BACKUP STATE (init) Sun Apr 3 00:07:57 2022: (VirtualInstance1) Entering MASTER STATE Sun Apr 3 00:07:57 2022: VRRP_Group(VirtualGroup1) Syncing instances to MASTER state Sun Apr 3 00:07:57 2022: (VirtualInstance2) Entering MASTER STATE
Entering MASTER STATE
という表示が出れば、ルータがマスターになったことを表している。
別のターミナルを開いて rt1
の IP アドレスを確認すると、ちゃんと VIP が付与されている。
$ sudo ip netns exec rt1 ip address show | grep .254/24 inet 192.0.2.254/24 scope global secondary rt1-veth0 inet 198.51.100.254/24 scope global secondary rt1-veth1
同様に rt2
で動作させる Keepalived 用の設定ファイルも用意する。
$ cat << 'EOF' > rt2-keepalived.conf net_namespace rt2 vrrp_sync_group VirtualGroup1 { group { VirtualInstance1 VirtualInstance2 } } vrrp_instance VirtualInstance1 { state BACKUP interface rt2-veth0 virtual_router_id 1 priority 10 advert_int 1 nopreempt authentication { auth_type PASS auth_pass passwd } virtual_ipaddress { 192.0.2.254/24 } } vrrp_instance VirtualInstance2 { state BACKUP interface rt2-veth1 virtual_router_id 1 priority 10 advert_int 1 nopreempt authentication { auth_type PASS auth_pass passwd } virtual_ipaddress { 198.51.100.254/24 } } EOF
上記の設定ファイルを使って rt2
用の Keepalived を起動する。
$ sudo keepalived -nlP -p /var/run/keepalived2 -f rt2-keepalived.conf Sun Apr 3 00:47:35 2022: Starting Keepalived v2.0.19 (10/19,2019) Sun Apr 3 00:47:35 2022: WARNING - keepalived was build for newer Linux 5.4.166, running on Linux 5.4.0-100-generic #113-Ubuntu SMP Thu Feb 3 18:43:29 UTC 2022 Sun Apr 3 00:47:35 2022: Command line: 'keepalived' '-nlP' '-p' '/var/run/keepalived2' '-f' 'rt2-keepalived.conf' Sun Apr 3 00:47:35 2022: Opening file 'rt2-keepalived.conf'. Sun Apr 3 00:47:35 2022: Changing syslog ident to Keepalived_rt2 Sun Apr 3 00:47:35 2022: Starting VRRP child process, pid=1300 Sun Apr 3 00:47:35 2022: Registering Kernel netlink reflector Sun Apr 3 00:47:35 2022: Registering Kernel netlink command channel Sun Apr 3 00:47:35 2022: Opening file 'rt2-keepalived.conf'. Sun Apr 3 00:47:35 2022: Registering gratuitous ARP shared channel Sun Apr 3 00:47:35 2022: (VirtualInstance1) Entering BACKUP STATE (init) Sun Apr 3 00:47:35 2022: (VirtualInstance2) Entering BACKUP STATE (init)
こちらは Entering BACKUP STATE
のまま、つまりバックアップになるので正しい。
tcpdump(1) でブリッジをキャプチャすると VRRP のパケットが流れているのが確認できる。
宛先の IP アドレス 224.0.0.18
は、VRRP ルータが所属するマルチキャストアドレス。
$ sudo ip netns exec sw1 tcpdump -tnl -i sw1-br0 vrrp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on sw1-br0, link-type EN10MB (Ethernet), capture size 262144 bytes IP 192.0.2.251 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 20, authtype simple, intvl 1s, length 20 IP 192.0.2.251 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 20, authtype simple, intvl 1s, length 20 IP 192.0.2.251 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 20, authtype simple, intvl 1s, length 20 ^C 3 packets captured 3 packets received by filter 0 packets dropped by kernel $ sudo ip netns exec sw2 tcpdump -tnl -i sw2-br0 vrrp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on sw2-br0, link-type EN10MB (Ethernet), capture size 262144 bytes IP 198.51.100.251 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 20, authtype simple, intvl 1s, length 20 IP 198.51.100.251 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 20, authtype simple, intvl 1s, length 20 IP 198.51.100.251 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 20, authtype simple, intvl 1s, length 20 ^C 3 packets captured 3 packets received by filter 0 packets dropped by kernel
疎通を確認する
これで、エンドツーエンドで通信する準備が整った。
実際に ht1
から ht2
に向けて Ping を打ってみよう。
$ sudo ip netns exec ht1 ping -c 3 198.51.100.1 PING 198.51.100.1 (198.51.100.1) 56(84) bytes of data. 64 bytes from 198.51.100.1: icmp_seq=1 ttl=63 time=0.166 ms 64 bytes from 198.51.100.1: icmp_seq=2 ttl=63 time=0.192 ms 64 bytes from 198.51.100.1: icmp_seq=3 ttl=63 time=0.214 ms --- 198.51.100.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2087ms rtt min/avg/max/mdev = 0.166/0.190/0.214/0.019 ms
ちゃんと疎通が確認できた。
試しに障害を起こしてみる
めでたしめでたし、と言いたいところだけど、せっかく冗長化しているのだから障害を起こさないと。
試しに sw1
につながっている rt1
のインターフェイスをダウンさせてみる。
これで VRRP のパケットが sw1
にマルチキャストで流れなくなる。
$ sudo ip netns exec sw1 ip link set rt1-sw1 down
実行すると rt1
がリンクダウンを検知してフォールトステートになる。
$ sudo keepalived -nlP -p /var/run/keepalived1 -f rt1-keepalived.conf ...(省略)... Sun Apr 3 00:48:37 2022: Netlink reports rt1-veth0 down Sun Apr 3 00:48:37 2022: (VirtualInstance1) Entering FAULT STATE Sun Apr 3 00:48:37 2022: (VirtualInstance1) sent 0 priority Sun Apr 3 00:48:37 2022: VRRP_Group(VirtualGroup1) Syncing instances to FAULT state Sun Apr 3 00:48:37 2022: (VirtualInstance2) Entering FAULT STATE
代わりに rt2
の方がマスターに昇格した。
$ sudo keepalived -nlP -p /var/run/keepalived2 -f rt2-keepalived.conf ...(省略)... Sun Apr 3 00:48:37 2022: (VirtualInstance2) Backup received priority 0 advertisement Sun Apr 3 00:48:39 2022: (VirtualInstance1) Entering MASTER STATE Sun Apr 3 00:48:39 2022: VRRP_Group(VirtualGroup1) Syncing instances to MASTER state Sun Apr 3 00:48:39 2022: (VirtualInstance2) Entering MASTER STATE
VIP を確認しても rt2
の方に移動している。
$ sudo ip netns exec rt1 ip address show | grep .254/24 $ sudo ip netns exec rt2 ip address show | grep .254/24 inet 192.0.2.254/24 scope global secondary rt2-veth0 inet 198.51.100.254/24 scope global secondary rt2-veth1
試しに障害を復旧させてみよう。 先ほどダウンさせたリンクをアップに戻す。
$ sudo ip netns exec sw1 ip link set rt1-sw1 up
しかし、VIP は元に戻らない。
これは、Keepalived の設定で nopreempt
を指定しているため。
明示的にマスターを切り替える動作をしない限り、元の状態には戻らない。
この動作によって、中途半端に壊れているようなときにマスターとバックアップが頻繁にフラップするのを防ぐことができる。
$ sudo ip netns exec rt2 ip address show | grep .254/24 inet 192.0.2.254/24 scope global secondary rt2-veth0 inet 198.51.100.254/24 scope global secondary rt2-veth1
背後で ping(8) を打ち続けていると、障害が起こってもマスターが切り替わってルーティングが継続される様子が観察できる。 いじょう。
まとめ
今回は Network Namespace と Keepalived の VRRP を使ってルータの冗長化を試した。