Calico是一个纯三层数据中心网络解决方案,与OpenStack等IaaS云架构无缝集成,可提供VM、容器、裸机之间可控的IP通信。为什么说是纯三层呢?因为所有的数据包都是通过路由找到对应的主机和容器,然后所有的路由通过BGP协议同步到所有的机器或者数据中心,从而完成整个网络的互联互通。简单来说,Calico在宿主机上创建一堆vethpair,一个在宿主机上,一个在容器的networknamespace里,然后分别在容器和宿主机里设置几条路由,完成网络互连。1.揭秘Calico网络模型下面我们通过具体的例子来帮助大家理解Calico网络的通信原理。在k8s集群中随机选择一个节点作为实验节点,进入容器A,查看容器A的IP地址:$ipa1:lo:mtu65536qdiscnoqueuestateUNKNOWNqlen1000link/loopback00:00:00:00:00:00brd00:00:00:00:00:00inet127.0.0.1/8范围主机lovalid_lftforeverpreferred_lftforever3:eth0@if771:mtu1440qdiscnoqueue状态UPlink/ether66:fb:34:db:c9:b4brdff:ff:ff:ff:ff:ffinet172.17.8.2/32scopeglobaleth0valid_lftforeverpreferred_lftforeverobtainedby这个容器是一个/32位的主机地址,表示容器A作为单点局域网。查看容器A的默认路由:$iproutedefaultvia169.254.1.1deveth0169.254.1.1deveth0scopelink现在问题来了。从路由表中我们可以知道169.254.1.1是容器的默认网关,但是我们找不到任何一个网卡对应这个IP地址,这是什么鬼?别着急,我们先回忆一下。当一个数据包的目的地址不是本机时,就会查询路由表。目的MAC在数据包中改为网关的MAC,网关的IP地址不会出现在任何网络包头中。也就是说,IP地址是多少没人关心,只要能找到对应的MAC地址,能响应ARP即可。想到这里,我们就可以继续进行下去了。可以通过ipneigh命令查看本地ARP缓存:$ipneigh169.254.1.1deveth0lladdree:ee:ee:ee:ee:eeREACHABLE这个MAC地址应该是被Calico强行塞进来的,然后响应到ARP。但它究竟是如何工作的呢?我们先回忆一下正常情况。内核会向外界发送ARP请求,询问在整个二层网络中谁拥有IP地址169.254.1.1,拥有这个IP地址的设备会将自己的MAC地址返回给对方。但是现在的情况比较尴尬,容器和宿主机都没有这个IP地址,甚至宿主机上的端口calicba2f87f6bb,MAC地址也是一个没用的ee:ee:ee:ee:ee:ee。按理说容器和宿主机网络根本无法通信!那么Calico是如何做到的呢?这里就不绕了,其实Calico使用的是网卡的代理ARP功能。代理ARP是ARP协议的变体。当ARP请求目标跨越网段时,网关设备接收到ARP请求,并以自己的MAC地址返回给请求者。这就是代理ARP(代理ARP)。例如:上图中,电脑发送ARP请求,请求服务器MAC地址8.8.8.8。当路由器(网关)收到这个请求后,就会做出判断。此时,它将自己的接口MAC地址返回给PC。后续计算机访问服务器时,直接将目标MAC封装为MAC254。现在我们知道Calico本质上是利用proxyARP来传播“善意的谎言”,下面我们来确认一下。查看主机的网卡信息和路由信息:$ipaddr...771:calicba2f87f6bb@if4:mtu1440qdiscnoqueuestateUPgroupdefaultlink/etheree:ee:ee:ee:ee:eebrdff:ff:ff:ff:ff:fflink-netnsid14inet6fe80::ecee:eeff:feee:eeee/64范围链接valid_lft永远preferred_lft永远...$ip路由...172.17.8.2devcalicba2f87f6bbscopelink...查看proxyARP是否开启:$cat/proc/sys/net/ipv4/conf/calicba2f87f6bb/proxy_arp1不放心可以通过tcpdump抓包验证:$tcpdump-icalicba2f87f6bb-e-nntcpdump:抑制详细输出,使用-v或-vv在calicba2f87f6bb上进行完整协议解码监听,链路类型EN10MB(以太网),捕获大小262144字节14:27:13.565539??ee:ee:ee:ee:ee:ee>0a:58:ac:1c:ce:12,ethertypeIPv4(0x0800),长度4191:10.96.0.1.443>172.17.8.2.36180:Flags[P.],seq403862039:403866164,ack2023703985,win990,选项,nop,TSval331780572ecr603755526],长度412514:27:13.5656130a:58:ac:1c:ce:12>ee:ee:ee:ee:ee:ee,ethertypeIPv4(0x0800),长度66:172.17.8.2.36180>10.96.0.1.443:Flags[.],ack4125,win2465,options[nop,nop,TSval603758497ecr331780572],length0总结:Calico通过巧妙的方法将所有的工作负载流量引导到一个特殊的网关169.254.1.1,从而将流量导流到宿主机的calixxx网络设备上,最终将二三层流量全部转化为三层流量对于转发中的ARP响应,是通过在主机上开启代理ARP功能来实现的,这样在主机上ARP广播就被抑制了,抑制了广播风暴,也不会有ARP表扩容的问题。2、模拟组网既然我们已经掌握了Calico的组网原理,接下来我们就可以手动模拟验证了。结构如图所示:先在Host0上执行以下命令:$iplinkaddveth0typevethpeernameeth0$ipnetnsaddns0$iplinkseteth0netnsns0$ipnetnsexecns0ipaadd10.20.1.2/24deveth0$ipnetnsexecns0iplinkseteth0up$ipnetnsexecns0iprouteadd169.254.1.1deveth0scopelink$ipnetnsexecns0iproute添加默认值via169.254.1.1deveth0$iplinksetveth0up$iprouteadd10.20.1.2devveth0scopelink$iprouteadd10.20.1.3via192.168.1.16devens192$echo1>/proc/sys/net/ipv4/conf/veth0/proxy_arp在Host1上执行以下命令:$iplinkaddveth0类型vethpeernameeth0$ipnetnsaddns1$iplinkseteth0netnsns1$ipnetnsexecns1ipaadd10.20.1.3/24deveth0$ipnetnsexecns1iplinkseteth0up$ipnetnsexecns1iprouteadd169.254.1.1deveth0scopelink$ipnetnsexecns1iprouteadddefaultvia169.254.1.1deveth0$iplinksetveth0up$iprouteadd10.20.1.3devveth0scopelink$iproute通过192.168添加10.20.1.2。1.32开发ens192$回声1>/proc/sys/net/ipv4/conf/veth0/proxy_arp网络连接测试:#Host0$ipnetnsexecns1ping10.20.1.3PING10.20.1.3(10.20.1.3)56(84)字节数据。64字节来自10.20.1.3:icmp_seq=1ttl=62time=0.303ms64bytesfrom10.20.1.3:icmp_seq=2ttl=62time=0.334ms实验成功!具体转发过程如下:ns0网络空间的所有数据包都转发到一个虚拟IP地址169.254.1.1,Host0的veth端发送ARP请求。当收到ARP请求时,直接将自己的MAC地址返回给ns0。ns0向ns1发送一个IP数据包。因为使用了169.254.1.1这样的地址,Host判断是三层路由转发,通过192.168.1.16devens192查询本地路由10.20.1.3发送给对端Host1。如果配置了BGP,您将看到proto协议是BIRD。Host1收到10.20.1.3的数据包后,匹配本地路由表10.20.1.3devveth0scopelink,将数据包转发到对应的veth0端,从而到达ns1。回程是相似的。通过这个实验,我们可以清晰的掌握Calico网络的数据转发过程。首先,我们需要为所有的ns配置一条特殊的路由,利用veth的代理ARP功能,让所有从ns的转发都成为三层路由。转发,然后使用主机的路由进行转发。这种方式不仅实现了同一主机的二三层转发,还实现了跨主机的转发。