概述
接著翻譯第二部分,原文打開速度好慢
原文地址
原文地址
作者:LAURENT BERNAILLE
介紹
在這篇blog的第一部分,我們知道了Docker如何給overlay創(chuàng)建了獨有的網絡命名空間并且將overlay上面的容器連接到這個命名空間。我們也知道了overlay網絡在Docker宿主機之間的通信是利用VXLAN。在第二部分,我們將更加詳細的探究一下VXLAN并且了解Docker是如何使用VXLAN的。
什么是VXLAN
Virtual Extensible LAN (VXLAN) is a network virtualization technology that attempts to improve the scalability problems associated with large cloud computing deployments.
VXLAN是一項通道技術,它將L2(鏈路層)的網絡幀包裝在UDP包里面并通過4789端口發(fā)送出去,這項技術最開始是被VMWare,Arista和思科一起開發(fā)的。VXLAN的主要目標是簡化需要在L2層多租戶的云部署。它提供:
- 通過在L3上構建通道傳輸L2以避免群集中所有主機之間的L2連接的必要性
- 可以超過4096個獨立的網絡(VLAN ID的數(shù)量被限制為不能大于4096)
在Linux上,Openvswitch支持VXLAN并且內核自3.7版本開始原生支持VXLAN。另外,從內核3.16開始,VXLAN支持網絡命名空間。
下面是一個VXLAN包的樣子:

外層的IP包用于在宿主機之間的通信,內層的L2幀被加上了一個VXLAN的頭部信息(特別是VXLAN ID)然后被包裝在一個UDP包里面的。
我們用tcpdump可以驗證我們的宿主機是用VXLAN進行通信的。我們從docker1上的容器ping C0并且在docker0上面抓包:
docker1:~$ docker run -it --rm --net demonet debian ping 192.168.0.100
PING 192.168.0.100 (192.168.0.100): 56 data bytes
64 bytes from 192.168.0.100: icmp_seq=0 ttl=64 time=0.680 ms
64 bytes from 192.168.0.100: icmp_seq=1 ttl=64 time=0.503 ms
docker0:~$ sudo tcpdump -pni eth0 "port 4789"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
12:55:53.652322 IP 10.0.0.11.64667 > 10.0.0.10.4789: VXLAN, flags [I] (0x08), vni 256
IP 192.168.0.2 > 192.168.0.100: ICMP echo request, id 1, seq 0, length 64
12:55:53.652409 IP 10.0.0.10.47697 > 10.0.0.11.4789: VXLAN, flags [I] (0x08), vni 256
IP 192.168.0.100 > 192.168.0.2: ICMP echo reply, id 1, seq 0, length 64
在tcpdump的輸出信息中,由于包含了對于VXLAN幀的分析(為了可讀性有些字段被刪除了),每個包都產生兩行信息:
- 外層幀,IP地址為10.0.0.11 和 10.0.0.10(docker宿主機)
- 里層幀,IP地址為192.168.0.100 和 192.168.0.2(我們的容器)和ICMP payload。我們也可以看到容器的MAC地址
更新網絡結構圖如下

解析容器名稱和位置
我們知道了從docker1上的容器ping docker0上的容器是使用VXLAN的,但是我們現(xiàn)在還不知道在各自宿主機上的容器是如何將IP映射為MAC地址,并將L2幀傳送到合適的宿主機上。
讓我們在docker1上面創(chuàng)建一個容器并查看容器的ARP表:
docker1:~$ docker run -it --rm --net demonet debian bash
root@6234b23677b9:/# ip neighbor show
現(xiàn)在在容器里面沒有ARP信息。當我們ping C0的時候,容器將會發(fā)起ARP通信。讓我們先在docker0的overlay網絡命名空間上看一下這個arp通信的是什么樣的:
docker0:~$ sudo nsenter --net=$overns tcpdump -pni any "arp"
回到剛才創(chuàng)建的容器,我們將試著去ping C0,這將會創(chuàng)建一個ARP包:
root@6234b23677b9:/# ping 192.168.0.100
在docker0的tcpdump輸出中沒看到任何信息,所以ARP包并沒有在VXLAN通道中發(fā)送(你也許看到arp請求,但是沒有針對主機是192.168.0.100的)。讓我們在docker1上面再創(chuàng)建一個容器并且在docker1的overlay網絡命名空間中利用tcpdump抓包,從而確認我們是有ARP請求的。
docker1:~$ docker run -it --rm --net demonet debian bash
我們在另一個窗口上運行tcpdump。我們列出Docker的網絡命名空間以便挑選出跟overlay相關的命名空間。這個命名空間可能變化了是因為當沒有容器附在這個overlay網絡上的時候,overlay的命名空間會被刪除。
docker1:~$ sudo ls -1 /var/run/docker/netns
102022d57fab
x-13fb802253
docker1:~$ overns=/var/run/docker/netns/x-13fb802253
docker1:~$ sudo nsenter --net=$overns tcpdump -peni any "arp"
當我們在容器的窗口執(zhí)行ping命令后,可以在看到tcpdump的輸出:
19:16:40.658369 Out 02:42:c0:a8:00:02 ethertype ARP (0x0806), length 44: Request who-has 192.168.0.100 tell 192.168.0.2, length 28
19:16:40.658352 B 02:42:c0:a8:00:02 ethertype ARP (0x0806), length 44: Request who-has 192.168.0.100 tell 192.168.0.2, length 28
19:16:40.658371 In 02:42:c0:a8:00:64 ethertype ARP (0x0806), length 44: Reply 192.168.0.100 is-at 02:42:c0:a8:00:64, length 28
19:16:40.658377 Out 02:42:c0:a8:00:64 ethertype ARP (0x0806), length 44: Reply 192.168.0.100 is-at 02:42:c0:a8:00:64, length 28
我們可以看到ARP的請求和應答,這意味著overlay網絡命名空間有相關的信息并且它充當一個ARP代理的角色。我們可以簡單驗證一下:
docker1:~$ sudo nsenter --net=$overns ip neigh show
192.168.0.100 dev vxlan0 lladdr 02:42:c0:a8:00:64 PERMANENT
這條記錄被標志為PERMANENT意味著這是一條靜態(tài)記錄并且是被手工添加的(不是通過ARP發(fā)現(xiàn)得到的結果)。如果我們在docker0上面創(chuàng)建第二個容器,那么將會發(fā)生什么呢?
docker0:~$ docker run -d --ip 192.168.0.200 --net demonet --name C1 debian sleep 3600
docker1:~$ sudo nsenter --net=$overns ip neigh show
192.168.0.200 dev vxlan0 lladdr 02:42:c0:a8:00:c8 PERMANENT
192.168.0.100 dev vxlan0 lladdr 02:42:c0:a8:00:64 PERMANENT
記錄被自動添加了,即使目前還沒有通信包被發(fā)到這個新的容器中。這意味著Docker自動操作在overlay網絡命名空間中的ARP記錄,并且vxlan網卡充當一個代理去應答ARP請求。
如果我們查看vxlan網卡的配置,我們可以看到代理標志是被設置好的。這解釋了這個這個行為(我們將在后面看到另外的選項)。
docker1:~$ sudo nsenter --net=$overns ip -d link show vxlan0
xx: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default
link/ether 5a:71:8f:a4:b8:1b brd ff:ff:ff:ff:ff:ff promiscuity 1
vxlan id 256 srcport 10240 65535 dstport 4789 proxy l2miss l3miss ageing 300
bridge_slav
那如何來查看MAC地址的所在的位置呢(02:42:c0:a8:00:64是在那臺主機上面)?我們可以查看overlay命名空間里面的網橋的轉發(fā)數(shù)據(jù)庫:
docker1:~$ sudo nsenter --net=$overns bridge fdb show
5a:71:8f:a4:b8:1b dev vxlan0 vlan 0 master br0 permanent
9a:ad:35:64:39:39 dev veth2 vlan 0 master br0 permanent
02:42:c0:a8:00:c8 dev vxlan0 dst 10.0.0.10 self permanent
02:42:c0:a8:00:64 dev vxlan0 dst 10.0.0.10 self permanent
33:33:00:00:00:01 dev veth2 self permanent
01:00:5e:00:00:01 dev veth2 self permanent
我們可以看到docker0容器的MAC地址在數(shù)據(jù)庫里面,并且被標志為permanent。這個信息也是docker動態(tài)添加的。

我們發(fā)現(xiàn)了Docker自動操作MAC地址和FDB的信息。它是如何做到的呢?
我們可以先看一下Consul的內容。它里面存了寫什么?

在我們剛開始的時候,這個網絡是空的,但是現(xiàn)在它包含了一些信息。我們可以利用overlay網絡的id來識別它。
當key的長度太長的時候,Consul的UI不能顯示key。但是我們可以使用curl去查看內容(docker將信息通過base64編碼過然后存為json格式,Consul應答格式為json):
net=$(docker network inspect demonet -f {{.Id}})
curl -s http://consul:8500/v1/kv/docker/network/v1.0/network/${net}/ | jq -r ".[0].Value" | base64 -d | jq .
{
"addrSpace": "GlobalDefault",
"attachable": false,
"created": "2017-04-23T16:33:02.442759329Z",
"enableIPv6": false,
"generic": {
"com.docker.network.enable_ipv6": false,
"com.docker.network.generic": {}
},
"id": "13fb802253b6f0a44e17e2b65505490e0c80527e1d78c4f5c74375aff4bf882a",
"inDelete": false,
"ingress": false,
"internal": false,
"ipamOptions": {},
"ipamType": "default",
"ipamV4Config": "[{\"PreferredPool\":\"192.168.0.0/24\",\"SubPool\":\"\",\"Gateway\":\"\",\"AuxAddresses\":null}]",
"ipamV4Info": "[{\"IPAMData\":\"{\\\"AddressSpace\\\":\\\"GlobalDefault\\\",\\\"Gateway\\\":\\\"192.168.0.1/24\\\",\\\"Pool\\\":\\\"192.168.0.0/24\\\"}\",\"PoolID\":\"GlobalDefault/192.168.0.0/24\"}]",
"labels": {},
"name": "demonet",
"networkType": "overlay",
"persist": true,
"postIPv6": false,
"scope": "global"
}
我們可以看到網絡的所有元數(shù)據(jù):
- 名稱: demonet
- id:13fb802253b6f0a44e17e2b65505490e0c80527e1d78c4f5c74375aff4bf882a
- 子網: 192.168.0.0/24
我們也可以利用curl獲取網絡端信息,但是太難閱讀了,所以我們將使用一個簡單的python(在GitHub的repository獲?。┠_本來獲取這個信息
import consul
import json
# First we connect to consul
c=consul.Consul(host="consul",port=8500)
# We retrieve all endpoint keys from Consul
(idx,endpoints)=c.kv.get("docker/network/v1.0/endpoint/",recurse=True)
epdata=[ ep['Value'] for ep in endpoints if ep['Value'] is not None]
# We print some interesting data on these endpoints
for data in epdata:
jsondata=json.loads(data.decode("utf-8"))
print("Endpoint Name: %s" % jsondata["name"])
print("IP address: %s" % jsondata["ep_iface"]["addr"])
print("MAC address: %s" % jsondata["ep_iface"]["mac"])
print("Locator: %s\n" % jsondata["locator"])
這個腳本展示了容器網絡端的主要信息:
- 名稱
- IP地址
- MAC地址
- 位置: 容器所在的宿主機
以下是我們當前的設置:
docker1:~$ python/dump_endpoints.py
Endpoint Name: adoring_einstein
IP address: 192.168.0.2/24
MAC address: 02:42:c0:a8:00:02
Locator: 10.0.0.11
Endpoint Name: C1
IP address: 192.168.0.200/24
MAC address: 02:42:c0:a8:00:c8
Locator: 10.0.0.10
Consul被用作所有靜態(tài)信息的中心存儲。然而,當有容器創(chuàng)建的時候,這還不足以動態(tài)通知所有的主機。事實證明,Docker使用serf和Gossip協(xié)議來實現(xiàn)這一目標。我們可以利用在docker0上訂閱serf事件然后再在docker1上面創(chuàng)建一個容器來驗證這一點。
docker0:~$ serf agent -bind 0.0.0.0:17946 -join 10.0.0.11:7946 -node demo -log-level=debug -event-handler=./serf.sh
#########################################
New event: member-join
demo 10.0.0.10
docker0 10.0.0.10
docker1 10.0.0.11
#########################################
為了我們能夠關注相關的信息,我刪掉了大多數(shù)的輸出。我們可以看到所有的節(jié)點都參與到Gossip中。
Serf利用以下的選項來啟動:
- bind: 綁定一個不同于7946的端口(這個端口已經被Docker使用)
- join: 加入serf集群中
- node: 給定一個節(jié)點名稱(docker0 已經被使用了)
- event-handler:一個簡單的腳本來顯示serf事件
- log-level=debug: 這個選項用于看到event handler腳本的輸出
serf.sh腳本的內容如下:
echo "New event: ${SERF_EVENT}"
while read line; do
printf "${line}\n"
現(xiàn)在,我們在docker1上面創(chuàng)建一個容器,然后看一下docker0上面的輸出:
docker1:~$ docker run -it --rm --net demonet debian sleep 10
在docker0上,我們看到:
New event: user
join 192.168.0.2 255.255.255.0 02:42:c0:a8:00:02
Docker的守護程序訂閱這些事件,然后去創(chuàng)建和刪除ARP表和FDB上面的記錄。

在swarm模式中,Docker不是依賴于Serf去同步節(jié)點的信息,而是利用它自己的Gossip協(xié)議的實現(xiàn),但是實際上是做的一樣的事情。
另一些VXLAN通信選項
Docker的守護程序利用serf通過Gossip協(xié)議來獲取信息,然后自動操作ARP表和FDB表,然后依賴VXLAN網卡的ARP代理overlay網絡的的通信。但是,VXLAN也提供了其他選項去發(fā)現(xiàn)信息。
端到端通信
當VXLAN配置“remote”選項,它會將未知的通信包都發(fā)送到這個IP,這個設置非常簡單但是僅限于兩個主機間的通道。

多播通信
當VXLAN配置為“group”選項,它將所有的未知包都發(fā)送給多播組。這個配置非常有效,但是需要所有主機間的多播連接。并不總是可用的,特別是在公有云上面。

更多關于VXLAN在linux上的配置的詳細信息,我推薦這個非常完整的post:VXLAN&Linux。
結論
在前面的兩部分,我們看到Docker的overlay網絡是如何工作的和它所依賴的技術。在第三部分,也是最后一部分,我們可以只利用Linux命令來創(chuàng)建自己的overlay網絡。