容器组网大致有两种方案:underlayoverlayunderlay 虽然性能较好,但是往往与底层网络耦合较重,使用范围比较受限,而 overlay 虽然性能可能差一点点,但是不受底层网络限制,应用范围更广。在 overlay 方案中,最常见的就是基于 VxLAN 的方案,今天我们就以 native Linux 为起点,不使用任何内核以外的工具,来徒手构建一个 VxLAN 容器网络,最终效果如图:

VxLAN 模式下,我们通过路由将原始报文转发到 VxLAN 网卡,所以首先需要在 Node 上开启转发功能:

echo 1 > /proc/sys/net/ipv4/ip_forward

然后进行网络配置,在 192.168.64.4 上:

# set up vxlan on host
ip link add vxlan.nic type vxlan id 4096 dstport 4789 dev enp0s1 nolearning
ip addr add 172.18.1.1/32 dev vxlan.nic
ip link set dev vxlan.nic address 00:1A:2B:3C:4D:5E  
ip link set vxlan.nic up

这里设置了:

  • VxLAN 网卡的 IP 地址和 MAC 地址
  • 绑定了宿主机网卡 enp0s1(也就是 Node 之间本身就连通的网卡,或者说 underlay 网卡)
  • nolearning 说明不需要学习(泛洪、组播、广播等手段),而是手动维护转发配置
  • VxLAN id 确定为 4096VxLAN 协议一般使用端口 4789

接下来:

# for container
ip netns add nsctr
ip link add veth0 type veth peer name veth1
ip link set dev veth0 netns nsctr
# host-side veth
ip link set dev veth1 mtu 1450
ip link set dev veth1 address ee:ee:ee:ee:ee:ee 
ip link set dev veth1 up
# container-side veth
ip netns exec nsctr ip link set lo up
ip netns exec nsctr ip link set veth0 name eth0
ip netns exec nsctr ip addr add 172.18.1.2/32 dev eth0
ip netns exec nsctr ip link set dev eth0 mtu 1450
ip netns exec nsctr ip link set dev eth0 address 08:00:27:89:AB:CD
ip netns exec nsctr ip link set eth0 up

这里设置了容器和宿主机之间的 pair 关系。

然后给容器增加路由:

# add default rule
ip netns exec nsctr ip route add 169.254.1.1 dev eth0 scope link
ip netns exec nsctr ip route add default via 169.254.1.1 dev eth0
# check
ip netns exec nsctr ip r

搭配上 veth1proxy_arp 设置: echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp,使其成为容器的默认网关。也就是说,当容器访问任何 IP 时,其下一跳会通过 veth0 走到 veth1,而 veth1 会用自己的 MAC 地址响应 arp,从而接管了容器的所有流量。veth1 接到流量后怎么处理呢?请继续看。

Node 192.168.64.5 上,将虚拟 IP 地址和 MAC 地址换成对应值,vxlan.nic: 172.18.1.129/3252:54:00:12:34:56, veth0: 172.18.1.130/32AA:BB:CC:DD:EE:FF。然后也运行上述脚本,搭建相同的环境。

然后,在两个节点各自配置邻居表:

# 192.168.64.4
ip neigh add 172.18.1.129 dev vxlan.nic lladdr 52:54:00:12:34:56 
# 192.168.64.5
ip neigh add 172.18.1.1 dev vxlan.nic lladdr 00:1A:2B:3C:4D:5E 

这样,两个节点就能互相知道对方 VxLAN 网卡的 IP <--> MAC 映射关系了。

然后,在两个节点各自配置两条路由:

# 192.168.64.4
ip route add 172.18.1.128/25 via 172.18.1.129 dev vxlan.nic onlink
ip route add 172.18.1.2 dev veth1
# 192.168.64.5
ip route add 172.18.1.0/25 via 172.18.1.1 dev vxlan.nic onlink
ip route add 172.18.1.130 dev veth1

第一条是 egress 流量,说明该网段的流量要通过 VxLAN 网卡发给 172.18.1.129 这个节点(也就是 192.168.64.5 对应的 VxLAN 网卡)
第二条是 ingress 流量,说明目的地为 172.18.1.2 的流量直接给到 veth1,也就是给到本地容器。

这也就回答了之前卖的关子,当 veth1 接到报文后,是通过 IP 路由来处理报文的,这也是为啥 veth1MAC 地址可以写成无关紧要的 ee:ee:ee:ee:ee:ee
当报文的目的 IP 地址属于对端节点,如 172.18.1.130 时,报文会被引导到 vxlan.nic 上去,由内核 VxLAN 进行处理,并且下一跳也确定了是 172.18.1.129,下一跳的 MAC 地址也可以从邻居表知道了,是 52:54:00:12:34:56,接下来怎么处理呢?且看下文。

我们继续在两个节点上配置,以两个节点 vxlan.nicMAC 地址,分别配上对端的 VxLAN 转发表:

# 192.168.64.4
bridge fdb append 52:54:00:12:34:56 dev vxlan.nic dst 192.168.64.5
# 192.168.64.5
bridge fdb append 00:1A:2B:3C:4D:5E dev vxlan.nic dst 192.168.64.4

这样,续接上文,当 vxlan.nic 收到报文后,看到 52:54:00:12:34:56 对应 192.168.64.5,就可以利用这个 IP 以及对应的 MAC 地址完成外层报文的目的地址填充了,然后利用 underlay link 将外层报文最终发送出去。

PS:在 underlay link 进行抓包会发现,外层报文源地址是 192.168.64.4 对应的 IPMAC;内层报文 IP 是容器 IP ,而 MAC 地址却是 vxlan.nicMAC 地址。

至此,两个节点就能互相用 VxLAN 网卡的 IP ping 通了。

进一步的,进入容器测试,比如在 192.168.64.4 上面:

ip netns exec nsctr ping 172.18.1.130
PING 172.18.1.130 (172.18.1.130) 56(84) bytes of data.
64 bytes from 172.18.1.130: icmp_seq=1 ttl=62 time=1.26 ms
64 bytes from 172.18.1.130: icmp_seq=2 ttl=62 time=1.94 ms
...

可见容器网络也已通。

192.168.64.5 上面抓包可以验证上述报文分析 tcpdump -i enp0s1 -evv port 4789

顺便地,检查下邻居表:

ip netns exec nsctr ip n
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee REACHABLE 

可见 arp proxy 确实是生效了,与之前的分析互相印证。

至此,本文首图构建的容器 overlay 网络已经完成!

事实上,本文也是 Calico VxLAN 模式的原理,只是还需要把以上过程自动化罢了。


MageekChiu
4.4k 声望1.7k 粉丝

T