- 链路聚合的目的
- Linux team架构
- Linux team现有的聚合模式
- Linux team常用操作
- 用户态进程teamd启动参数解读
- 链路聚合方案大对比
- Linux team lacp超时切ab runner实现思路
- Linux team 加入OVS桥后,对端交换机免配实现思路
- Linux team 尚存问题
- 其他(Linux team部分功能实现分析)
链路聚合的目的
链路聚合及建立聚合口,其目的是为了提高网络底层容错能力,实现网络冗余部署。当然某些链路聚合方式,也可在一定程度上,实现网络流量的负载均衡。
Linux team架构
Linux team整体架构如上图所示,其采用管控面和数据面分离的方案实现,及用户态运行管控面程序teamd(由libteam封装开发而来),teamd中根据加载不同的runner,以实现不同功能的聚合模式。内核态则通过加载team.ko、 team_mode_activebackup、team_mode_loadbalance、team_mode_roundrobin模块实现数据面的team口收发包逻辑(需要注意的是,用户态的teamd程序,和内核team口是一一对应的关系,及一个用户态的teamd进程,对应一个内核team口)
除此之外,Linux team还提供了teamdctl以及teamnl两个teamdctl工具,用于配置/管理Linux team。其中teamnl主要是利用libteam库(netlink)直接与内核模块交互(只能获取一些内核模块中存在的信息)。teamdctl则是通过socket等方式与先与用户态的teamd交互,若有需要的话,teamd再间接通过libteam库(netlink)与内核模块进行通信,其能获取用户态teamd中如runner、lacp状态等大量有用信息
Linux team现有的聚合模式
Linux team可支持的聚合模式如下图所示:
可以看到,目前team支持:activebackup、broadcast、lacp、loadbalance、random、roundrobin
在loadbalance和lacp模式下,tx hash所实现的类型:
Linux team常用操作
创建一个team口
方式一:
systemctl stop NetworkManager//注意:需要停止此服务或者给teaming口配个ip,否则teamd可能会被杀,导致teaming口消失
nmcli con add type team con-name team0 ifname team0 config '{"runner": {"name":"loadbalance"}}'
nmcli connection add type team-slave con-name team-port1 ifname eno1 master team0
nmcli connection add type team-slave con-name team-port2 ifname eno2 master team0
nmcli connection up team-port1
nmcli connection up team-port2
方式二:
teamd -g -c '{"device": "team0", "ports": {"eno1": {"link_watch": {"name": "ethtool"}}, "eno2": {"link_watch": {"name": "ethtool"}}}, "runner": {"name": "loadbalance", "tx_hash": ["eth"]}}' -d//-d参数表示后台运行
teamd -g -c '{"device": "team0", "ports": {"eno1": {"link_watch": {"name": "ethtool"}}, "eno2": {"link_watch": {"name": "ethtool"}}}, "runner": {"name": "loadbalance", "tx_balancer": {"name": "basic"}, "tx_hash": ["eth"]}}'//需要加入team的成员网口,需要是down状态,在加入team口后,将会自动up起来
解除一个team口
teamd -t team0 -k
获取某个teamd的pid号
cat /var/run/teamd/team0.pid
查看teaming状态及支持配置的参数
teamdctl team0 state view
teamnl team0 ports
teamnl team0 options
修改team工作模式
teamnl team0 setoptions mode lacp//需要将成员口都清空
删除成员网口
teamdctl team0 port remove eno1
添加成员网口
teamdctl team0 port add eno1
用户态进程teamd启动参数解读
teamd包含许多启动参数,这里挑选几个重要的启动参数进行解读:
-o:带有该参数启动teamd时,会先去判断需要创建的内核team口是否存在(根据team口的名称)。若存在,则不再创建team口,直接takeover该team口
-N:teamd在收到SIGINT、SIGQUIT以及SIGTERM信号正常退出时,均会在team_fini逻辑中将内核team口destroy掉,若加了-N参数,则在正常退出时,内核team口不会被destroy(需要注意的是,由于在teamd_run_loop_run中执行了teamd_flush_ports,所以收到信号正常退出时,即使内核team口依然存在,但还是会将所有成员网口都disable掉,导致网络中断)
-r:此参数表示若内核team口存在时,是否要强行重建内核team口该参数的优先级低于-o,即若存在-o参数,该参数无效(需要注意的是,若使用-r参数,则team口上的tc、mtu等配置均会被清空或重置)
链路聚合方案大对比
对于目前常见的Linux bond、Linux team以及OVS bond三大类链路聚合方案,进行功能对比:
总的来说原生Linux team对比原生Linux bond,在聚合功能上并没有多大的优势,但team由于控制逻辑都在用户态,所以team的扩展性,相对于bond会优秀很多
Linux team lacp超时切ab runner实现思路
从上图的对比中可知原生的Linux team并不支持,OVS的lacp超时切主备功能。研究发现,在不修改内核模块的前提下,其可在teamd上通过扩展runner的方式实现,现给出实现思路:
如上图所示,增加状态机2,并在状态机2中主要实现以下内容,以满足上图所示的功能:
1.选择一个link状态up的team成员网口,并将其enable(通过team_set_port_enabled,enable一个成员网口)
2.让ovs转发到team的业务报文,能正常收发(通过调用lacp_carrier_fini,启用team)
3.改造lacp_event_watch_port_changed,若为状态机2,且1中选取的tx port down了,则需要重新选取port口,重新走一遍1、2流程
在状态机3中主要实现以下内容,以满足上图所示的功能:
1.disable掉状态机2中所enable的成员网口
lacp超时切ab,基本功能已跑通:
1.team成员口均lacp超时时,进入ab状态,选择一个成员口进行业务报文的收发
2.在ab状态下down了正在进行业务报文收发包的成员口,可自动切换到另一个成员口//1s ping未出现丢包
3.在ab状态下,后续所有成员口lacp协商成功,可变为聚合口正常收发包//lacp协商过程1s ping会丢3-5个包(同场景下OVS丢3个包)
参考代码:https://github.com/avalonLZ/Linux_teamd
Linux team 加入OVS桥后,对端交换机免配实现思路
当Linux team口加入OVS桥后,三层口便退化为了二层口,也就失去了对端sw免聚合的功能,team原生的loadbalance模式则退化为了xor静态聚合。所以想在team上弥补这一缺陷,在不修改内核模块的前提下,具体实现思路如下:
方案总结(方案思路参考ovs slb的实现):
用active/salve策略,保证只收取一份广播、组播、未知单播报文,
在整个聚合口,只收取的一份广播、组播、未知单播报文的前提下,再通过src mac过滤掉由本地发出的报文
本方案细化:
tx方向,必须是src mac hash,因为dst mac会变动,所以算出的hash值也会变动,sw的fdb表也就会乱,并且不可以用3/4层的hash,多ip绑一个mac,不同ip很可能会hash到两个口上,sw回包会有问题
1.active和slave的选择,在teamd的lb_event_watch_port_link_changed中进行选取,在各个slave口的rx路径上,选择一个bpf hook点(可以是generic xdp),下发丢弃所有组播、广播报文的bpf策略(保证所有广播、组播、未知单播报文均只从active口收取一次)
2.在team口rx路径上,选择一个bpf hook点(可以是generic xdp),提取所有报文的src mac,并判断是否已在发送src mac map中(保证接收的广播、组播报文、未知单播,均是外部发出的),若在则drop,否则pass
3.team tx bpf hook(bpf_create_code)修改为只做src hash(或者选择tc egree ebpf hook点),并且update src mac map
风险点:
1.team rx上是否有generic xdp—>存在
2.ebpf之间,如何实现src hash map的共享—>可以共享,将map的定义和不同hook的ebpf程序,写在同一个C文件中即可
3.ebpf程序维护的src mac hash map是否能定时老化—>这个可能要将老化逻辑挂到能访问系统时钟的ebpf hook点上(perf的ebpf点上应该有此功能),是否需要挂在perf的ebpf hook点上实现老化,可以再考虑
4.操作src mac hash map需要锁—>可以使用BPF_FUNC_spin_lock和BPF_FUNC_spin_unlock
5.igmp snooping开启,ovs bond在normal表项下的行为—>看运气,由ovs-bond发出的加入组播组的报文,刚好hash到active-member,在外部sw开igmp snooping才可以正常进行组播通讯
6.存在比normal更高优先级的流表项时,normal的行为—>
分三种情况
①更高优先级的流表项action不是output action,也不是修改src/dst mac、vlan等action,这样对低优先级output normal的行为无影响
②更高优先级的流表项action不是output action,但是修改src/dst mac、vlan等action,这样会间接影响优先级output normal的行为(可能导致tx hash数值改变等间接影响)
③更高优先级的流表项action是output action,则不会走到xlate_output_action中normal(xlate_normal)的逻辑里,即不会去更新改br的fdb表,对output normal的行为有直接影响(失效)
7.team用户态程序teamd升级能否不断网—>可以直接替换用户态的二进制程序,做切换模式等操作时,自动更新为最新版teamd,若不做任何操作,则会一直使用内存中的老的teamd程序
本方案思路,参考ovs slb的实现:
设置一个活动口成员网口
活动成员网口收到组播、广播报文,通过查fdb表(因为ovs都是bridge,所以直接查br的fdb表在判断对应的port就能判断是否是本地发出的了),判断是否是本地发出的,若为本地发出的且非免费arp报文(vm迁移等操作会发免费arp,需要放通),则丢弃。非本地发出的组播、广播报文,则正常接收。非活动成员口,丢弃一切组播、广播报文
ovs loadbalance-slb normal表项发包:
正常hash选口发包,无特殊处理
ovs loadbalance-slb normal表项收包:
ofproto-dpif-xlate.c
do_xlate_actions->xlate_output_action->xlate_normal→is_admissible→bond_check_admissibility
Linux team 尚存问题
Linux team在teamd出core退出后,主备、loadbalance模式下报文的转发均不会受影响,但在lacp模式下,会影响到转发,具体表现为,用带-o参数,相同team配置,重新拉起teamd,过一段时间后网络中断,目前推测应该是lacp协商方面引发的问题,针对该问题,还需要深入研究
其他(Linux team部分功能实现分析)
ab(主备模式)如何设置活动口:
是在team用户态设置的,ab_link_watch_handler和ab_state_active_port_set(cli)配置主口(活动口)
通过netlink下发到内核ab_active_port_set处理(理论上lacp失败切ab,此时的ab选路,也就可以通过用户态下发不同的hash算法控制选择发包的tx port实现,lacp_runner中的lacp_event_watch_port_changed函数稍作改造应该也具备ab_runner中ab_event_watch_port_link_changed函数的功能)
team内核模块接收netlink配置:
team_nl_ops->team_nl_cmd_options_set->team_option_set→lb_options//使用不同的聚合模式,会挂不同的option
lacp配置bpf_hash_func(内核):
lb_bpf_func_set->bpf_prog_create
bpf代码由用户态teamd_bpf_desc_compile中的bpf_create_code生成,再通过sock_fprog传入(重要)
lacp hash、tx port选择与发包:
loadbalance mode内核模块中,调用lb_transmit发包时,会调用BPF_PROG_RUN,通过用户态传入的bpf程序句柄,用bpf算出hash值,再在lb_transmit函数中,利用这个值,通过lb_hash_select_tx_port函数(num % en_port_count取余)选出tx port,从而进行发包
lacp超时:
针对单个meber,利用timerfd_create创建定时器,并由teamd_run_loop_run轮询检查,若状态读取确实为超时,则在在teamd_run_loop_do_callbacks中触发lacp_callback_timeout进行状态改变(需要增加整个team超时状态)