Fannel纯三层路由的方式实现不同宿主机间的容器通信,这种方式工作的前提是宿主机之间是二层连通的。需要注意的是,宿主机之间二层不连通的情况也是广泛存在的,这就不得不提到calico网络方案了。
下面就开始讲述calico网络方案的场景。先了解下calico的几个功能模块:
Calico项目,实际上是将集群里的所有宿主机,都当作边界路由器,它们组成了一个全连通的网络BGP Mesh,互相之间通过BGP协议交换路由规则,这些节点成为BGP Peer。
1. 宿主机二层互通场景
如图Figure -- 1所示,calico插件与fannel的host-gw模式的另一个不同之处就是,它不会在宿主机上创建类似docker0、cni0这样的网桥设备,Veth Pair在宿主机的一端的接口直接暴露在宿主机上,并通过设置路由规则,将容器IP暴露到宿主机的通信路由上。
Figue -- 1
分析下IP包转发的流程:
1)节点1通过Veth Pair在宿主机一侧的端口收到“原始IP”包,源IP地址是10.233.71.10,目的IP是10.233.75.10。容器1是根据怎么样的路由规则转发的呢?来看看。在容器1上,执行下面的命令:
#ip addr show
3: eth0@if29:
link/ether ae:55:33:72:03:8a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.233.71.10/32 scope global eth0 // 单点网络
valid_lft forever preferred_lft forever
# ip route // 缺省网关169.254.1.1,查看整个拓扑没有一个设备是这个地址,怎么回事?
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
#ip neigh // 查看arp,eth0接口居然学习到有对应IP169.254.1.1的记录
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee STALE
在宿主机上 // 宿主机上veth pair一端设备mac地址:ee:ee:ee:ee:ee:ee
#ip link show cali98bd2961a02
29: cali98bd2961a02@if3:
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 24
2)宿主机网络栈就会根据路由规则找到下一跳发送“原始IP包”,处理过程与fannel host-gw模式完全一致了。
#ip route
10.233.75.0/26 via 192.168.205.31 dev eth0
3)到达节点2的“原始IP包”,宿主机网络栈查询路由,发送报文到容器2。
#ip route
10.233.75.10 dev cali56ba2161c33scope link
即发往10.233.75.10的IP包,应该进入cali56ba2161c33,Veth Pair在容器的对端设备即容器的网卡收到报文。
“原始IP包”的封装转发过程如图Figure -- 2所示:
Figure -- 2
总结看一下,容器的IP地址是个单点网络,其通过一条特殊的默认路由(gw:169.254.1.1)发送“原始IP包”到Veth pair在宿主机一端的设备;然后就由内核完成路由查找,IP包发送。这些过程无需用户软件参与,都由操作系统完成。
2. 宿主机三层互通场景
上一节为大家讲了calico插件在宿主机二层可达的情况下,容器间IP报文是如何封装转发的。刚开始就提到了,在容器云场景,宿主机之间跨网段,二层不可达的情况是广泛存在的(例如:openstack云平台下,同一租户的不同网络。或者物理VLAN隔离的网络),“原始IP包”不能通过“第一跳”就到达“目的宿主机”,“源宿主机”和“目的宿主机”之间隔着不同的VLAN网络,需要经过中间的n个路由器转发。
有人会问,“原始IP包”就这样通过中间的n个路由器转发就可以到达“目的容器”了吗?答案是“非也”。因为这些中间的路由器,它们不具备BGP协议的学习能力,学习不到calico网络里的路由规则,因此并不知道“目的容器”在哪个宿主机上。这种情况下,“源IP包”,经过N次缺省路由转发,当IP包的ttl等于0,这个“源IP包”就被丢弃了。
怎么办呢?Fannel采用UDP封装软件转发、或者VXLAN封装路由两种方式,Calico具备先天的路由自学习能力优势,学习到了“目的容器”在哪个宿主机上后,在两台宿主机之间建立ip tunnel,通过这个tunnel来封装承载“原始IP包”,即:IPinIP的方式。
Figure -- 3
如图figure -- 3所示,讲述下宿主机不在同一网络的情形,即节点1需要通过下一跳转发,才能把IP包发送到目的地。在这种情况下,就需要打开calico的IPIP模式。对比figure -- 1,IPIP模式,多了一个tunl0设备,是一个IP隧道(IP tunnel)设备。宿主机网络栈从Veth pair收到“原始IP包”,查询路由后,就被linux内核的IPIP驱动就接管,“原始IP包”直接被封装成新IP的payload,宿主机1和宿主机2三层是互通的,经过中间的n跳,最终跳到节点2。
#ip route
10.233.75.0/26 via 192.168.206.31 dev tunl0
这条路由规则表示,发往10.233.75.0网络的下一跳是192.168.206.31,负责将这个包发出去的设备是tunl0。
下面,看一下“原始IP包”的封装转发过程如图Figure -- 4所示:
Figure -- 4
“原始IP包”经过ip tunnel的封装,经过宿主机之间的中间路由器的一跳,一跳转发,到达目的宿主机,目的宿主机发现这个包是IPinIP,解封装后的“原始IP包”,由内核网络栈查询路由,发送到目的容器。
可以看到,宿主机节点之间传输的是IPinIP的报文,内层IP即容器的“原始IP包”,外层IP就是IP tunnel这个设备添加的,源IP即源宿主机的IP,目的IP即目的宿主机的IP。Figure -- 5这个通过tcpdump抓包的结果也可以看出上面的分析流程。
Figure -- 5