flannel通信方式
目前比較成熟的flannel網(wǎng)絡(luò)通信方式有UDP、VXLAN以及host-gw三種方式。
flannel的UDP通信方式
flannel作為一種overlay網(wǎng)絡(luò),而overlay的意思就是數(shù)據(jù)報(bào)文裝在另一種網(wǎng)絡(luò)包里,然后進(jìn)行路由轉(zhuǎn)發(fā)和通信。對(duì)于UDP通信方式來說,就是報(bào)文在進(jìn)入實(shí)際物理網(wǎng)絡(luò)之前,經(jīng)過flannel,進(jìn)行一層UDP封裝,將報(bào)文作為payload發(fā)送給對(duì)端;對(duì)端收到UDP報(bào)文之后,flannel負(fù)責(zé)解包,得到真正的用戶報(bào)文后,再轉(zhuǎn)到真正的接收方。
總的來說,flannel跨主機(jī)通信的一系列的流程都可以用下面的一張圖說明,

我們以發(fā)送icmp報(bào)文為例:
1、容器A發(fā)出ICMP請(qǐng)求報(bào)文:10.3.3.2 -> 10.3.83.2,根據(jù)容器A內(nèi)的路由表,報(bào)文將發(fā)送到網(wǎng)關(guān)10.3.3.1,即docker0設(shè)備。
[root@container-a /]# route -n
Kernel IP routing table
Destination? ? Gateway? ? ? ? Genmask? ? ? ? Flags Metric Ref? ? Use Iface
0.0.0.0? ? ? ? 10.3.3.1? ? ? ? 0.0.0.0? ? ? ? UG? ? 0? ? ? 0? ? ? ? 0 eth0
10.3.3.0? ? ? ? 0.0.0.0? ? ? ? 255.255.255.0? U? ? 0? ? ? 0? ? ? ? 0 eth0
2、此時(shí)docker0再根據(jù)主機(jī)A上的路由表,報(bào)文將送到flannel0設(shè)備(10.3.3.0)。
[root@HOST-A ~]# route -n
Kernel IP routing table
Destination? ? Gateway? ? ? ? Genmask? ? ? ? Flags Metric Ref? ? Use Iface
0.0.0.0? ? ? ? 192.168.52.2? ? 0.0.0.0? ? ? ? UG? ? 100? ? 0? ? ? ? 0 ens33
10.3.0.0? ? ? ? 0.0.0.0? ? ? ? 255.255.0.0? ? U? ? 0? ? ? 0? ? ? ? 0 flannel0
10.3.3.0? ? ? ? 0.0.0.0? ? ? ? 255.255.255.0? U? ? 0? ? ? 0? ? ? ? 0 docker0
192.168.52.0? ? 0.0.0.0? ? ? ? 255.255.255.0? U? ? 100? ? 0? ? ? ? 0 ens33
3、flannel0是一個(gè)tun設(shè)備,工作在三層,因此flannel0接收到的是一個(gè)RAW IP包(無MAC信息)。隨后,flanneld進(jìn)程將接收?qǐng)?bào)文,然后通過查詢etcd數(shù)據(jù)庫,根據(jù)目的容器IP,確定目的主機(jī)IP,最后進(jìn)行UDP的封包。加上8個(gè)字節(jié)的UDP頭,再加上20個(gè)字節(jié)的IP頭:192.168.52.129:8285 -> 192.168.52.145:8285,發(fā)送給對(duì)端目標(biāo)主機(jī)的flanneld進(jìn)程。正是因?yàn)榉獍腢DP頭和IP頭,flannel網(wǎng)絡(luò)設(shè)備的MTU為1472(1500-28),避免因?yàn)閿?shù)據(jù)報(bào)文超過ens33的MTU而丟包。
flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>? mtu 1472
4、flanneld進(jìn)程將打包好的UDP報(bào)文根據(jù)主機(jī)路由表發(fā)往ens33網(wǎng)卡。
5、主機(jī)A通過ens33網(wǎng)卡將UDP報(bào)文通過網(wǎng)絡(luò)傳輸?shù)街鳈C(jī)B的ens33網(wǎng)卡。
6、主機(jī)B查詢報(bào)文目的端口為8285(flanneld監(jiān)聽端口),因此將報(bào)文遞交給flanneld進(jìn)程。
[root@HOST-B /]# netstat -anup | grep flanneld
udp? ? ? ? 0? ? ? 0 192.168.52.145:8285? ? 0.0.0.0:*? ? ? ? ? ? 2778/flanneld
7、flanneld進(jìn)程將報(bào)文解包,得到真正的ICMP請(qǐng)求報(bào)文:10.3.3.2 -> 10.3.83.2。
8、flannel0設(shè)備根據(jù)目標(biāo)ip 10.3.83.2,匹配主機(jī)B的路由表,將解包后的報(bào)文遞交給docker0。
[root@HOST-B /]# route -n
Kernel IP routing table
Destination? ? Gateway? ? ? ? Genmask? ? ? ? Flags Metric Ref? ? Use Iface
0.0.0.0? ? ? ? 192.168.52.2? ? 0.0.0.0? ? ? ? UG? ? 0? ? ? 0? ? ? ? 0 ens33
10.3.0.0? ? ? ? 0.0.0.0? ? ? ? 255.255.0.0? ? U? ? 0? ? ? 0? ? ? ? 0 flannel0
10.3.83.0? ? ? 0.0.0.0? ? ? ? 255.255.255.0? U? ? 0? ? ? 0? ? ? ? 0 docker0
192.168.52.0? ? 0.0.0.0? ? ? ? 255.255.255.0? U? ? 0? ? ? 0? ? ? ? 0 ens33
9、docker0最終將報(bào)文提交給容器B,10.3.83.2,處理完ICMP請(qǐng)求報(bào)文后,原路徑發(fā)送ICMP應(yīng)答報(bào)文。
抓包分析
根據(jù)上面的分析,我們可以通過tcpdump在各個(gè)網(wǎng)絡(luò)接口上抓包,驗(yàn)證我們的分析。
我們同樣以ICMP報(bào)文為例,
1、容器A -> veth
我們?cè)谥鳈C(jī)A的vethb5898d1 抓取icmp報(bào)文,
[root@HOST-A ~]# tcpdump -i vethb5898d1 -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vethb5898d1, link-type EN10MB (Ethernet), capture size 262144 bytes
22:34:07.067929 IP 10.3.3.2 > 10.3.83.2: ICMP echo request, id 32, seq 1, length 64
22:34:07.069460 IP 10.3.83.2 > 10.3.3.2: ICMP echo reply, id 32, seq 1, length 64
可以看到, IP 10.3.3.2 > 10.3.83.2發(fā)起了icmp請(qǐng)求報(bào)文,然后收到對(duì)端的應(yīng)答。
2、veth -> docker0
我們?cè)谥鳈C(jī)A的docker0設(shè)備上抓取報(bào)文,
[root@HOST-A ~]# tcpdump -i docker0 -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
22:37:36.997615 IP 10.3.3.2 > 10.3.83.2: ICMP echo request, id 33, seq 1, length 64
22:37:37.000023 IP 10.3.83.2 > 10.3.3.2: ICMP echo reply, id 33, seq 1, length 64
和在veth上抓取的報(bào)文是一樣的,因?yàn)閐ocker0只是轉(zhuǎn)發(fā)報(bào)文,并沒有做封裝操作。
3、docker0 -> flannel0
在flannel0設(shè)備上抓包,
[root@HOST-A ~]# tcpdump -i flannel0 -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel0, link-type RAW (Raw IP), capture size 262144 bytes
22:39:30.628099 IP 10.3.3.0 > 10.3.83.2: ICMP echo request, id 34, seq 1, length 64
22:39:30.628666 IP 10.3.83.2 > 10.3.3.0: ICMP echo reply, id 34, seq 1, length 64
走到flannel0也還未封裝,真正的封裝是在flanneld進(jìn)程中。但是我們發(fā)現(xiàn)這里源IP地址變化了,容器內(nèi)的IP是10.3.3.2。這是因?yàn)橄到y(tǒng)對(duì)源IP進(jìn)行了偽裝,我們可以查看系統(tǒng)的iptables規(guī)則,在nat的POSTROUTING鏈中有以下規(guī)則,
*nat
......
-A POSTROUTING -s 10.3.3.0/24 ! -o docker0 -j MASQUERADE
所以從flannel0出來后源IP地址就變成了flannel0的地址。這樣就隱藏了后端容器的IP,更為安全。當(dāng)然也可以刪除這條規(guī)則,并不會(huì)影響網(wǎng)絡(luò)的通信。
4、flanneld進(jìn)程 -> ens33
沒辦法在flannld進(jìn)程上抓包,因此我們直接在ens33中抓包。此時(shí)因?yàn)橐呀?jīng)經(jīng)過flanneld進(jìn)程的UDP封裝,因此無法抓取icmp報(bào)文,只能抓取UDP報(bào)文。
[root@HOST-A ~]# tcpdump -i ens33 -p udp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
23:16:44.689161 IP 192.168.52.129.8285 > 192.168.52.145.8285: UDP, length 84
23:16:44.689588 IP 192.168.52.145.8285 > 192.168.52.129.8285: UDP, length 84
可以看到UDP報(bào)文發(fā)往HOST B的8285端口,也就是flanneld進(jìn)程。
我們可以將抓取的報(bào)文通過wireshark分析,

在報(bào)文的數(shù)據(jù)段,我們可以找到封裝的容器內(nèi)部IP信息,即10.3.3.0 > 10.3.83.2。