文章

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直接安装:

opkg update
opkg install keepalived

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

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

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

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

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

# 下载包,你可能要选择合适自己的版本
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  

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

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安装上面提示缺失的包:

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的进程以及它使用的配置文件:

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

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

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

主机:

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

备机:

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进程:

/etc/init.d/keepalived restart

验证

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

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

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

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的日志:

# 使用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