数据中心网络高可用技术:ECMP

在之前的文章中,已经介绍过很多次 ECMP1 了,它的原理非常简单:在路由协议中,如果下一跳有多个路径可以选择,并且多个路径的 cost metric 相等,那么路由器就会根据包的 header,计算一个 hash 值,然后根据这个 hash 值对这个 flow 选择一条固定的路径,作为下一跳。

这里的要点是:

  • 路径的 cost metric 要相等。需要注意,Administrative Distance 决定协议的优先级,而 ECMP 只适用于同一协议内部的路径选择,例如 OSPF 使用 Cost 值,BGP 使用 AS-Path 长度;
  • 如我们前面讨论过的 bonding 技术2,ECMP 选路的时候,也是基于 hash 的,这样可以对一个 flow 尽量保证包到达的顺序一致;
  • ECMP 只影响在某一跳选择下一跳,不影响全局。即,它在每一跳之间做均衡,而不是全局做均衡。

我们拿实际的问题来分析。

负载不均衡的 ECMP 部署

如果如图所示部署,一共有三台服务器,部署在两个 rack,这样会造成右边的服务器承担的流量是其他服务器的两倍,流量是不均衡的。因为第一层 ECMP 是发生在两个 Rack 之间,每一个 Rack 的交换机都是拿到 1/2 的流量。然后再进行服务器和 Rack Switch 之间的 ECMP,服务器之间均分 Rack 分得的流量。

所以 ECMP 对部署结构是有要求的,要保证每一个 Rack 的服务器数量大致相当

ECMP 环境排错要点:理解 flow hash

因为环境中有多条 path,所以在排查问题的时候,要考虑到这一点。

多条 path 之间是通过 flow 的 hash 结果来选路的。ICMP 包主要通过 Src IP, Dst IP, Type, Code, Identifier 来确定一个 flow,如果使用 mtr3,那么一次 mtr 的链路是固定的,因为 mtr 使用固定的 Identifier 来发送 ICMP,在 ECMP 链路中,总会 hash 到一条特定的路线。有的时候,网络存在问题,但是 ping 和 mtr 可能显示没有问题,原因就是恰好 ICMP 被 hash 到了好的线路上去。

如果使用 mtr --tcp --port 80,就可以看到链路中所有的线路。因为 mtr 使用 tcp 的时候,每次使用的 src port 是不固定的,这样就导致每次发出来的 TCP 包都 hash 到不同的线路上。

mtr 默认使用 ICMP,只能看到一条路线
如果使用 --tcp,会显示出来(第8跳)所有的路线

假设就是想测试和客户端一样的固定线路,可以使用 tcptraceroute4,这个工具可以指定 src port,这样 TCP 四元组就固定了。在 ECMP 环境中就会走一样的路线。

另外一点排错经验,如果是 25% 丢包,50% 丢包,通常和某条线路丢包有关。

连接池问题

我们常用的很多 SDK,比如 redis,和 db 的 SDK,都会老道的创建一个连接池来复用连接,减少 tcp 创建的 overhead。还有一个细节,就是这些 SDK 一般会给连接加上最大存活时间,如果超过之后,就会关闭这个连接并删除。

这样做是有好处的,我们自己写的代码中访问 HTTP 服务也会用连接池,但是很少会注意要重建连接。就导致一个连接建立起来,会存在数天。有一些交换机有 Sticky ECMP 的功能,假设多路网络中有一个设备下线,流量就会分散到剩余的设备中。当这个设备回来的时候, Sticky ECMP 会保证这期间创建的连接都依然走一样的路线(不会考虑重新加入的设备)。

这样就会造成连接的带宽使用不均衡。如果连接一直不关闭,就会一直不均衡。在总带宽利用远小于 100% 的时候,就会出现丢包的情况。

所以我们在写代码的时候,也要注意定时重建连接。

  1. 四层负载均衡漫谈 ↩︎
  2. 数据中心网络高可用技术之从服务器到交换机:802.3 ad ↩︎
  3. 使用 mtr 检查网络问题,以及注意事项 ↩︎
  4. https://linux.die.net/man/1/tcptraceroute ↩︎

数据中心网络高可用技术系列

  1. 数据中心网络高可用技术:序
  2. 数据中心网络高可用技术之从服务器到交换机:active-backup
  3. 数据中心网络高可用技术之从服务器到交换机:balance-tlb 和 balance-alb
  4. 数据中心网络高可用技术之从服务器到交换机:链路聚合 (balance-xor, balance-rr, broadcast)
  5. 数据中心网络高可用技术之从服务器到交换机:802.3 ad
  6. 数据中心网络高可用技术之从交换机到交换机:MLAG, 堆叠技术
  7. 数据中心网络高可用技术之从服务器到网关:VRRP
  8. 数据中心网络高可用技术:ECMP
 

iptables 拦截 bridge 包的问题排查

最近排查的一个网络问题,两个 IP 之间的网络不通,经过在 Linux 上一个一个 interface 上抓包,发现包丢在了本地的 bridge 上。

Bridge 就是一个简单的二层设备,虽然是虚拟的,但是应该逻辑也很简单,怎么会丢包呢?

经过一通乱查,发现 Bridge 的包跑到了 iptables 里面去,被 iptables 的 FORWARD chain DROP 了。

iptables dropped pakcet

说到这里跑个题,我有一个排查 iptables 是哪一条 rule 丢包的妙计,就是 watch -d "iptables -nvL | grep DROP,watch 会监控引号中的脚本,脚本会过滤出来所有会丢包的 rule,-d 参数很关键,它可以让 watch 每次对比和上一次命令的不通,然后高亮出来。一眼定位到问题。

话说回来,bridge 一个二层的设备怎么会跑到 iptables 里面去?iptables 可是 IP tables,这是三层呀。

在 Linux 中有一个机制,可以让 layer 2 的 bridge 代码调用 iptables, arptables, ip6tables 等。这样能做的事情就比 BROUTING chain (Bridge Routing1) 更多。可以在 bridge 上通过 iptables 做 dnat, stateful firewall, conntrack 等2

如果不需要 bridge 上的包跑到 iptables 上过一遍,可以通过 kernel 参数关闭:

sysctl -w net.bridge.bridge-nf-call-iptables=0

0 的意思是 bridge 的包不会去 iptables,1 就是会去 iptables,默认是 1. 也是执行完这行命令,网络果然就通了。

Bridge call iptables, 之前是在 kernel 实现的一个功能,但是显然这样会有性能问题。后来就独立出来作为一个独立的 kernel module 了 (br_netfilter3)。(如果使用 physdev 4就会自动启用这个 module)。

另外,nftablesiptables-nft 也会受到影响,layer violation 会有很多复杂的问题。新的 kernel module – nf_conntrack_bridge5 可以做到直接在 bridge layer 实现 connection track. nftables6 是下一代的 iptables。

前面说过这个功能是一个 kernel module,所以在关闭的时候有一个小小的问题。即我们关闭的时候,可能还没有这个 module load,所以会告诉我们无法设置这个参数:error: "net.bridge.bridge-nf-call-iptables" is an unknown key。如果后来创建一个 bridge,那么这个 module 会自动 load,那么包就又会跑到 iptables 里面去。

libvert 给出的解决方案7是,通过 udev 来创建一个事件,每次创建 bridge 就执行 sysctl 来 disable net.bridge.bridge-nf-call-iptables.

  1. ebtables https://linux.die.net/man/8/ebtables ↩︎
  2. ebtables/iptables interaction on a Linux-based bridge:https://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html ↩︎
  3. https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=linux-3.18.y&id=34666d467cbf1e2e3c7bb15a63eccfb582cdd71f ↩︎
  4. physdev: https://manpages.debian.org/bookworm/iptables/iptables-extensions.8.en.html#physdev ↩︎
  5. nf_conntrack_bridge: https://www.kernelconfig.io/config_nf_conntrack_bridge ↩︎
  6. nftables wiki: https://wiki.nftables.org/wiki-nftables/index.php/Main_Page ↩︎
  7. https://wiki.libvirt.org/Net.bridge.bridge-nf-call_and_sysctl.conf.html ↩︎
 

Docker 命令行小技巧:runlike

事情要从上周的一次事故说起,我们用 docker 部署的程序有一点问题,要马上回滚到上一个版本。

这个 docker 是一个比较复杂的和 BPF 有关的程序,启动时候需要设置很多 mount 和 environments,docker run 的命令特别长。所以我用 Ansible 来配置好这些变量,然后启动 docker,一个实例要花费 3~5 分钟才能启动。

同事突然说,某实例他手动启动了,当时我就震惊了,怎么手速这么快?!

请教了一下,原来是用的 runlike 工具,项目地址是:https://github.com/lavie/runlike

这个工具的原理是,docker inspect <container-name> | runlike --stdin ,就会生成这个容器的 docker run 命令。这个思路简直太棒了。就和 Chrome 的 copy as cURL 功能一样好用!

 

ARP Flux 问题和解决方法

我们写程序不需要写 ARP request 和 response 相关的逻辑,因为这部分是操作系统帮我们做的。

Linux 处理 ARP 请求的逻辑是:

如果操作系统收到了一个 ARP reuqest,并且本机中某一个接口配置了 ARP request 中请求的 IP,那就回复这个 ARP request,回复的 ARP response 中,使用收到此 ARP request 的 interface 的 MAC 地址作为答案。

这个逻辑看起来没有问题:从 Linux 的视角,既然我能从一个 interface 收到 ARP 请求,那么对方向这个 interface 发送数据包,我也可以从这个 interface 收到,所以,回复 interface 的 MAC 地址即可。

但是,在下面这种情况就会出问题:

  1. Linux 有两个 interface 接入到了同一个交换机上;
  2. 这两个 interface 配置的 IP 地址在同一个广播域;

满足这两个条件的话,当有 LAN 中有主机发送 ARP request 广播包,交换机会转发到自己的所有端口,Linux 会从这两个端口都收到此 ARP request 广播包。按照以上处理逻辑,Linux 会回复 2 个 ARP reply,因为收到了两个 request,两个 interface 各回复一次,并且这两个 ARP reply 分别是两个 interface 的 MAC 地址。

实验环境如下图所示:

ubuntu-1 有两条线接到同一个交换机

配置好 IP 之后,我们在 ubuntu-2 主机发送 ARP request。

一共发送了 3 个 request,却收到了 6 个 ARP reply。并且有两种 MAC 地址。

这个就是 ARP Flux 问题。

解决方法0: 配置不同的 IP subnet

因为 ARP 是在 LAN 内的,如果两个 interface 分别配置在不同的 subnet,就不会有这个问题了。

但是有时候我们的软件守交换机网段的限制,只能配置在一个网段。并且有些场景需要两个线路和两个 IP,比如管理和数据面分开(带外管理),就又会遇到这个问题。

解决方法1: 隐藏其中一个接口

使用 ip link set dev eth0 arp off 可以禁用 interface 的 ARP 恢复,这样,在 LAN 上就相当于把这个 interface 隐藏了,因为没有人可以发现它的 MAC 地址了。在四层负载均衡中,如果使用 DSR 的模式并且不加隧道的话,就需要对 RS 的 VIP 禁用 ARP 回复1

这里有一个有意思的现象:假设 192.168.1.2 配置在 eth0 上,但是我们把 eth0 的 ARP 禁用了。

192.168.1.2 所在的 eth0 ARP禁用

这时候如果请求 192.168.1.2 的地址,会拿到 eth1 的 MAC 地址。参考上文讨论过的 Linux ARP 工作原理。

这个方法只能通过 interface 级别来设置,如果一个 interface 上有多个 IP 地址,那么只能全部禁用或者开启。

如果要 by IP 来禁用 ARP,可以使用 arptables(8)2 3.

解决方法 2: arp_ignore

直接禁用 ARP 差不多等于把这个 interface 关闭了,大部分情况下不是我们想要的结果。

我们希望的效果是对于 ARP request 只回复一次,并且只有 IP 在自己的 interface 上才回复,不要代替其他 interface 回复。

通过 arp_ignore4 可以改变 Linux 的 ARP 行为:

  • 0 – 默认,只要本地有的 IP 就会回复,无论是哪一个 interface;
  • 1 – 只有当 ARP request 询问的 IP 配置在收到 ARP 的接口上时,才会回复;
  • 2 – 同 1,并且 sender 的 IP 在本地 IP 的相同 subnet 内,才会回复;
  • 3scope host 不会回复,只有 scope linkscope global 才会回复 (可以通过 ip address 命令查看配置的 IP scope);
  • 47 – 保留字段;
  • 8 – 所有 local address 都不回复;

配置方法:通过 sysctl -w 可以修改配置。arp_ignore 是 per interface 的配置。

其中 all 是一个全局变量,最终在 interface 上生效的值是 max(all, eth0)。以最大的为准。

default 是模板变量,在新建一个 interface 的时候,会自动使用这个值。

我们可以给所有的 interface 都设置为 1.

然后再发送 arp 请求,就会发现回复之后一个了,并且是正确的那个。

解决方法3: arp_filter

另一个方法是使用 arp_filter 参数,这个参数是一个 bool,默认是 0 ,如果开启为 1,就意味着,如果收到 ARP request,ARP 请求的 IP 为 A,收到 ARP 的 interface 为 X 和 Y,那么 kernel 就测试路由,假设要发出去 source IP 为 A 的包,从 X 发出去还是从 Y 发出去。如果从 X 发出去,那么只有 X 会回复。(source based rotuing).

举例来说,我们现在有两个接口和两个 IP,回复 192.168.1.10 的 ARP 时,可以测试当前的路由情况:

可以看到无论 source IP 是哪一个,都会从 eth0 口出。

这是因为我们的 ip route 配置:

会永远走第一条路由。

所以我们如果发送 ARP request,对 .2.3 两个地址,得到的结果会是一样的,因为都是 eth0 在回复。

如果我们把第一条路由删了,那么现在就都走 eth1 了,这时候 ARP 结果就都是 eth1 了。

Lookback 接口是否会回复 ARP?

答案是是的(虽然听起来很没道理)。这个主要是跟路由有关。

当我们在给 lo 绑定一个地址的时候,kernel 会默认添加 2 条路由,lo 所在网段和 lo 的地址会被标记成 dev lo 的本地路由。

其实,当我们给 lo 地址添加 192.168.1.2/24 的时候,由于自动添加的 192.168.1.0/24 的 route (第5行),导致我们其他的接口也不会回复这个网段的 ARP 请求了。这个时候从 ubuntu-2 上 arping 192.168.1.10192.168.1.11 都会 timeout。

这个是默认的行为。但是如果我们改一下,删除这两条路由,添加另一条从 eth0 口出的路由的话,就会发现,arping lo 接口的 IP,也会收到 ARP response 了。

在 ubuntu-2 可以 ping 通。

所以说 lo 接口是否回复 ARP request,主要和 ip route 的设置有关。

  1. 四层负载均衡漫谈 ↩︎
  2. arptables(8) – Linux man page ↩︎
  3. Using arptables to disable ARP ↩︎
  4. Kernel 的 ip sysctl 文档 ↩︎
 

特殊的 ARP 用法:Gratuitous ARP, ARP Probe 和 ARP Announce

在 Ethernet 环境中,所有的数据最终都要以二层 Ethernet Frame 的形式发送,要添加 Src MAC, Dst MAC, 以及其他的 header,比如 CRC 等,Ethernet 就会把数据送达到目的地。

我们在编程的时候经常指定 IP 和 Port,但是几乎从没有指定过 MAC 地址,那么 Frame 是怎么发出去的呢?这是操作系统帮我做了这件事。如果系统不知道一个 IP 对应的 MAC 地址是什么,系统会发送一个广播的 ARP 请求,(由于这个请求也是要通过 Ethernet 发送出去的,所以也要填充 Dst MAC,Dst MAC 就是 ff:ff:ff:ff:ff:ff。)询问谁有目的 IP,如果有,请回复给 MAC-B。广播域中所有的 Host 都会收到这个请求,只有拥有这个 IP 的 Host 才会回复给 MAC-B 自己的 MAC 地址,这样,B 就知道了这个 IP 对应的 MAC 地址。

这就是普通的 ARP request 和 response 的过程。

其中,里面的几个值得注意的 field 如下:

对于 ARP request 来说:

  • Operation: 1 (1表示 request, 2 表示 response);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:全1,即广播给所有主机;
  • Dst IP:是要询问的 IP;

对于 ARP Response 来说:

  • Operation: 2
  • Src MAC: sender(sender 在这里是响应者) 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:receiver (在这里是 ARP 问题的发出者)的 MAC;
  • Dst IP:receiver IP;

ARP 是一个很简单的协议,但是人们基于交换机和主机的工作原理,设计了其他的用法。这些用法其实并不复杂,最重要的就是理解这些设备最基本的工作原理,这样就可以理解这些特殊的 ARP 设计意图了。

Gratuitous ARP

在之前的博客中介绍过两次使用 Gratuitous ARP 的例子。Gratuitous ARP 的目的是更新其他设备的 ARP cache,或者交换机 mac-address table。

在 VRRP1 中,目的主要是切换网关,更新的对象是 mac-address table。交换机的 mac-address table 主要是通过收到的包的 Src MAC 来学习 MAC 对应的物理 port 的,所以最重要的是 Src MAC,其他的都不重要。

所以在 VRRP 中的 Gratuitous ARP 是这样的:

  • Operation: 1 (request);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:全1,即广播给所有主机;
  • Dst IP:全1;

在 Linux 的 balance-alb bonding 模式2中,ARP 的目的是为了刷新其他主机的 ARP Cache,并且希望不同的主机拿到不一样的 MAC 地址,所以使用的 ARP 是 response,并且是 unicast。

  • Operation: 2 (response);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:bond driver 记录的 MAC;
  • Dst IP:bond driver 记录的 IP;

取决于使用场景,Gratuitous ARP 也是 broadcast 的 reply,将 response 信息广播给所有的主机3

ARP Probe

ARP Probe 的作用是,在分配到一个 IP 之后,但是在 IP 使用之前,可以先用 ARP 协议来检查一下当前的 LAN 内有没有人在使用这个 IP。

检查的方式就是用 ARP 询问这个 IP 的 MAC 地址,如果有主机正在使用,那么就会收到 ARP reply。

但是这里有一个问题。一个主机如果收到了 ARP request,询问谁有 IP X?请发送回复给 IP 地址 Y at MAC 地址 Z。即使这个主机没有 IP X,也不会单纯丢弃这个 ARP request,而是会从这个 ARP request 中学习到,IP Y 对应 MAC Z,会将它放到自己的 ARP cache 中。

这里我们只是想检查一个 IP 是否正在被使用,我们还没有真正地开始使用这个 IP。如果用普通的 ARP 请求来询问,假设这个 IP 已经被使用,那么发出去的 ARP request 就会传达错误的信息,其他主机就会根据这个 ARP request 来更新自己的 ARP cache。

为了解决这个问题,ARP Probe 使用的 request 将 Src IP 设置为 0.0.0.0,这样,这个 ARP request 就完全无害了。

ARP 请求如下(加粗部分是和普通的 ARP request 不一样的部分)。

  • Operation: 1 (request);
  • Src MAC: sender 的 MAC;
  • Src IP: 0.0.0.0
  • Dst MAC:全1,即广播给所有主机;
  • 询问的 IP:自己将要使用的 IP;

ARP Announce

ARP Announce 用于在 ARP Probe 确定没有问题之后,决定使用这个 IP,但是再一次确定唯一性。

ARP Announce 发送一个普通的 ARP request, 在 LAN 内询问自己将要使用的 IP,正常情况下不会收到任何回复,因为这个 IP 是自己的。异常情况下收到回复,那么说明 LAN 已经有人在使用这个 IP 了,需要终止继续使用这个 IP。

ARP Announce 是普通的 ARP 请求,和 Gratuitous ARP 很像。询问的目标 IP 是自己的 IP。

ARP Announce 又叫做 Unsolictied ARP。

ARP Announce 和 ARP Probe 的唯一区别就是 Src IP 不同,ARP Announce 使用自己的 IP 来发送,所以也会更新 LAN 内其他设备的 ARP cache。

  • Operation: 1 (request);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:全1,即广播给所有主机;
  • Dst IP:自己即将使用的 IP;

参考资料:

  1. 首跳冗余协议 VRRP ↩︎
  2. 数据中心网络高可用技术之从服务器到交换机:balance-tlb 和 balance-alb ↩︎
  3. Gratuitous ARP https://www.practicalnetworking.net/series/arp/gratuitous-arp/ ↩︎