概述
終于到了最后一部分了
原文地址
原文地址
作者:LAURENT BERNAILLE
介紹
在這篇文章的第一部分,我們已經(jīng)知道了Docker創(chuàng)建了一個(gè)專(zhuān)用的網(wǎng)絡(luò)命名空間給overlay網(wǎng)絡(luò),所有上面的容器都連接到這個(gè)命名空間。在第二部分,我們?cè)敿?xì)的了解了Docker如何利用VXLAN技術(shù)在宿主機(jī)之間利用通道進(jìn)行overlay的通信。在這第三部分,我們可以了解到如何利用標(biāo)準(zhǔn)的Linux命令來(lái)創(chuàng)建我們自己的overlay網(wǎng)絡(luò)。
手工創(chuàng)建overlay網(wǎng)絡(luò)
如果你在環(huán)境中執(zhí)行過(guò)前面兩部分中介紹的命令,你需要通過(guò)刪除所有的容器和overlay網(wǎng)絡(luò)來(lái)清空你的Docker宿主機(jī):
docker0:~$ docker rm -f $(docker ps -aq)
docker0:~$ docker network rm demonet
docker1:~$ docker rm -f $(docker ps -aq)
接下來(lái),你需要做的是創(chuàng)建一個(gè)命名為“overns”的網(wǎng)絡(luò)命名空間:
sudo ip netns add overns
現(xiàn)在,我們?cè)僭谶@個(gè)命名空間里面創(chuàng)建一個(gè)bridge,給這個(gè)bridge一個(gè)IP地址,并啟動(dòng)它:
docker0:~$ sudo ip netns exec overns ip link add dev br0 type bridge
docker0:~$ sudo ip netns exec overns ip addr add dev br0 192.168.0.1/24
docker0:~$ sudo ip netns exec overns ip link set br0 up
接下來(lái)創(chuàng)建一個(gè)VXLAN的網(wǎng)卡,然后附在上面的bridge:
docker0:~$ sudo ip link add dev vxlan1 type vxlan id 42 proxy learning dstport 4789
docker0:~$ sudo ip link set vxlan1 netns overns
docker0:~$ sudo ip netns exec overns ip link set vxlan1 master br0
docker0:~$ sudo ip netns exec overns ip link set vxlan1 up
到目前為止,最重要的命令是創(chuàng)建VXLAN網(wǎng)卡。我們配置這張網(wǎng)卡的id為42,通道端口為標(biāo)準(zhǔn)的VXLAN端口。proxy選項(xiàng)允許vxlan網(wǎng)卡響應(yīng)ARP請(qǐng)求(在第二部分看到的那樣)。我們將會(huì)在后面討論learning選項(xiàng)。注意,我們沒(méi)有在overlay的命名空間內(nèi)創(chuàng)建VXLAN網(wǎng)卡,而是在主機(jī)上面創(chuàng)建網(wǎng)卡,然后移到命名空間中。這個(gè)是必須的,這樣VXLAN網(wǎng)卡可以保持到主機(jī)的網(wǎng)卡鏈接,從而可以通過(guò)網(wǎng)絡(luò)來(lái)發(fā)送網(wǎng)絡(luò)包。如果我們直接在命名空間中創(chuàng)建這張網(wǎng)卡(像創(chuàng)建br0那樣),那么我們將不能講網(wǎng)絡(luò)包發(fā)送到命名空間外面去。
一旦,我們?cè)赿ocker0和docker1上面執(zhí)行這些命令以后,我們將擁有如下網(wǎng)絡(luò)圖:

現(xiàn)在,我們將創(chuàng)建容器并且將他們連接到bridge上面去。我們從docker0上面開(kāi)始。首先,我們創(chuàng)建一個(gè)容器:
docker0:~$ docker run -d --net=none --name=demo debian sleep 3600
我們將需要這個(gè)容器的網(wǎng)絡(luò)命名空間路徑。我們可以通過(guò)inspect這個(gè)容器來(lái)發(fā)現(xiàn)。
docker0:~$ ctn_ns_path=$(docker inspect --format="{{ .NetworkSettings.SandboxKey}}" demo)
由于--net=none選項(xiàng),我們的容器沒(méi)有網(wǎng)絡(luò)連接。我們現(xiàn)在創(chuàng)建一對(duì)veth網(wǎng)卡,將其中一端(veth1)移到我們的overlay網(wǎng)絡(luò)命名空間里去,附在bridge上面,然后啟動(dòng)它。
docker0:~$ sudo ip link add dev veth1 mtu 1450 type veth peer name veth2 mtu 1450
docker0:~$ sudo ip link set dev veth1 netns overns
docker0:~$ sudo ip netns exec overns ip link set veth1 master br0
docker0:~$ sudo ip netns exec overns ip link set veth1 up
第一個(gè)命令使用MTU為1450來(lái)創(chuàng)建網(wǎng)卡,這個(gè)是必須要的,是因?yàn)閂XLAN標(biāo)準(zhǔn)頭部添加的開(kāi)銷(xiāo)。
接下來(lái)的步驟是設(shè)置veth2:將它放到容器的命名空間中,并配置MAC地址為(02:42:c0:a8:00:02)和IP 地址為(192.168.0.2):
docker0:~$ ctn_ns=${ctn_ns_path##*/}
docker0:~$ sudo ln -sf $ctn_ns_path /var/run/netns/$ctn_ns
docker0:~$ sudo ip link set dev veth2 netns $ctn_ns
docker0:~$ sudo ip netns exec $ctn_ns ip link set dev veth2 name eth0 address 02:42:c0:a8:00:02
docker0:~$ sudo ip netns exec $ctn_ns ip addr add dev eth0 192.168.0.2/24
docker0:~$ sudo ip netns exec $ctn_ns ip link set dev eth0 up
docker0:~$ sudo rm /var/run/netns/$ctn_ns
在/var/run/netns中創(chuàng)建符號(hào)鏈接是必須的,這樣我們就可以使用本地的ip netns命令(將網(wǎng)卡移動(dòng)到容器網(wǎng)絡(luò)命名空間中)。我們使用和Docker相同的地址命名方式:MAC地址的最后4個(gè)字節(jié)和容器的IP地址一致,第二個(gè)字節(jié)是VXLAN id。
我們需要在docker1上面重復(fù)相同的命令,但是使用不同的MAC地址和IP地址(02:42:c0:a8:00:03 and 192.168.0.3)。如果你使用來(lái)自倉(cāng)庫(kù)的terraform stack,里面有個(gè)幫助腳本將容器附到overlay網(wǎng)絡(luò)上。我們可以子啊docker1上面使用:
docker1:~$ docker run -d --net=none --name=demo debian sleep 3600
docker1:~$ ./attach-ctn.sh demo 3
第一個(gè)參數(shù)是容器的名稱(chēng),第二個(gè)參數(shù)是MAC/IP地址的最后的數(shù)字。
下面是我們的當(dāng)前配置

現(xiàn)在我們的容器配置好了,我們可以測(cè)試連通性:
docker0:~$ docker exec -it demo ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
92 bytes from 192.168.0.2: Destination Host Unreachable
我們現(xiàn)在還不能ping通。讓我們利用在容器和overlay網(wǎng)絡(luò)命名空間中查看ARP表記錄來(lái)理解一下為什么:
docker0:~$ docker exec demo ip neighbor show
docker0:~$ sudo ip netns exec overns ip neighbor show
兩個(gè)命令都沒(méi)有返回任何信息:他們并不知道哪個(gè)MAC地址是關(guān)聯(lián)到IP 192.168.0.3上的。我們可以通過(guò)創(chuàng)建ARP請(qǐng)求并且在overlay網(wǎng)絡(luò)上面的利用tcpdump進(jìn)行抓包來(lái)驗(yàn)證我們的命令:
docker0:~$ sudo ip netns exec overns tcpdump -i br0
docker0:~$ tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
我們?cè)诹硪粋€(gè)窗口重新運(yùn)行ping命令,將會(huì)看到的tcpdump有如下輸出:
17:15:27.074500 ARP, Request who-has 192.168.0.3 tell 192.168.0.2, length 28
17:15:28.071265 ARP, Request who-has 192.168.0.3 tell 192.168.0.2, length 28
ARP請(qǐng)求被廣播且被overlay網(wǎng)絡(luò)命名空間收到,但是沒(méi)有收到任何應(yīng)答。我們?cè)诘诙糠忠呀?jīng)知道Docker守護(hù)程序來(lái)操作ARP表和FDB表,并且通過(guò)VXLAN網(wǎng)卡的proxy選項(xiàng)來(lái)應(yīng)答ARP請(qǐng)求。我們配置利用相同的選項(xiàng)配置了我們的網(wǎng)卡,所以我們可以通過(guò)簡(jiǎn)單的操作overlay網(wǎng)絡(luò)命名空間的ARP表和FDB表來(lái)達(dá)到相同的效果:
docker0:~$ sudo ip netns exec overns ip neighbor add 192.168.0.3 lladdr 02:42:c0:a8:00:03 dev vxlan1
docker0:~$ sudo ip netns exec overns bridge fdb add 02:42:c0:a8:00:03 dev vxlan1 self dst 10.0.0.11 vni 42 port 4789
第一個(gè)命令為192.168.0.3創(chuàng)建了一條ARP記錄,第二個(gè)命令設(shè)置轉(zhuǎn)發(fā)表,設(shè)置了MAC地址是可以通過(guò)VXLAN網(wǎng)卡來(lái)連通的,其中VXLAN id為42,主機(jī)是10.0.0.11.
現(xiàn)在可以連通了嗎?
docker0:~$ docker exec -it demo ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
^C--- 192.168.0.3 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
還沒(méi)有,因?yàn)槲覀冞€沒(méi)有在docker1上面配置。ICMP請(qǐng)求被docker1上面的容器接收到了,但是它不知如何去應(yīng)答。我們可以在docker1上面驗(yàn)證這一點(diǎn):
docker1:~$ sudo ip netns exec overns ip neighbor show
docker1:~$ sudo ip netns exec overns bridge fdb show
0e:70:32:15:1d:01 dev vxlan1 vlan 0 master br0 permanent
02:42:c0:a8:00:03 dev veth1 vlan 0 master br0
ca:9c:c1:c7:16:f2 dev veth1 vlan 0 master br0 permanent
02:42:c0:a8:00:02 dev vxlan1 vlan 0 master br0
02:42:c0:a8:00:02 dev vxlan1 dst 10.0.0.10 self
33:33:00:00:00:01 dev veth1 self permanent
01:00:5e:00:00:01 dev veth1 self permanent
33:33:ff:c7:16:f2 dev veth1 self permanent
第一個(gè)命令顯示,沒(méi)有任何的ARP信息在192.168.0.3上,這跟預(yù)期的一致。第二個(gè)命令令人驚訝的,因?yàn)槲覀兛梢钥吹睫D(zhuǎn)發(fā)表里面有關(guān)于docker0上面的容器的信息。這個(gè)是如下導(dǎo)致的:當(dāng)ICMP請(qǐng)求到達(dá)網(wǎng)卡,這些記錄是被學(xué)習(xí)到的并記錄到轉(zhuǎn)發(fā)表中。這個(gè)行為是通過(guò)VXLAN網(wǎng)卡的“l(fā)earning”選項(xiàng)來(lái)達(dá)到的。讓我們添加ARP信息到docker1上,然后驗(yàn)證一下我們可以ping通了:
docker1:~$ sudo ip netns exec overns ip neighbor add 192.168.0.2 lladdr 02:42:c0:a8:00:02 dev vxlan1
docker0:~$ docker exec -it demo ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
64 bytes from 192.168.0.3: icmp_seq=0 ttl=64 time=1.737 ms
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.494 ms
我現(xiàn)在成功通過(guò)標(biāo)準(zhǔn)的Linux命令來(lái)創(chuàng)建了一個(gè)overlay網(wǎng)絡(luò):

動(dòng)態(tài)容器發(fā)現(xiàn)
我們剛剛從頭開(kāi)始創(chuàng)建了一個(gè)overlay網(wǎng)絡(luò)。然而,我們需要手工為容器創(chuàng)建ARP和FDB記錄為了他們之間能夠相互連通。我們現(xiàn)在來(lái)看如何將這個(gè)發(fā)現(xiàn)過(guò)程自動(dòng)化。
我們先清空從頭創(chuàng)建的內(nèi)容
docker0:~$ docker rm -f $(docker ps -aq)
docker0:~$ sudo ip netns delete overns
docker1:~$ docker rm -f $(docker ps -aq)
docker1:~$ sudo ip netns delete overns
捕獲網(wǎng)絡(luò)事件:NETLINK
NETLINK用來(lái)在內(nèi)核和用戶(hù)空間來(lái)傳遞信息:https://en.wikipedia.org/wiki/Netlink。
iproute2,我們以前用來(lái)配置網(wǎng)卡,依賴(lài)Netlink獲取/發(fā)送配置信息給內(nèi)核。它由多種協(xié)議組成以便和不同的內(nèi)核組件進(jìn)行通信。這當(dāng)中最常見(jiàn)的協(xié)議就是NETLINK_ROUTE,它是用來(lái)配置路由和鏈接的接口。
對(duì)于每種協(xié)議,Netlink消息是按組來(lái)組織的,例如拿NETLINK_ROUTE 來(lái)說(shuō):
- RTMGRP_LINK:鏈接相關(guān)的消息
- RTMGRP_NEIGH: 鄰居相關(guān)的消息
- 其他
對(duì)于每個(gè)組,有多個(gè)通知,如:
- RTMGRP_LINK:
- RTM_NEWLINK:一個(gè)鏈接被創(chuàng)建
- RTM_DELLINK:一個(gè)鏈接被刪除
- RTMGRP_NEIGH:
- RTM_NEWNEIGH:一個(gè)鄰居被添加
- RTM_DELNEIGH:一個(gè)鄰居被刪除
- RTM_GETNEIGH:內(nèi)核在查找鄰居
我描述了發(fā)生這些事件時(shí)內(nèi)核發(fā)送給用戶(hù)空間的消息通知,但是相同的消息可以發(fā)送給內(nèi)核來(lái)配置鏈接和鄰居。
iproute2允許我們利用monitor子命令來(lái)監(jiān)聽(tīng)Netlink事件。通過(guò)如下命令,我們可以監(jiān)聽(tīng)實(shí)例鏈接信息:
docker0:~$ ip monitor link
在另一個(gè)docker0的窗口上,我們可以創(chuàng)建和刪除鏈接:
docker0:~$ sudo ip link add dev veth1 type veth peer name veth2
docker0:~$ sudo ip link del veth1
在第一個(gè)窗口,我們可以看到一些輸出。
當(dāng)我們創(chuàng)建網(wǎng)卡的時(shí)候:
32: veth2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether b6:95:d6:b4:21:e9 brd ff:ff:ff:ff:ff:ff
33: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether a6:e0:7a:da:a9:ea brd ff:ff:ff:ff:ff:ff
當(dāng)我們刪除網(wǎng)卡的時(shí)候:
Deleted 33: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether a6:e0:7a:da:a9:ea brd ff:ff:ff:ff:ff:ff
Deleted 32: veth2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether b6:95:d6:b4:21:e9 brd ff:ff:ff:ff:ff:ff
我們可以用這個(gè)命令來(lái)監(jiān)聽(tīng)其他的事件:
docker0:~$ ip monitor route
在另一個(gè)終端:
docker0:~$ sudo ip route add 8.8.8.8 via 10.0.0.1
docker0:~$ sudo ip route del 8.8.8.8 via 10.0.0.1
我們獲取下面的輸出:
8.8.8.8 via 10.0.0.1 dev eth0
Deleted 8.8.8.8 via 10.0.0.1 dev eth0
在我們的場(chǎng)景中,我們關(guān)注鄰居事件,特別是RTM_GETNEIGH。這個(gè)事件是在內(nèi)核沒(méi)有鄰居信息的時(shí)候產(chǎn)生的并發(fā)送這個(gè)通知給用戶(hù)空間,然后應(yīng)用可以創(chuàng)建它。默認(rèn)情況下,這個(gè)事件時(shí)不會(huì)發(fā)送給用戶(hù)空間的,但是我們可以啟用它并監(jiān)聽(tīng)鄰居事件通知:
docker0:~$ echo 1 | sudo tee -a /proc/sys/net/ipv4/neigh/eth0/app_solicit
docker0:~$ ip monitor neigh
之后不需要此設(shè)置,因?yàn)槲覀兊膙xlan網(wǎng)卡的l2miss和l3miss選項(xiàng)將生成RTM_GETNEIGH事件。
在第二個(gè)窗口,我們可以觸發(fā)GETNEIGH 事件的創(chuàng)建:
docker0:~$ ping 10.0.0.100
下面是我們獲取的輸出:
10.0.0.100 dev eth0 FAILED
miss 10.0.0.100 dev eth0 INCOMPLETE
我們可以在overlay網(wǎng)絡(luò)上的容器里面使用相同的命令。我們先創(chuàng)建一個(gè)overlay網(wǎng)絡(luò)并附加一個(gè)容器在上面:
docker0:~$ ./create-overlay.sh
docker0:~$ docker run -d --net=none --name=demo debian sleep 3600
docker0:~$ ./attach-ctn.sh demo 2
docker0:~$ docker exec demo ip monitor neigh
這兩個(gè)腳本可以在github的倉(cāng)庫(kù)中找到。
create-overlay 腳本利用前面介紹的命令創(chuàng)建了一個(gè)overlay網(wǎng)絡(luò)叫overns:
#!/bin/bash
sudo ip netns delete overns 2> /dev/null && echo "Deleting existing overlay"
sudo ip netns add overns
sudo ip netns exec overns ip link add dev br0 type bridge
sudo ip netns exec overns ip addr add dev br0 192.168.0.1/24
sudo ip link add dev vxlan1 type vxlan id 42 proxy learning l2miss l3miss dstport 4789
sudo ip link set vxlan1 netns overns
sudo ip netns exec overns ip link set vxlan1 master br0
sudo ip netns exec overns ip link set vxlan1 up
sudo ip netns exec overns ip link set br0 up
attach-ctn腳本將一個(gè)容器附在overlay網(wǎng)絡(luò)上。第一個(gè)參數(shù)是容器的名稱(chēng),第二個(gè)參數(shù)是IP地址的最后一個(gè)字節(jié):
#!/bin/bash
ctn=${1:-demo}
ip=${2:-2}
ctn_ns_path=$(docker inspect --format="{{ .NetworkSettings.SandboxKey}}" $ctn)
ctn_ns=${ctn_ns_path##*/}
# create veth interfaces
sudo ip link add dev veth1 mtu 1450 type veth peer name veth2 mtu 1450
# attach first peer to the bridge in our overlay namespace
sudo ip link set dev veth1 netns overns
sudo ip netns exec overns ip link set veth1 master br0
sudo ip netns exec overns ip link set veth1 up
# crate symlink to be able to use ip netns commands
sudo ln -sf $ctn_ns_path /var/run/netns/$ctn_ns
sudo ip link set dev veth2 netns $ctn_ns
# move second peer tp container network namespace and configure it
sudo ip netns exec $ctn_ns ip link set dev veth2 name eth0 address 02:42:c0:a8:00:0${ip}
sudo ip netns exec $ctn_ns ip addr add dev eth0 192.168.0.${ip}/24
sudo ip netns exec $ctn_ns ip link set dev eth0 up
# Clean up symlink
sudo rm /var/run/netns/$ctn_ns
我們現(xiàn)在可以在容器里面運(yùn)行ip monitor:
docker0:~$ docker exec demo ip monitor neigh
在第二窗口里面,我們可以ping一個(gè)未知的主機(jī)去創(chuàng)建一個(gè)GETNEIGH 事件:
docker0:~$ docker exec demo ping 192.168.0.3
在第一個(gè)窗口,我們可以看到一個(gè)鄰居事件:
192.168.0.3 dev eth0 FAILED
我們也可以查看overlay的網(wǎng)絡(luò)命名空間:
docker0:~$ sudo ip netns exec overns ip monitor neigh
miss 192.168.0.3 dev vxlan1 STALE
這個(gè)事件有一點(diǎn)不同,因?yàn)檫@個(gè)是被vxlan網(wǎng)卡創(chuàng)建的(因?yàn)槲覀兝胠2miss選項(xiàng)和l3miss選項(xiàng)來(lái)創(chuàng)建網(wǎng)卡)。讓我們添加鄰居信息到overlay網(wǎng)絡(luò)命名空間:
docker0:~$ sudo ip netns exec overns ip neighbor add 192.168.0.3 lladdr 02:42:c0:a8:00:03 dev vxlan1 nud permanent
如果我們運(yùn)行ip monitor neigh命令并且在另一個(gè)窗口執(zhí)行ping,下面我們可以看到:
docker0:~$ sudo ip netns exec overns ip monitor neigh
miss dev vxlan1 lladdr 02:42:c0:a8:00:03 STALE
現(xiàn)在我們有了ARP信息, 我們得到了一個(gè)L2miss是因?yàn)槲覀冞€不知道這個(gè)mac地址是位于overlay的哪個(gè)位置。讓我們來(lái)添加這個(gè)信息:
docker0:~$ sudo ip netns exec overns bridge fdb add 02:42:c0:a8:00:03 dev vxlan1 self dst 10.0.0.11 vni 42 port 4789
現(xiàn)在我們?cè)賵?zhí)行ip monitor neigh 命令并執(zhí)行ping命令,將不會(huì)再看到鄰居事件出現(xiàn)了。
在我們需要獲取事件去操作2層和3層網(wǎng)絡(luò)信息方面,ip monitor是非常有用的,所以我們需要編寫(xiě)代碼去跟它交互。
下面是一個(gè)簡(jiǎn)單的python腳本去訂閱Netlink消息并解析GETNEIGH事件:
#!/usr/bin/env python
# Create the netlink socket and bind to NEIGHBOR NOTIFICATION,
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
s.bind((os.getpid(), RTMGRP_NEIGH))
while True:
data = s.recv(65535)
msg_len, msg_type, flags, seq, pid = struct.unpack("=LHHLL", data[:16])
# We fundamentally only care about GETNEIGH messages
if msg_type != RTM_GETNEIGH:
continue
data=data[16:]
ndm_family, _, _, ndm_ifindex, ndm_state, ndm_flags, ndm_type = struct.unpack("=BBHiHBB", data[:12])
logging.debug("Received a Neighbor miss")
logging.debug("Family: {}".format(if_family.get(ndm_family,ndm_family)))
logging.debug("Interface index: {}".format(ndm_ifindex))
logging.debug("State: {}".format(nud_state.get(ndm_state,ndm_state)))
logging.debug("Flags: {}".format(ndm_flags))
logging.debug("Type: {}".format(type.get(ndm_type,ndm_type)))
data=data[12:]
rta_len, rta_type = struct.unpack("=HH", data[:4])
logging.debug("RT Attributes: Len: {}, Type: {}".format(rta_len,nda_type.get(rta_type,rta_type)))
data=data[4:]
if nda_type.get(rta_type,rta_type) == "NDA_DST":
dst=socket.inet_ntoa(data[:4])
logging.info("L3Miss: Who has IP: {}?".format(dst))
if nda_type.get(rta_type,rta_type) == "NDA_LLADDR":
mac="%02x:%02x:%02x:%02x:%02x:%02x" % struct.unpack("BBBBBB",data[:6])
logging.info("L2Miss: Who has MAC: {}?".format(mac))
這個(gè)腳本只包含我們關(guān)注的行,完整的腳本在github倉(cāng)庫(kù)里面。讓我們快速過(guò)一下最重要的部分。首先,我們創(chuàng)建了一個(gè)NETLINK的socket,配置為NETLINK_ROUTE 協(xié)議,并訂閱鄰居消息組(RTMGRP_NEIGH):
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
s.bind((os.getpid(), RTMGRP_NEIGH))
然后我們解析消息,然后只處理GETNEIGH 消息:
msg_len, msg_type, flags, seq, pid = struct.unpack("=LHHLL", data[:16])
# We fundamentally only care about GETNEIGH messages
if msg_type != RTM_GETNEIGH:
continue
為了理解消息是如何解析的,下面是消息包結(jié)構(gòu)的表示。Netlink頭部用橙色標(biāo)示。當(dāng)我們有一個(gè)GETNEIGH 消息的實(shí)時(shí),我們可以解析ndmsg頭(藍(lán)色部分)。

ndm_family, _, _, ndm_ifindex, ndm_state, ndm_flags, ndm_type = struct.unpack("=BBHiHBB", data[:12])
這個(gè)頭部后面是rtattr結(jié)構(gòu),這部分包含我們感興趣的信息。首先,我們解析這部分的頭部(紫色):
rta_len, rta_type = struct.unpack("=HH", data[:4])
我們可以獲取兩類(lèi)信息:
- NDA_DST:L3 miss,內(nèi)核在查找ip地址(rta頭部后面的四個(gè)字節(jié))關(guān)聯(lián)的mac地址。
- NDA_LLADDR:L2 miss,內(nèi)核在查找mac地址(rta頭部后面的6個(gè)字節(jié))所在的vxlan的主機(jī)
data=data[4:]
if nda_type.get(rta_type,rta_type) == "NDA_DST":
dst=socket.inet_ntoa(data[:4])
logging.info("L3Miss: Who has IP: {}?".format(dst))
if nda_type.get(rta_type,rta_type) == "NDA_LLADDR":
mac="%02x:%02x:%02x:%02x:%02x:%02x" % struct.unpack("BBBBBB",data[:6])
logging.info("L2Miss: Who has MAC: {}?".format(mac))
我們可以在overlay上面試一下這個(gè)腳本(或者在一個(gè)干凈的環(huán)境中創(chuàng)建所有內(nèi)容)
docker0:~$ docker rm -f $(docker ps -aq)
docker0:~$ ./create-overlay.sh
docker0:~$ docker run -d --net=none --name=demo debian sleep 3600
docker0:~$ ./attach-ctn.sh demo 2
docker0:~$ sudo ip netns exec overns python/l2l3miss.py
如果我們?cè)诹硪粋€(gè)窗口ping:
docker0:~$ docker exec -it demo ping 192.168.0.3
下面是我們獲取的輸出信息:
INFO:root:L3Miss: Who has IP: 192.168.0.3?
如果我們添加鄰居信息并再次執(zhí)行ping
docker0:~$ sudo ip netns exec overns ip neighbor add 192.168.0.3 lladdr 02:42:c0:a8:00:03 dev vxlan1
docker0:~$ docker exec -it demo ping 192.168.0.3
現(xiàn)在我們得到了一個(gè)L2 miss,因?yàn)槲覀円呀?jīng)添加了L3信息。
INFO:root:L2Miss: Who has MAC: 02:42:c0:a8:00:03?
利用Consul來(lái)動(dòng)態(tài)發(fā)現(xiàn)
現(xiàn)在我們可以利用python腳本來(lái)獲取L2和L3 miss的消息。我們將L2和L3的所有信息存到Consul里面并且當(dāng)我們捕獲鄰居事件時(shí)候,將添加記錄到overlay的網(wǎng)絡(luò)命名空間里面。
首先,我們添加記錄到Consul里面,我們可以利用WEB界面或在curl來(lái)完成:
docker0:$ curl -X PUT -d '02:42:c0:a8:00:02' http://consul:8500/v1/kv/demo/arp/192.168.0.2
docker0:$ curl -X PUT -d '02:42:c0:a8:00:03' http://consul:8500/v1/kv/demo/arp/192.168.0.3
docker0:$ curl -X PUT -d '10.0.0.10' http://consul:8500/v1/kv/demo/fib/02:42:c0:a8:00:02
docker0:$ curl -X PUT -d '10.0.0.11' http://consul:8500/v1/kv/demo/fib/02:42:c0:a8:00:03
我們創(chuàng)建了兩類(lèi)信息:
- ARP: 使用key為 demo/arp/{IP address},對(duì)應(yīng)的value為MAC地址
-
FIB: 使用key為 demo/arp/{MAC address},對(duì)應(yīng)的value為MAC地址所在的宿主機(jī)。
在web頁(yè)面,我們可以看到ARP的信息:
web interface
當(dāng)我們接收到了GETNEIGH 事件的時(shí)候,我們?cè)贑onsul中尋找信息并操作ARP表和FIB表。以下是(簡(jiǎn)化過(guò)的)python腳本來(lái)實(shí)現(xiàn)這些:
from pyroute2 import NetNS
vxlan_ns="overns"
consul_host="consul"
consul_prefix="demo"
ipr = NetNS(vxlan_ns)
ipr.bind()
c=consul.Consul(host=consul_host,port=8500)
while True:
msg=ipr.get()
for m in msg:
if m['event'] != 'RTM_GETNEIGH':
continue
ifindex=m['ifindex']
ifname=ipr.get_links(ifindex)[0].get_attr("IFLA_IFNAME")
if m.get_attr("NDA_DST") is not None:
ipaddr=m.get_attr("NDA_DST")
logging.info("L3Miss on {}: Who has IP: {}?".format(ifname,ipaddr))
(idx,answer)=c.kv.get(consul_prefix+"/arp/"+ipaddr)
if answer is not None:
mac_addr=answer["Value"]
logging.info("Populating ARP table from Consul: IP {} is {}".format(ipaddr,mac_addr))
try:
ipr.neigh('add', dst=ipaddr, lladdr=mac_addr, ifindex=ifindex, state=ndmsg.states['permanent'])
except NetlinkError as (code,message):
print(message)
if m.get_attr("NDA_LLADDR") is not None:
lladdr=m.get_attr("NDA_LLADDR")
logging.info("L2Miss on {}: Who has Mac Address: {}?".format(ifname,lladdr))
(idx,answer)=c.kv.get(consul_prefix+"/fib/"+lladdr)
if answer is not None:
dst_host=answer["Value"]
logging.info("Populating FIB table from Consul: MAC {} is on host {}".format(lladdr,dst_host))
try:
ipr.fdb('add',ifindex=ifindex, lladdr=lladdr, dst=dst_host)
except NetlinkError as (code,message):
print(message)
完整版本的腳本也在前面提到的github倉(cāng)庫(kù)上面。下面簡(jiǎn)單解釋了腳本做了什么:
我們使用pyroute2庫(kù)來(lái)代替手工解析Netlink消息。這個(gè)庫(kù)將會(huì)解析Netlink消息并且可以使用它來(lái)發(fā)送Netlink消息去配置ARP/FIB記錄。同時(shí),我們將Netlink socket綁定到overlay的命名空間中。我們可以使用ip netns命令來(lái)啟動(dòng)腳本來(lái)進(jìn)入合適的命名空間。但是我們需要訪(fǎng)問(wèn)Consul里面的數(shù)據(jù)以便獲取配置數(shù)據(jù)。為了達(dá)到這個(gè)目的,我們?cè)谥鳈C(jī)的命名空間中運(yùn)行腳本,在腳本中綁定Netlink socket到overlay的命名空間中。
from pyroute2 import NetNS
ipr = NetNS(vxlan_ns)
ipr.bind()
c=consul.Consul(host=consul_host,port=8500)
我們監(jiān)聽(tīng)GETNEIGH 事件:
while True:
msg=ipr.get()
for m in msg:
if m['event'] != 'RTM_GETNEIGH':
continue
我們獲取網(wǎng)卡的index和名稱(chēng)(為了記錄日志用)
ifindex=m['ifindex']
ifname=ipr.get_links(ifindex)[0].get_attr("IFLA_IFNAME")
現(xiàn)在,如果收到L3 miss,我們將從Netlink消息中獲取IP地址信息,然后去Consul中查找相關(guān)ARP記錄。如果我們發(fā)現(xiàn)了記錄,我們通過(guò)Netlink消息發(fā)送相關(guān)信息到內(nèi)核去添加鄰居記錄到overlay命名空間里面去。
if m.get_attr("NDA_DST") is not None:
ipaddr=m.get_attr("NDA_DST")
logging.info("L3Miss on {}: Who has IP: {}?".format(ifname,ipaddr))
(idx,answer)=c.kv.get(consul_prefix+"/arp/"+ipaddr)
if answer is not None:
mac_addr=answer["Value"]
logging.info("Populating ARP table from Consul: IP {} is {}".format(ipaddr,mac_addr))
try:
ipr.neigh('add', dst=ipaddr, lladdr=mac_addr, ifindex=ifindex, state=ndmsg.states['permanent'])
except NetlinkError as (code,message):
print(message)
如果收到L2 miss消息,我們隊(duì)FIB數(shù)據(jù)進(jìn)行相同的操作。
現(xiàn)在我們?cè)囈幌逻@個(gè)腳本。首先,我們清空所有當(dāng)前創(chuàng)建的內(nèi)容,然后重新創(chuàng)建overlay命名空間和容器。
docker0:~$ docker rm -f $(docker ps -aq)
docker0:~$ ./create-overlay.sh
docker0:~$ docker run -d --net=none --name=demo debian sleep 3600
docker0:~$ ./attach-ctn.sh demo 2
docker1:~$ docker rm -f $(docker ps -aq)
docker1:~$ ./create-overlay.sh
docker1:~$ docker run -d --net=none --name=demo debian sleep 3600
docker1:~$ ./attach-ctn.sh demo 3
如果我們嘗試從docker0 ping docker1,這是不通的,是因?yàn)槲覀冞€沒(méi)有ARP/FIB數(shù)據(jù)。
docker0:~$ docker exec -it demo ping -c 4 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
92 bytes from 192.168.0.2: Destination Host Unreachable
92 bytes from 192.168.0.2: Destination Host Unreachable
92 bytes from 192.168.0.2: Destination Host Unreachable
92 bytes from 192.168.0.2: Destination Host Unreachable
--- 192.168.0.3 ping statistics ---
4 packets transmitted, 0 packets received, 100% packet loss
我們現(xiàn)在在兩臺(tái)機(jī)器上都啟動(dòng)啟動(dòng)我們的腳本:
docker0:~$ sudo python/arpd-consul.py
docker1:~$ sudo python/arpd-consul.py
然后在執(zhí)行ping(從docker0上的另一個(gè)窗口)
docker0:~$ docker exec -it demo ping -c 4 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=999.730 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.453 ms
下面是我們?cè)赿ocker0上的python腳本的輸出:
INFO Starting new HTTP connection (1): consul
INFO L3Miss on vxlan1: Who has IP: 192.168.0.3?
INFO Populating ARP table from Consul: IP 192.168.0.3 is 02:42:c0:a8:00:03
INFO L2Miss on vxlan1: Who has Mac Address: 02:42:c0:a8:00:03?
INFO Populating FIB table from Consul: MAC 02:42:c0:a8:00:03 is on host 10.0.0.11
INFO L2Miss on vxlan1: Who has Mac Address: 02:42:c0:a8:00:03?
INFO Populating FIB table from Consul: MAC 02:42:c0:a8:00:03 is on host 10.0.0.11
首先,我們收到了一個(gè)L3 miss(沒(méi)有192.168.0.3 的ARP數(shù)據(jù)),我們?nèi)onsul中查詢(xún)相應(yīng)的MAC地址并操作鄰居表。然后我們收到了L2 miss(沒(méi)有02:42:c0:a8:00:03的FIB信息),我們根據(jù)MAC地址去Consul里面查詢(xún)并操作轉(zhuǎn)發(fā)表。
在docker1上面,我們看到相似的輸出,但是我們只收到了L3 miss,因?yàn)長(zhǎng)2 轉(zhuǎn)發(fā)信息已經(jīng)在收到ICMP請(qǐng)求包的時(shí)候就被overlay命名空間學(xué)習(xí)到了。
下面是我們創(chuàng)建的內(nèi)容的概覽:

結(jié)論
到這里為止關(guān)于Docker overlay網(wǎng)絡(luò)的三部分內(nèi)容就結(jié)束了。如果你發(fā)現(xiàn)了一些錯(cuò)誤或者不準(zhǔn)確的地方,或者你覺(jué)得部分內(nèi)容不是很清楚。歡迎聯(lián)系我(比如通過(guò)twitter)。我將盡最大努力來(lái)盡快修訂這些內(nèi)容。
