Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)

概述

终于到了最后一部分了

原文地址

原文地址
作者:LAURENT BERNAILLE

介绍

在这篇文章的第一部分,我们已经知道了Docker创建了一个专用的网络命名空间给overlay网络,所有上面的容器都连接到这个命名空间。在第二部分,我们详细的了解了Docker如何利用VXLAN技术在宿主机之间利用通道进行overlay的通信。在这第三部分,我们可以了解到如何利用标准的Linux命令来创建我们自己的overlay网络。

手工创建overlay网络

如果你在环境中执行过前面两部分中介绍的命令,你需要通过删除所有的容器和overlay网络来清空你的Docker宿主机:

docker0:~$ docker rm -f $(docker ps -aq)
docker0:~$ docker network rm demonet

docker1:~$ docker rm -f $(docker ps -aq)

接下来,你需要做的是创建一个命名为“overns”的网络命名空间:

sudo ip netns add overns

现在,我们再在这个命名空间里面创建一个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

接下来创建一个VXLAN的网卡,然后附在上面的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

到目前为止,最重要的命令是创建VXLAN网卡。我们配置这张网卡的id为42,通道端口为标准的VXLAN端口。proxy选项允许vxlan网卡响应ARP请求(在第二部分看到的那样)。我们将会在后面讨论learning选项。注意,我们没有在overlay的命名空间内创建VXLAN网卡,而是在主机上面创建网卡,然后移到命名空间中。这个是必须的,这样VXLAN网卡可以保持到主机的网卡链接,从而可以通过网络来发送网络包。如果我们直接在命名空间中创建这张网卡(像创建br0那样),那么我们将不能讲网络包发送到命名空间外面去。
一旦,我们在docker0和docker1上面执行这些命令以后,我们将拥有如下网络图:


Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)_第1张图片
网络图

现在,我们将创建容器并且将他们连接到bridge上面去。我们从docker0上面开始。首先,我们创建一个容器:

docker0:~$ docker run -d --net=none --name=demo debian sleep 3600

我们将需要这个容器的网络命名空间路径。我们可以通过inspect这个容器来发现。

docker0:~$ ctn_ns_path=$(docker inspect --format="{{ .NetworkSettings.SandboxKey}}" demo)

由于--net=none选项,我们的容器没有网络连接。我们现在创建一对veth网卡,将其中一端(veth1)移到我们的overlay网络命名空间里去,附在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来创建网卡,这个是必须要的,是因为VXLAN标准头部添加的开销。

接下来的步骤是设置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中创建符号链接是必须的,这样我们就可以使用本地的ip netns命令(将网卡移动到容器网络命名空间中)。我们使用和Docker相同的地址命名方式:MAC地址的最后4个字节和容器的IP地址一致,第二个字节是VXLAN id。

我们需要在docker1上面重复相同的命令,但是使用不同的MAC地址和IP地址(02:42:c0:a8:00:03 and 192.168.0.3)。如果你使用来自仓库的terraform stack,里面有个帮助脚本将容器附到overlay网络上。我们可以子啊docker1上面使用:

docker1:~$ docker run -d --net=none --name=demo debian sleep 3600
docker1:~$ ./attach-ctn.sh demo 3

第一个参数是容器的名称,第二个参数是MAC/IP地址的最后的数字。
下面是我们的当前配置

Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)_第2张图片
网络图

现在我们的容器配置好了,我们可以测试连通性:

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

我们现在还不能ping通。让我们利用在容器和overlay网络命名空间中查看ARP表记录来理解一下为什么:

docker0:~$ docker exec demo ip neighbor show
docker0:~$ sudo ip netns exec overns ip neighbor show

两个命令都没有返回任何信息:他们并不知道哪个MAC地址是关联到IP 192.168.0.3上的。我们可以通过创建ARP请求并且在overlay网络上面的利用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网络命名空间收到,但是没有收到任何应答。我们在第二部分已经知道Docker守护程序来操作ARP表和FDB表,并且通过VXLAN网卡的proxy选项来应答ARP请求。我们配置利用相同的选项配置了我们的网卡,所以我们可以通过简单的操作overlay网络命名空间的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创建了一条ARP记录,第二个命令设置转发表,设置了MAC地址是可以通过VXLAN网卡来连通的,其中VXLAN id为42,主机是10.0.0.11.

现在可以连通了吗?

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上面的容器接收到了,但是它不知如何去应答。我们可以在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上,这跟预期的一致。第二个命令令人惊讶的,因为我们可以看到转发表里面有关于docker0上面的容器的信息。这个是如下导致的:当ICMP请求到达网卡,这些记录是被学习到的并记录到转发表中。这个行为是通过VXLAN网卡的“learning”选项来达到的。让我们添加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

我现在成功通过标准的Linux命令来创建了一个overlay网络:


Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)_第3张图片
网络结构

动态容器发现

我们刚刚从头开始创建了一个overlay网络。然而,我们需要手工为容器创建ARP和FDB记录为了他们之间能够相互连通。我们现在来看如何将这个发现过程自动化。

我们先清空从头创建的内容

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

捕获网络事件:NETLINK

NETLINK用来在内核和用户空间来传递信息:https://en.wikipedia.org/wiki/Netlink。
iproute2,我们以前用来配置网卡,依赖Netlink获取/发送配置信息给内核。它由多种协议组成以便和不同的内核组件进行通信。这当中最常见的协议就是NETLINK_ROUTE,它是用来配置路由和链接的接口。

对于每种协议,Netlink消息是按组来组织的,例如拿NETLINK_ROUTE 来说:

  • RTMGRP_LINK:链接相关的消息
  • RTMGRP_NEIGH: 邻居相关的消息
  • 其他

对于每个组,有多个通知,如:

  • RTMGRP_LINK:
    • RTM_NEWLINK:一个链接被创建
    • RTM_DELLINK:一个链接被删除
  • RTMGRP_NEIGH:
    • RTM_NEWNEIGH:一个邻居被添加
    • RTM_DELNEIGH:一个邻居被删除
    • RTM_GETNEIGH:内核在查找邻居

我描述了发生这些事件时内核发送给用户空间的消息通知,但是相同的消息可以发送给内核来配置链接和邻居。
iproute2允许我们利用monitor子命令来监听Netlink事件。通过如下命令,我们可以监听实例链接信息:

docker0:~$ ip monitor link

在另一个docker0的窗口上,我们可以创建和删除链接:

docker0:~$ sudo ip link add dev veth1 type veth peer name veth2
docker0:~$ sudo ip link del veth1

在第一个窗口,我们可以看到一些输出。

当我们创建网卡的时候:

32: veth2:  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:  mtu 1500 qdisc noop state DOWN group default
    link/ether a6:e0:7a:da:a9:ea brd ff:ff:ff:ff:ff:ff

当我们删除网卡的时候:

Deleted 33: veth1:  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:  mtu 1500 qdisc noop state DOWN group default
    link/ether b6:95:d6:b4:21:e9 brd ff:ff:ff:ff:ff:ff

我们可以用这个命令来监听其他的事件:

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

在我们的场景中,我们关注邻居事件,特别是RTM_GETNEIGH。这个事件是在内核没有邻居信息的时候产生的并发送这个通知给用户空间,然后应用可以创建它。默认情况下,这个事件时不会发送给用户空间的,但是我们可以启用它并监听邻居事件通知:

docker0:~$ echo 1 | sudo tee -a /proc/sys/net/ipv4/neigh/eth0/app_solicit
docker0:~$ ip monitor neigh

之后不需要此设置,因为我们的vxlan网卡的l2miss和l3miss选项将生成RTM_GETNEIGH事件。

在第二个窗口,我们可以触发GETNEIGH 事件的创建:

docker0:~$ ping 10.0.0.100

下面是我们获取的输出:

10.0.0.100 dev eth0  FAILED
miss 10.0.0.100 dev eth0  INCOMPLETE

我们可以在overlay网络上的容器里面使用相同的命令。我们先创建一个overlay网络并附加一个容器在上面:

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 脚本利用前面介绍的命令创建了一个overlay网络叫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网络上。第一个参数是容器的名称,第二个参数是IP地址的最后一个字节:

#!/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

我们现在可以在容器里面运行ip monitor:

docker0:~$ docker exec demo ip monitor neigh

在第二窗口里面,我们可以ping一个未知的主机去创建一个GETNEIGH 事件:

docker0:~$ docker exec demo ping 192.168.0.3

在第一个窗口,我们可以看到一个邻居事件:

192.168.0.3 dev eth0  FAILED

我们也可以查看overlay的网络命名空间:

docker0:~$ sudo ip netns exec overns ip monitor neigh
miss 192.168.0.3 dev vxlan1  STALE

这个事件有一点不同,因为这个是被vxlan网卡创建的(因为我们利用l2miss选项和l3miss选项来创建网卡)。让我们添加邻居信息到overlay网络命名空间:

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命令并且在另一个窗口执行ping,下面我们可以看到:

docker0:~$ sudo ip netns exec overns ip monitor neigh
miss dev vxlan1 lladdr 02:42:c0:a8:00:03 STALE

现在我们有了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

现在我们再执行ip monitor neigh 命令并执行ping命令,将不会再看到邻居事件出现了。
在我们需要获取事件去操作2层和3层网络信息方面,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))

这个脚本只包含我们关注的行,完整的脚本在github仓库里面。让我们快速过一下最重要的部分。首先,我们创建了一个NETLINK的socket,配置为NETLINK_ROUTE 协议,并订阅邻居消息组(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

为了理解消息是如何解析的,下面是消息包结构的表示。Netlink头部用橙色标示。当我们有一个GETNEIGH 消息的实时,我们可以解析ndmsg头(蓝色部分)。


Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)_第4张图片
包信息
ndm_family, _, _, ndm_ifindex, ndm_state, ndm_flags, ndm_type = struct.unpack("=BBHiHBB", data[:12])

这个头部后面是rtattr结构,这部分包含我们感兴趣的信息。首先,我们解析这部分的头部(紫色):

rta_len, rta_type = struct.unpack("=HH", data[:4])

我们可以获取两类信息:

  • NDA_DST:L3 miss,内核在查找ip地址(rta头部后面的四个字节)关联的mac地址。
  • NDA_LLADDR:L2 miss,内核在查找mac地址(rta头部后面的6个字节)所在的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上面试一下这个脚本(或者在一个干净的环境中创建所有内容)

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?

如果我们添加邻居信息并再次执行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

现在我们得到了一个L2 miss,因为我们已经添加了L3信息。

INFO:root:L2Miss: Who has MAC: 02:42:c0:a8:00:03?

利用Consul来动态发现

现在我们可以利用python脚本来获取L2和L3 miss的消息。我们将L2和L3的所有信息存到Consul里面并且当我们捕获邻居事件时候,将添加记录到overlay的网络命名空间里面。
首先,我们添加记录到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

我们创建了两类信息:

  • ARP: 使用key为 demo/arp/{IP address},对应的value为MAC地址
  • FIB: 使用key为 demo/arp/{MAC address},对应的value为MAC地址所在的宿主机。
    在web页面,我们可以看到ARP的信息:


    Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)_第5张图片
    web interface

当我们接收到了GETNEIGH 事件的时候,我们在Consul中寻找信息并操作ARP表和FIB表。以下是(简化过的)python脚本来实现这些:

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消息并且可以使用它来发送Netlink消息去配置ARP/FIB记录。同时,我们将Netlink socket绑定到overlay的命名空间中。我们可以使用ip netns命令来启动脚本来进入合适的命名空间。但是我们需要访问Consul里面的数据以便获取配置数据。为了达到这个目的,我们在主机的命名空间中运行脚本,在脚本中绑定Netlink socket到overlay的命名空间中。

from pyroute2 import NetNS
ipr = NetNS(vxlan_ns)
ipr.bind()

c=consul.Consul(host=consul_host,port=8500)

我们监听GETNEIGH 事件:

while True:
  msg=ipr.get()
  for m in msg:
    if m['event'] != 'RTM_GETNEIGH':
      continue

我们获取网卡的index和名称(为了记录日志用)

    ifindex=m['ifindex']
    ifname=ipr.get_links(ifindex)[0].get_attr("IFLA_IFNAME")

现在,如果收到L3 miss,我们将从Netlink消息中获取IP地址信息,然后去Consul中查找相关ARP记录。如果我们发现了记录,我们通过Netlink消息发送相关信息到内核去添加邻居记录到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数据进行相同的操作。
现在我们试一下这个脚本。首先,我们清空所有当前创建的内容,然后重新创建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数据。

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

我们现在在两台机器上都启动启动我们的脚本:

docker0:~$ sudo python/arpd-consul.py

docker1:~$ sudo python/arpd-consul.py

然后在执行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数据),我们去Consul中查询相应的MAC地址并操作邻居表。然后我们收到了L2 miss(没有02:42:c0:a8:00:03的FIB信息),我们根据MAC地址去Consul里面查询并操作转发表。
在docker1上面,我们看到相似的输出,但是我们只收到了L3 miss,因为L2 转发信息已经在收到ICMP请求包的时候就被overlay命名空间学习到了。

下面是我们创建的内容的概览:


Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3)_第6张图片
结构图

结论

到这里为止关于Docker overlay网络的三部分内容就结束了。如果你发现了一些错误或者不准确的地方,或者你觉得部分内容不是很清楚。欢迎联系我(比如通过twitter)。我将尽最大努力来尽快修订这些内容。

你可能感兴趣的:(Deep dive 3 into Docker Overlay networks – Part 3(深入理解Docker的Overlay网络 3))