CUBE SUGAR CONTAINER

技術系のこと書きます。

Linux の Network Namespace と Keepalived でルータの冗長化を試す

今回は「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

構築するネットワークについて

これから組んでいくネットワークは次のような論理構成になる。 この中で rt1rt2 がルータで、Keepalived の VRRP を使って冗長化する。 冗長化はマスター・バックアップ構成で、マスターのルータは仮想 IP アドレス (以下 VIP) を自身のネットワークインターフェイスに持つ。 図では IP アドレスの 192.0.2.254198.51.100.254 が VIP を表している。

f:id:momijiame:20220401005508p:plain
ネットワークの論理構成

物理構成もあった方が分かりやすいはずだけど、描くのがちょっと大変そうなので今回は省略する。

ネットワークを構築する

まずは必要な 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 を使ってルータの冗長化を試した。