前言
Weave 作為 Docker 跨主機集群網絡解決方案的一種,可以用于連接部署在多臺主機上的 Docker 容器,使用網絡的應用程序不必去配置端口映射等信息,Weave的通信支持加密,所以用戶可以從一個不受信任的網絡連接到主機。Weave 在控制層面和 Calico 類似,在數據層面通過UDP封裝實現 L2 overlay 。 Weave 在 1.2 版本之前都是使用 usersapce 實現,在 weave-1.2版本后, weave 結合了內核的 openvswitch 模塊,實現了 open vSwitch datapath(odp)功能,結合 kernel 的 vxlan 特性,在網絡性能上有較大的提升。 由于 odp 功能和內核相關模塊結合較為緊密,在實際使用中可能遇到一些和內核相關的坑,本文描述的兩個問題都和內核有關系。
坑一:weave fastdp造成虛擬機網絡中斷
問題描述
在 weave 1.2 版本之后,考慮到原先 sleeve 模式的網絡性能較差,增加了 fastdp 的模式,該模式成為了 weave 啟動時的默認模式,在 fastdp 模式中使用了 kernel 中的 openvswitch 模塊,做報文封裝時使用了 vxlan 協議。在使用 qemu-kvm 創建的云主機上, 如果安裝的是 centos7.0 ,內核版本為 kernel-3.10.123 ,在啟動 weave 并使用 fastdp 模式時,會造成 virtio_net 虛擬網卡無法發送數據,進而導致整個虛擬機的網絡中斷的問題。
問題分析
造成網絡斷開的原因是因為觸發了內核的一個 bug ,該內核 bug 的 commit 鏈接地址:https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/commit/?id=8a0cafc9a8131cc545dc9924aed38f7176ee4ad7。
觸發該 bug 主要是由于 Weave 在初始化時會發送一個 60000 字節的 UDP 數據包進行 PMTU 的探測,并且 Weave 發送使用的套接字為 raw socket ,導致了 virtio_net 使用的內存被污染,表現的現象就是無法通知到宿主機上 vhost 取數據,在接口上看到發送報文的計數始終不會增加。
該問題不是只有 weave 才能觸發,使用普通的應用程序只要在建立 socket 時,使用是 raw socket ,并且在發送數據時,發送的數據大于接口的 MTU 值,接口的 UFO 功能是打開的,都極有可能觸發該問題,造成網絡的中斷。
圖1:fastdp 模式的數據流原理
解決方案
有兩種方式可以解決該問題:
1)升級內核,保證內核版本大于等于 3.13 ,
2)關閉虛擬機網卡的 ufo 特性,centos7.1 的 kernel-3.10.229 內核已經修復了該問題。
圖2:guest 通知 vhost 讀取數據流程
坑二:Weave無法使用fastdp模式
問題描述
在內核版本 CentOS Linux (3.10.0-327.10.1.el7.x86_64) 7 (Core) 上 ,weave 版本大約 1.2 ,如果云主機的 MTU 值為 1450 或者小于 1474 , weave 啟動時無法正常的選擇 fast data path模式。在 weave 啟動后一直選擇 sleeve 模式,本應該默認模式為 fastdp ,該問題也和內核的版本相關。
問題分析
Weave的fast data path路徑使用到了 odp 技術,也就是內核中的OVS模塊,在 Container 中直接發送數據包到 ovs 模塊。在啟動 Weave 時,會自動選擇使用 sleeve 模式還是 fastdp 模式,這里通過發送心跳包來決定的,出現該問題時,在云主機通過 docker logs weave 的日志可以看到這樣出錯的信息:fastdp timed out waiting for vxlan heartbeat。
這個 heartbeat 的數據包,是一個 UDP 包,目的端口號為 6784 ,在某些云主機上接口的MTU值為 1454 ,但是在發送 UDP 的 heartbeat 數據包時,發送的是 1474 字節,這樣就會對報文在IP層進行分片,但在主機上發現心跳報文發送不出,當 MTU 的值修改為 1500 后,就可以發送出去,在 MTU 為 1454 的情況下,會出現下面的 ICMP 的錯誤報文。
圖3: 出現的錯誤icmp報文
出現上面錯誤的 icmp 報文,是內核中的 ip_fragment 函數調用 icmp_send 函數發送的,
if (unlikely(((iph->frag_off & htons(IP_DF)) && !skb->ignore_df) ||
(IPCB(skb)->frag_max_size &&
IPCB(skb)->frag_max_size > mtu))) {
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(mtu));
kfree_skb(skb);
return -EMSGSIZE;
}
通過上面的代碼可以看出,如果出現上面出錯的 ICMP 報文,下面的判斷條件 iph->frag_off & htons(IP_DF)) && !skb->ignore_df 需要成立,通過對抓取的報文分析可知 iph->frag_off & htons(IP_DF)) 的值為真,那么 skb->ignore_df 值需要為 0 ,這里的關鍵在于 skb->ignore_df 的值是何時賦值為0的。
通過分析 weave 發送心跳包的流程可知,在 vxlan_tnl_send 函數中,對 skb->ignore_df 賦值為 1 ; 在最后調用 tunnel 的發送函數 iptunnel_xmit 時,調用了 skb_scrub_packet 函數,在該函數中又重新對 skb->ignore_df 賦值為0.( kernel 版本為:3.10.0-327.el7 ),造成后續發送報文時,發送了 ICMP 目的不可達,并且錯誤碼為 ICMP_FRAG_NEEDED 的報文。
void skb_scrub_packet(struct sk_buff *skb, bool xnet)
{
skb->tstamp.tv64 = 0;
skb->pkt_type = PACKET_HOST;
skb->skb_iif = 0;
skb->ignore_df = 0;
skb_dst_drop(skb);
secpath_reset(skb);
nf_reset(skb);
nf_reset_trace(skb);
if (!xnet)
return;
skb_orphan(skb);
skb->mark = 0;
}
上面代碼是 centos 7 的 3.10.0 - 327.el7 ,而在老一些的內核版本 3.10.0 - 123.el7 上, iptunnel_xmit 調用的是 secpath_reset(skb) 函數,在該函數中并沒有對 skb->local_df (低版本的內核使用的是 local_df )進行重新的初始化,也就是 skb->local_df 值仍舊為1,因此在該版本上不會出現該問題。
static inline void
secpath_reset(struct sk_buff *skb)
{
#ifdef CONFIG_XFRM
secpath_put(skb->sp);
skb->sp = NULL;
#endif
}
圖4:內核版本不同造成設置不同
在新的內核版本中存在該問題,但是內核本身是沒問題的,還是 weave 用戶態的管理 datapath 的程序和內核的適配上有問題(它并不是使用 ovs-switchd ),在 ovs 中的對 tunnel 類型可以設置為 df_default = false 進行分片。
解決方案
保證接口 MTU 值為默認的1500.
總結
Weave 的 odp 功能使用了內核的特性,在使用 weave 的 fastdp 功能時遇到上面兩個問題,都是和內核有著緊密聯系,通過對內核層面的分析,可以定位到問題的根因。在后續遇到類似問題時,可以多從內核的角度進行考慮。
本文由『UCloud內核團隊』原創,作者:曾福振。
未經允許不得私自轉載,比心~