概述
終于到了最后一部分了
原文地址
原文地址
作者:LAURENT BERNAILLE
介紹
在這篇文章的第一部分桐磁,我們已經(jīng)知道了Docker創(chuàng)建了一個專用的網(wǎng)絡(luò)命名空間給overlay網(wǎng)絡(luò),所有上面的容器都連接到這個命名空間拆檬。在第二部分约素,我們詳細的了解了Docker如何利用VXLAN技術(shù)在宿主機之間利用通道進行overlay的通信。在這第三部分贝攒,我們可以了解到如何利用標(biāo)準(zhǔn)的Linux命令來創(chuàng)建我們自己的overlay網(wǎng)絡(luò)盗誊。
手工創(chuàng)建overlay網(wǎng)絡(luò)
如果你在環(huán)境中執(zhí)行過前面兩部分中介紹的命令,你需要通過刪除所有的容器和overlay網(wǎng)絡(luò)來清空你的Docker宿主機:
docker0:~$ docker rm -f $(docker ps -aq)
docker0:~$ docker network rm demonet
docker1:~$ docker rm -f $(docker ps -aq)
接下來隘弊,你需要做的是創(chuàng)建一個命名為“overns”的網(wǎng)絡(luò)命名空間:
sudo ip netns add overns
現(xiàn)在哈踱,我們再在這個命名空間里面創(chuàng)建一個bridge,給這個bridge一個IP地址梨熙,并啟動它:
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
接下來創(chuàng)建一個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選項允許vxlan網(wǎng)卡響應(yīng)ARP請求(在第二部分看到的那樣)质欲。我們將會在后面討論learning選項树埠。注意,我們沒有在overlay的命名空間內(nèi)創(chuàng)建VXLAN網(wǎng)卡嘶伟,而是在主機上面創(chuàng)建網(wǎng)卡怎憋,然后移到命名空間中。這個是必須的,這樣VXLAN網(wǎng)卡可以保持到主機的網(wǎng)卡鏈接绊袋,從而可以通過網(wǎng)絡(luò)來發(fā)送網(wǎng)絡(luò)包毕匀。如果我們直接在命名空間中創(chuàng)建這張網(wǎng)卡(像創(chuàng)建br0那樣),那么我們將不能講網(wǎng)絡(luò)包發(fā)送到命名空間外面去癌别。
一旦皂岔,我們在docker0和docker1上面執(zhí)行這些命令以后,我們將擁有如下網(wǎng)絡(luò)圖:
現(xiàn)在规个,我們將創(chuàng)建容器并且將他們連接到bridge上面去凤薛。我們從docker0上面開始。首先诞仓,我們創(chuàng)建一個容器:
docker0:~$ docker run -d --net=none --name=demo debian sleep 3600
我們將需要這個容器的網(wǎng)絡(luò)命名空間路徑缤苫。我們可以通過inspect這個容器來發(fā)現(xiàn)。
docker0:~$ ctn_ns_path=$(docker inspect --format="{{ .NetworkSettings.SandboxKey}}" demo)
由于--net=none選項墅拭,我們的容器沒有網(wǎng)絡(luò)連接活玲。我們現(xiàn)在創(chuàng)建一對veth網(wǎng)卡,將其中一端(veth1)移到我們的overlay網(wǎng)絡(luò)命名空間里去谍婉,附在bridge上面舒憾,然后啟動它。
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
第一個命令使用MTU為1450來創(chuàng)建網(wǎng)卡穗熬,這個是必須要的镀迂,是因為VXLAN標(biāo)準(zhǔn)頭部添加的開銷。
接下來的步驟是設(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)建符號鏈接是必須的探遵,這樣我們就可以使用本地的ip netns命令(將網(wǎng)卡移動到容器網(wǎng)絡(luò)命名空間中)。我們使用和Docker相同的地址命名方式:MAC地址的最后4個字節(jié)和容器的IP地址一致妓柜,第二個字節(jié)是VXLAN id箱季。
我們需要在docker1上面重復(fù)相同的命令,但是使用不同的MAC地址和IP地址(02:42:c0:a8:00:03 and 192.168.0.3)棍掐。如果你使用來自倉庫的terraform stack藏雏,里面有個幫助腳本將容器附到overlay網(wǎng)絡(luò)上。我們可以子啊docker1上面使用:
docker1:~$ docker run -d --net=none --name=demo debian sleep 3600
docker1:~$ ./attach-ctn.sh demo 3
第一個參數(shù)是容器的名稱作煌,第二個參數(shù)是MAC/IP地址的最后的數(shù)字掘殴。
下面是我們的當(dāng)前配置
現(xiàn)在我們的容器配置好了,我們可以測試連通性:
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表記錄來理解一下為什么:
docker0:~$ docker exec demo ip neighbor show
docker0:~$ sudo ip netns exec overns ip neighbor show
兩個命令都沒有返回任何信息:他們并不知道哪個MAC地址是關(guān)聯(lián)到IP 192.168.0.3上的奏寨。我們可以通過創(chuàng)建ARP請求并且在overlay網(wǎng)絡(luò)上面的利用tcpdump進行抓包來驗證我們的命令:
docker0:~$ sudo ip netns exec overns tcpdump -i br0
docker0:~$ tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
我們在另一個窗口重新運行ping命令,將會看到的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請求被廣播且被overlay網(wǎng)絡(luò)命名空間收到努酸,但是沒有收到任何應(yīng)答。我們在第二部分已經(jīng)知道Docker守護程序來操作ARP表和FDB表杜恰,并且通過VXLAN網(wǎng)卡的proxy選項來應(yīng)答ARP請求获诈。我們配置利用相同的選項配置了我們的網(wǎng)卡仍源,所以我們可以通過簡單的操作overlay網(wǎng)絡(luò)命名空間的ARP表和FDB表來達到相同的效果:
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
第一個命令為192.168.0.3創(chuàng)建了一條ARP記錄,第二個命令設(shè)置轉(zhuǎn)發(fā)表舔涎,設(shè)置了MAC地址是可以通過VXLAN網(wǎng)卡來連通的笼踩,其中VXLAN id為42,主機是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
還沒有嚎于,因為我們還沒有在docker1上面配置。ICMP請求被docker1上面的容器接收到了挟冠,但是它不知如何去應(yīng)答于购。我們可以在docker1上面驗證這一點:
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
第一個命令顯示,沒有任何的ARP信息在192.168.0.3上知染,這跟預(yù)期的一致肋僧。第二個命令令人驚訝的,因為我們可以看到轉(zhuǎn)發(fā)表里面有關(guān)于docker0上面的容器的信息控淡。這個是如下導(dǎo)致的:當(dāng)ICMP請求到達網(wǎng)卡嫌吠,這些記錄是被學(xué)習(xí)到的并記錄到轉(zhuǎn)發(fā)表中。這個行為是通過VXLAN網(wǎng)卡的“l(fā)earning”選項來達到的掺炭。讓我們添加ARP信息到docker1上辫诅,然后驗證一下我們可以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)在成功通過標(biāo)準(zhǔn)的Linux命令來創(chuàng)建了一個overlay網(wǎng)絡(luò):
動態(tài)容器發(fā)現(xiàn)
我們剛剛從頭開始創(chuàng)建了一個overlay網(wǎng)絡(luò)。然而涧狮,我們需要手工為容器創(chuàng)建ARP和FDB記錄為了他們之間能夠相互連通炕矮。我們現(xiàn)在來看如何將這個發(fā)現(xiàn)過程自動化。
我們先清空從頭創(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用來在內(nèi)核和用戶空間來傳遞信息:https://en.wikipedia.org/wiki/Netlink勋篓。
iproute2,我們以前用來配置網(wǎng)卡儒旬,依賴Netlink獲取/發(fā)送配置信息給內(nèi)核芬为。它由多種協(xié)議組成以便和不同的內(nèi)核組件進行通信。這當(dāng)中最常見的協(xié)議就是NETLINK_ROUTE,它是用來配置路由和鏈接的接口拴曲。
對于每種協(xié)議,Netlink消息是按組來組織的梭灿,例如拿NETLINK_ROUTE 來說:
- RTMGRP_LINK:鏈接相關(guān)的消息
- RTMGRP_NEIGH: 鄰居相關(guān)的消息
- 其他
對于每個組阴汇,有多個通知,如:
- RTMGRP_LINK:
- RTM_NEWLINK:一個鏈接被創(chuàng)建
- RTM_DELLINK:一個鏈接被刪除
- RTMGRP_NEIGH:
- RTM_NEWNEIGH:一個鄰居被添加
- RTM_DELNEIGH:一個鄰居被刪除
- RTM_GETNEIGH:內(nèi)核在查找鄰居
我描述了發(fā)生這些事件時內(nèi)核發(fā)送給用戶空間的消息通知尼桶,但是相同的消息可以發(fā)送給內(nèi)核來配置鏈接和鄰居操灿。
iproute2允許我們利用monitor子命令來監(jiān)聽Netlink事件。通過如下命令泵督,我們可以監(jiān)聽實例鏈接信息:
docker0:~$ ip monitor link
在另一個docker0的窗口上趾盐,我們可以創(chuàng)建和刪除鏈接:
docker0:~$ sudo ip link add dev veth1 type veth peer name veth2
docker0:~$ sudo ip link del veth1
在第一個窗口,我們可以看到一些輸出。
當(dāng)我們創(chuàng)建網(wǎng)卡的時候:
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)卡的時候:
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
我們可以用這個命令來監(jiān)聽其他的事件:
docker0:~$ ip monitor route
在另一個終端:
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
在我們的場景中救鲤,我們關(guān)注鄰居事件久窟,特別是RTM_GETNEIGH。這個事件是在內(nèi)核沒有鄰居信息的時候產(chǎn)生的并發(fā)送這個通知給用戶空間本缠,然后應(yīng)用可以創(chuàng)建它斥扛。默認情況下,這個事件時不會發(fā)送給用戶空間的丹锹,但是我們可以啟用它并監(jiān)聽鄰居事件通知:
docker0:~$ echo 1 | sudo tee -a /proc/sys/net/ipv4/neigh/eth0/app_solicit
docker0:~$ ip monitor neigh
之后不需要此設(shè)置稀颁,因為我們的vxlan網(wǎng)卡的l2miss和l3miss選項將生成RTM_GETNEIGH事件。
在第二個窗口楣黍,我們可以觸發(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)建一個overlay網(wǎng)絡(luò)并附加一個容器在上面:
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
這兩個腳本可以在github的倉庫中找到。
create-overlay 腳本利用前面介紹的命令創(chuàng)建了一個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腳本將一個容器附在overlay網(wǎng)絡(luò)上锡凝。第一個參數(shù)是容器的名稱粘昨,第二個參數(shù)是IP地址的最后一個字節(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)在可以在容器里面運行ip monitor:
docker0:~$ docker exec demo ip monitor neigh
在第二窗口里面,我們可以ping一個未知的主機去創(chuàng)建一個GETNEIGH 事件:
docker0:~$ docker exec demo ping 192.168.0.3
在第一個窗口窜锯,我們可以看到一個鄰居事件:
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
這個事件有一點不同张肾,因為這個是被vxlan網(wǎng)卡創(chuàng)建的(因為我們利用l2miss選項和l3miss選項來創(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
如果我們運行ip monitor neigh命令并且在另一個窗口執(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信息吞瞪, 我們得到了一個L2miss是因為我們還不知道這個mac地址是位于overlay的哪個位置。讓我們來添加這個信息:
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)在我們再執(zhí)行ip monitor neigh 命令并執(zhí)行ping命令驾孔,將不會再看到鄰居事件出現(xiàn)了芍秆。
在我們需要獲取事件去操作2層和3層網(wǎng)絡(luò)信息方面,ip monitor是非常有用的翠勉,所以我們需要編寫代碼去跟它交互妖啥。
下面是一個簡單的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))
這個腳本只包含我們關(guān)注的行,完整的腳本在github倉庫里面对碌。讓我們快速過一下最重要的部分荆虱。首先,我們創(chuàng)建了一個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)我們有一個GETNEIGH 消息的實時叁丧,我們可以解析ndmsg頭(藍色部分)啤誊。
ndm_family, _, _, ndm_ifindex, ndm_state, ndm_flags, ndm_type = struct.unpack("=BBHiHBB", data[:12])
這個頭部后面是rtattr結(jié)構(gòu)岳瞭,這部分包含我們感興趣的信息。首先蚊锹,我們解析這部分的頭部(紫色):
rta_len, rta_type = struct.unpack("=HH", data[:4])
我們可以獲取兩類信息:
- NDA_DST:L3 miss寝优,內(nèi)核在查找ip地址(rta頭部后面的四個字節(jié))關(guān)聯(lián)的mac地址。
- NDA_LLADDR:L2 miss枫耳,內(nèi)核在查找mac地址(rta頭部后面的6個字節(jié))所在的vxlan的主機
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上面試一下這個腳本(或者在一個干凈的環(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
如果我們在另一個窗口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)在我們得到了一個L2 miss,因為我們已經(jīng)添加了L3信息孟抗。
INFO:root:L2Miss: Who has MAC: 02:42:c0:a8:00:03?
利用Consul來動態(tài)發(fā)現(xiàn)
現(xiàn)在我們可以利用python腳本來獲取L2和L3 miss的消息迁杨。我們將L2和L3的所有信息存到Consul里面并且當(dāng)我們捕獲鄰居事件時候,將添加記錄到overlay的網(wǎng)絡(luò)命名空間里面凄硼。
首先铅协,我們添加記錄到Consul里面,我們可以利用WEB界面或在curl來完成:
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)建了兩類信息:
- ARP: 使用key為 demo/arp/{IP address}摊沉,對應(yīng)的value為MAC地址
-
FIB: 使用key為 demo/arp/{MAC address}狐史,對應(yīng)的value為MAC地址所在的宿主機。
在web頁面说墨,我們可以看到ARP的信息:
當(dāng)我們接收到了GETNEIGH 事件的時候骏全,我們在Consul中尋找信息并操作ARP表和FIB表。以下是(簡化過的)python腳本來實現(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倉庫上面尼斧。下面簡單解釋了腳本做了什么:
我們使用pyroute2庫來代替手工解析Netlink消息姜贡。這個庫將會解析Netlink消息并且可以使用它來發(fā)送Netlink消息去配置ARP/FIB記錄。同時棺棵,我們將Netlink socket綁定到overlay的命名空間中楼咳。我們可以使用ip netns命令來啟動腳本來進入合適的命名空間。但是我們需要訪問Consul里面的數(shù)據(jù)以便獲取配置數(shù)據(jù)烛恤。為了達到這個目的母怜,我們在主機的命名空間中運行腳本,在腳本中綁定Netlink socket到overlay的命名空間中缚柏。
from pyroute2 import NetNS
ipr = NetNS(vxlan_ns)
ipr.bind()
c=consul.Consul(host=consul_host,port=8500)
我們監(jiān)聽GETNEIGH 事件:
while True:
msg=ipr.get()
for m in msg:
if m['event'] != 'RTM_GETNEIGH':
continue
我們獲取網(wǎng)卡的index和名稱(為了記錄日志用)
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)了記錄,我們通過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消息疙挺,我們隊FIB數(shù)據(jù)進行相同的操作。
現(xiàn)在我們試一下這個腳本怜浅。首先铐然,我們清空所有當(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搀暑,這是不通的沥阳,是因為我們還沒有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)在在兩臺機器上都啟動啟動我們的腳本:
docker0:~$ sudo python/arpd-consul.py
docker1:~$ sudo python/arpd-consul.py
然后在執(zhí)行ping(從docker0上的另一個窗口)
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
下面是我們在docker0上的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
首先自点,我們收到了一個L3 miss(沒有192.168.0.3 的ARP數(shù)據(jù))桐罕,我們?nèi)onsul中查詢相應(yīng)的MAC地址并操作鄰居表。然后我們收到了L2 miss(沒有02:42:c0:a8:00:03的FIB信息)桂敛,我們根據(jù)MAC地址去Consul里面查詢并操作轉(zhuǎn)發(fā)表功炮。
在docker1上面,我們看到相似的輸出术唬,但是我們只收到了L3 miss薪伏,因為L2 轉(zhuǎn)發(fā)信息已經(jīng)在收到ICMP請求包的時候就被overlay命名空間學(xué)習(xí)到了。
下面是我們創(chuàng)建的內(nèi)容的概覽:
結(jié)論
到這里為止關(guān)于Docker overlay網(wǎng)絡(luò)的三部分內(nèi)容就結(jié)束了粗仓。如果你發(fā)現(xiàn)了一些錯誤或者不準(zhǔn)確的地方嫁怀,或者你覺得部分內(nèi)容不是很清楚。歡迎聯(lián)系我(比如通過twitter)借浊。我將盡最大努力來盡快修訂這些內(nèi)容塘淑。