這次分享介紹Flannel中的vxlan backend,包含兩方面內容:
?????? 深入理解內核中的VXLAN原理:使用iproute2和bridge等原生工具來搭建一個基于VXLAN的Overlay網絡。
?????? 理解Flannel使用vxlan backend時的工作原理: 有了前面對內核VXLAN原理的理解,通過分析Flannel部分源碼來從根本上掌握其vxlan backend的原理。
一、VXLAN的原理
? ? ? ? Virtual eXtensible Local Area Network(VXLAN)是一個在已有的3層物理網絡上構建2層邏輯網絡的協議。
?????? 在2012年底的v3.7.0之后,Linux Kernel加入了VXLAN協議支持,作者:Stephen Hemminger,所以如果要使用Linux Kernel中的VXLAN支持,最低內核版本3.7+(推薦3.9+)。
?????? Stephen Hemminger同時也實現了iproute2、bridge等工具,用以管理Linux中復雜的網絡配置,目前在絕大多數Linux發行版中都是默認支持的。
?????? VXLAN本質上是一種tunnel(隧道)協議,用來基于3層網絡實現虛擬的2層網絡。泛泛地說,tunnel協議有點像今天電話會議,通過可視電話連接不同的會議室讓每個人能夠直接交談,就好像坐在一個會議室里一樣。很多tunnel協議,如GRE也有類似VXLAN中VNI的用法。
????? tunnel協議的另外一個重要的特性就是軟件擴展性,是軟件定義網絡(Software-defined Network,SDN)的基石之一。
????? Flannel中有兩個基于tunnel協議的backend:UDP(默認實現)和VXLAN,本質上都是tunnel協議,區別僅僅在于協議本身和實現方式。
?????? 這里順便提一句:tunnel協議在比較老的內核中已經有支持,我印象中v2.2+就可以使用tunnel來創建虛擬網絡了,因此UDP backend適合在沒有vxlan支持的linux版本中使用,但性能會相比vxlan backend差一些。
?以上是一些背景介紹,下面開始介紹VXLAN的內核支持
圖1. VXLAN可以在分布多個網段的主機間構建2層虛擬網絡
圖2. VXLAN基本原理:套路還是tunnel那一套,區別僅僅在于tunnel協議本身的實現
?????? 為了說明圖1和圖2中談到的VXLAN原理,這里在兩臺不同網段的VPS上手動搭建一個Overlay Network,并在兩個節點上分別運行了Docker Container,當我們看到容器之間使用虛擬網絡的IP完成直接通信時,實驗就成功了。
圖3 手動搭建vxlan虛擬網絡的網絡拓撲
圖3中提到一個VTEP的概念,全稱VXLAN Tunnel Endpoint,本質上就是前面提到的tunnel中的endpoint
現在正式開始手動搭建圖 3中的虛擬網絡
第一步、創建docker bridge
?????? 默認的docker bridge地址范圍是172.17.0.1/24(比較老的版本是172.17.42.1/24), 而本實驗中兩個節點node1和node2的子網要求分別為: 192.1.78.1/24,192.1.87.1/24 。
????? 修改docker daemon啟動參數,增加以下參數后重啟docker daemon:
node1: --bip=192.1.78.1/24
node2: --bip=192.1.87.1/24
?????? 這時node1和node2的容器之間還不能直接通信, node1也不能跨主機和node2上的容器直接通信,反之node2也無法直接和node1上的容器通信.。
第二步、創建VTEPs
在node1上執行以下命令:
PREFIX=vxlan
IP=$external-ip-of-node-1
DESTIP=$external-ip-of-node-2
PORT=8579
VNI=1
SUBNETID=78
SUBNET=192.$VNI.0.0/16
VXSUBNET=192.$VNI.$SUBNETID.0/32
DEVNAME=$PREFIX.$VNI
ip link delete $DEVNAME
ip link add $DEVNAME type vxlan id $VNI dev eth0 local $IP dstport $PORT nolearning
echo '3' > /proc/sys/net/ipv4/neigh/$DEVNAME/app_solicit
ip address add $VXSUBNET dev $DEVNAME
ip link set $DEVNAME up
ip route delete $SUBNET dev $DEVNAME scope global
ip route add $SUBNET dev $DEVNAME scope global
node2上執行以下命令:
PREFIX=vxlan
IP=$external-ip-of-node-2
DESTIP=$external-ip-of-node-1
VNI=1
SUBNETID=87
PORT=8579
SUBNET=192.$VNI.0.0/16
VXSUBNET=192.$VNI.$SUBNETID.0/32
DEVNAME=$PREFIX.$VNI
ip link delete $DEVNAME
ip link add $DEVNAME type vxlan id $VNI dev eth0 local $IP dstport $PORT nolearning
echo '3' > /proc/sys/net/ipv4/neigh/$DEVNAME/app_solicit
ip -d link show
ip addr add $VXSUBNET dev $DEVNAME
ip link set $DEVNAME up
ip route delete $SUBNET dev $DEVNAME scope global
ip route add $SUBNET dev $DEVNAME scope global
第三步、為VTEP配置forward table
# node1
$ bridge fdb add $mac-of-vtep-on-node-2 dev $DEVNAME dst $DESTIP
#?node2
$ bridge fdb add $mac-of-vtep-on-node-1 dev $DEVNAME dst $DESTIP
第四步、配置Neighbors,IPv4中為ARP Table
# node1
$ ip neighbor add $ip-on-node-2 lladdr $mac-of-vtep-on-node-2 dev vxlan.1
# node2
$ ip neighbor add $ip-on-node-1 lladdr $mac-of-vtep-on-node-1 dev vxlan.1
????? 注意:ARP表一般不會手動更新,在VXLAN的實現中由對應的Network Agent監聽L3 MISS來 動態更新;這里手動添加ARP entry僅僅是為了測試;另外,如果跨主機訪問多個IP, 每個跨主機的IP就都需要配置對應的ARP entry。
????? 以上操作都需要root權限,完成后整個Overlay Network就搭建成功了,下面通過測試兩種連通性來總結本實驗:
node1容器與node2上的容器直接通信(容器與跨主機容器間直接通信)
node1與node2上容器直接通信;node2與node1上容器直接通信(主機和跨主機容器之間通信)
????? 先看容器與跨主機容器間直接通信的測試。
現在node1和node2上分別起一個busybox:
node1$ docker run -it --rm busybox sh
node1$ ip a
1: lo: mtu 65536 qdisc noqueue qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
? valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
? valid_lft forever preferred_lft forever
6: eth0@if7: mtu 1500 qdisc noqueue
link/ether 02:42:c0:01:4e:02 brd ff:ff:ff:ff:ff:ff
inet 192.1.78.2/24 scope global eth0
? valid_lft forever preferred_lft forever
inet6 fe80::42:c0ff:fe01:4e02/64 scope link
? valid_lft forever preferred_lft forever
node2$ docker run -it --rm busybox sh
node2$ ip a
1: lo: mtu 65536 qdisc noqueue qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
? valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
? valid_lft forever preferred_lft forever
10: eth0@if11: mtu 1500 qdisc noqueue
link/ether 02:42:c0:01:57:02 brd ff:ff:ff:ff:ff:ff
inet 192.1.87.2/24 scope global eth0
? valid_lft forever preferred_lft forever
inet6 fe80::42:c0ff:fe01:5702/64 scope link
? valid_lft forever preferred_lft forever
接下來讓我們來享受一下容器之間的連通性。
node1@busybox$ ping -c1 192.1.87.2
PING 192.1.87.2 (192.1.87.2): 56 data bytes
64 bytes from 192.1.87.2: seq=0 ttl=62 time=2.002 ms
node2@busybox$ ping -c1 192.1.78.2
PING 192.1.78.2 (192.1.78.2): 56 data bytes
64 bytes from 192.1.78.2: seq=0 ttl=62 time=1.360 ms
然后看主機和跨主機容器之間連通性的測試。
node1$ ping -c1 192.1.87.2
PING 192.1.87.2 (192.1.87.2) 56(84) bytes of data.
64 bytes from 192.1.87.2: icmp_seq=1 ttl=63 time=1.49 ms
node2$ ping -c1 192.1.78.2
PING 192.1.78.2 (192.1.78.2) 56(84) bytes of data.
64 bytes from 192.1.78.2: icmp_seq=1 ttl=63 time=1.34 ms
具體實驗的截屏可以訪問:https://asciinema.org/a/bavkebqxc4wjgb2zv0t97es9y 。
二、Flannel中vxlan backend實現
?弄清楚了kernel中vxlan的原理后,就不難理解Flannel的機制了。
注意:
使用vxlan backend時,數據是由Kernel轉發的,Flannel不轉發數據,僅僅動態設置ARP entry
而udp backend會承擔數據轉發工具(這里不展開介紹其實現),UDP backend自帶了一個C實現的proxy,連接不同節點上的tunnel endpoints 這里討論的源碼基于最新穩定版v0.7.0。
vxlan backend啟動時會動態啟動兩個并發任務:
監聽Kernel中L3 MISS并反序列化成Golang對象
根據L3 MISS和子網配置(etcd)來自動更新本地neighbor配置
關于源碼,請看:http://dwz.cn/5MXKuC
最后,Flannel的實現中有一個小細節,在0.7.0中剛剛加入,即VTEP的IP加上了/32位的掩碼避免了廣播,此前的版本都是/16掩碼,解決了VXLAN網絡中由于廣播導致的“網絡風暴”的問題。
三、總結一下
Flannel中有多種backend,其中vxlan backend通過內核轉發數據,而udp backend通過用戶態進程中的proxy轉發數據
Flannel在使用vxlan backend的時候,短暫啟停flanneld不會造成網絡中斷,而udp backend會
很多第三方的網絡測試表明,udp backend比vxlan backend網絡的性能差大概1個數量級,一般來說只要內核支持(v3.9+),建議選擇vxlan backend
Flannel中使用vxlan backend時,建議升級到0.7+,因為此前的版本都存在潛在的網絡風暴問題