文章

keepalived - 再也不担心 OpenWRT 挂掉了

背景

在我家里有众多联网设备,为了让网络更好用,也为了让我的折腾不会导致网络全面瘫痪,我划分了不同的 VLAN。给家里老人使用的是纯正的网络,而另一个 VLAN 是用于 HomeLab,它们的网关 / DNS 设置等都不一样。为了方便畅通地获取学习资料,有时需要借助 Openwrt 的一些插件。在我以往的文章中提到过,它们是以旁路由的方式工作的。然而,时不时个别机器的抽风,让这个单点的网关也停止服务,不得又要折腾一番。于是想着,有必要让这个网关也高可用起来,挂掉一个备用自动顶上。如果你也有类似诉求,欢迎继续看。

实现原理

要解决这个问题或许有多种方案,在一个技术群里和朋友们聊起,比如写一个监听的小脚本,自动切换。再上流一点,使用 webhook 等结合自动化定时探测再远程触发切换网络。但听起来都不是太优雅的样子。

如果我们用几个关键词搜索一下,会发现 OpenWrt 官网有讨论 [1] 这个事情,并且给出了一个不错的解法。它就是基于 Keepalived 做热备份

keepalived is a linux daemon that uses VRRP (Virtual Router Redundancy Protocol) to healthcheck and elect a router on the network that will serve a particular IP. We'll be using a small subset of its features in our use case.

它和以往我们了解的基于 ARP 方式在 L2 层做 IP 欺骗的不一样(见 Metallb 等)。以下是它的一些简介(来源于 GPT4):

 VRRP(虚拟路由冗余协议)广播不是 ARP 广播,而是 VRRP 协议特定的消息格式。VRRP 广播使用的是 IP 包,封装在 IP 协议之上,通常使用 IPv4 或 IPv6。VRRP 消息是通过 IP 多播发送的,而不是 ARP 协议使用的以太网广播。

VRRP 消息格式 VRRP 消息封装在 IP 数据包中,使用的是以下格式:

IP 头部:标准的 IP 头部,其中源地址通常是发送 VRRP 广播的路由器的实际 IP 地址,目的地址是 VRRP 多播地址。对于 IPv4,多播地址是 224.0.0.18;对于 IPv6,是 ff02::12。

VRRP 头部:紧随 IP 头部之后,包含了 VRRP 的版本信息、虚拟路由器的 ID、优先级、心跳间隔和校验和等信息。

VRRP 的工作机制 VRRP 路由器通过多播地址发送 VRRP 广告,以便所有参与的 VRRP 路由器都能接收到这些广告。主路由器(Master)会定时发送 VRRP 广告,来宣告它拥有对应的虚拟 IP 地址。如果备份路由器(Backup)在预定的时间内没有收到来自主路由器的 VRRP 广告,它会认为主路由器已下线,并发起新一轮的主路由器选举。不同于 ARP ARP(地址解析协议)是用于解析网络层地址(如 IPv4 地址)到链路层地址(如以太网 MAC 地址)的协议。ARP 广播是在本地链路层发送的,目的是让所有在同一物理网络上的设备知道发送者的 IP 地址和对应的 MAC 地址。

相比之下,VRRP 的目的是在路由器之间协调哪一个应该承担虚拟路由器的角色,以提供网络路由的高可用性。VRRP 广播是在网络层上发送的,并且它使用的是特定的多播地址来通信,而不是像 ARP 那样使用硬件地址。

部署与配置

安装 keepalived

在 Openwrt 上安装 keepalived,可以通过 opkg 直接安装:

1
2
opkg update
opkg install keepalived

运气好你可能直接安装好了,就可以跳到下一小节。但也可能你会收到这样的提示:

1
2
 * satisfy_dependencies_for: Cannot satisfy the following dependencies for keepalived:
 * 	kernel (= 6.1.69-1-4bb6c728f5087dc3a67fcfdd70aa0707)

看起来是内核版本和源中的不太匹配了(或许你的 OpenWRT 太旧了)。但你或许也不想因为这就重新再刷个 OpenWRT 吧?有两种办法可解。

方案一:寻找对应版本的 ipk

我们可以直接去源仓库下载和我们内核比较匹配的版本:下载 keepalived.ipk 后,安装它:

1
2
3
4
5
# 下载包,你可能要选择合适自己的版本
wget https://downloads.openwrt.org/releases/23.05.2/packages/x86_64/packages/keepalived_2.2.7-10_x86_64.ipk

# 使用opkg安装此ipk
opkg install keepalived_2.2.7-10_x86_64.ipk  

要是像我一样不走运,还会报错:

1
2
3
4
5
6
7
Unknown package 'keepalived'.  
Collected errors:

- pkg_hash_check_unresolved: cannot find dependency libmagic for keepalived
- pkg_hash_check_unresolved: cannot find dependency libnfnetlink0 for keepalived
- pkg_hash_fetch_best_installation_candidate: Packages for keepalived found, but incompatible with the architectures configured
- opkg_install_cmd: Cannot install package keepalived.

我们可以用 opkg 安装上面提示缺失的包:

1
2
opkg install libmagic
opkg install libnfnetlink0

现在再次 opkg install keepalived_2.2.7-10_x86_64.ipk 或许能成功了。

方案二:自行编译 keepalived

我们可以直接去 keepalived 官网 (https://www.keepalived.org/download.html) 下载对应版本的源码,基于里面的 Makefile 等编译,但这好像略麻烦。

更简单的是因为 OpenWRT 已经集成好了相关的编译脚本 [2],那么,你要自行编译 OpenWRT 吗?

有机会我再试一下,方法一成功后这步我就直接跳过了:)

配置主机和备机

上面安装成功后,我们直接 ps | grep keep 可以看到相关 keepalived 的进程以及它使用的配置文件:

1
2
root@OpenWrtClub:~# ps | grep keep
 7529 root      7308 S    /usr/sbin/keepalived -n -f /tmp/keepalived.conf

我知道你想编辑对应的配置了,但请等等,注意看这个文件的行首提醒了你人家是自动生成的,所以我们不能直接编辑 /tmp/keepalived.conf。你可以这么做(其实官方文档讲了,怕你没注意):

1
2
3
4
cat > /etc/config/keepalived <<-EOF
config globals 'globals'  
  option alt_config_file "/etc/keepalived/keepalived.conf"
EOF

上面比较重要的是通过 option alt_config_file 告诉脚本使用写自己的配置文件,它的路径在 /etc/keepalived/keepalived.conf。然后再配置主机和备机,配置稍有不同。

然后要确定一个关键的东西是 VIP(虚拟 IP),我的两台 Openwrt 的地址分别是:192.168.50.253,192.168.50.254。所以给 VIP 设定为一个不冲突的 192.168.50.252

主机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat > /etc/keepalived/keepalived.conf <<-EOF
global_defs {
    router_id LVS_1
}
vrrp_instance VI_1 {
    interface br-lan # 这里是lan口网卡名,通过ip a查看
    state MASTER
    virtual_router_id 1  # 主机和备机这个需要一致
    priority 100  # 主机建议设置比备机高
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.50.252/24 # 这里是虚拟ip地址
    }
}
EOF

备机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat > /etc/keepalived/keepalived.conf <<-EOF
global_defs {
    router_id LVS_1
}
vrrp_instance VI_1 {
    interface br-lan # 这里是lan口网卡名,通过ip a查看
    state BACKUP
    virtual_router_id 1  # 主机和备机这个需要一致
    priority 50  # 主机建议设置比这个更高
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.50.252/24 # 这里是虚拟ip地址
    }
}
EOF

这样配置后,分别重启主机和备机的 keepalived 进程:

1
/etc/init.d/keepalived restart

验证

接下来是见证奇迹的时刻,你肯定好奇这故障了多久能切换啊?我也是比较好奇,所以我这样抓了个包:

1
2
# 前面铺垫的知识知道了VRRP的广播地址是这个,直接tcpdump抓包:
tcpdump -i any host 224.0.0.18

然后让主机故障,你可以通过 ifconfig br-lan down 来关闭设备,也可以更暴力点直接关掉那台机器。于是会收到:

1
2
3
4
5
6
00:58:15.004593 IP 192.168.50.254 > vrrp.mcast.net: VRRPv2, Advertisement, vrid 1, prio 100, authtype simple, intvl 1s, length 20
00:58:15.004612 IP 192.168.50.254 > vrrp.mcast.net: VRRPv2, Advertisement, vrid 1, prio 100, authtype simple, intvl 1s, length 20
00:58:15.466668 IP 192.168.50.254 > vrrp.mcast.net: VRRPv2, Advertisement, vrid 1, prio 0, authtype simple, intvl 1s, length 20
00:58:15.466696 IP 192.168.50.254 > vrrp.mcast.net: VRRPv2, Advertisement, vrid 1, prio 0, authtype simple, intvl 1s, length 20
00:58:16.271598 IP 192.168.50.253 > vrrp.mcast.net: VRRPv2, Advertisement, vrid 1, prio 50, authtype simple, intvl 1s, length 20
00:58:16.271604 IP 192.168.50.253 > vrrp.mcast.net: VRRPv2, Advertisement, vrid 1, prio 50, authtype simple, intvl 1s, length 20

从时间可以看到,大概用时 1s 就切换完成了。我们也可以看一下 OpenWRT 的日志:

1
2
3
4
5
6
# 使用logread查看openwrt的日志
> logread
Thu Dec 28 00:58:16 2023 daemon.info Keepalived_vrrp[7530]: (VI_1) Entering MASTER STATE
Thu Dec 28 00:58:16 2023 daemon.info avahi-daemon[3305]: Registering new address record for 192.168.50.252 on br-lan.IPv4.
Thu Dec 28 01:00:25 2023 daemon.info Keepalived_vrrp[7530]: (VI_1) Master received advert from 192.168.50.254 with higher priority 100, ours 50
Thu Dec 28 01:00:25 2023 daemon.info Keepalived_vrrp[7530]: (VI_1) Entering BACKUP STATE

以上是备机日志,可以看到 58 分备机成为了 Master,之后主恢复后,备机又因为优先级低于主,让出了主再次成为备。

后记

搞定了一个小问题,又牵出了一些不懂的技术点,还欠下了债(内网 DNS 也需要高可用),以后有机会再完善吧。希望本文对你在使用 OpenWRT 上更进一步。

参考资料

[1]OpenWrt 官网有讨论: https://openwrt.org/docs/guide-user/network/high-availability

[2] 编译脚本: https://github.com/openwrt/packages/blob/openwrt-22.03/net/keepalived/Makefile

License:  CC BY 4.0