Docker的网络模式可以解决一个节点上的容器之间的网络通信问题,但是对于跨主机的容器之间的通信就无能为力了,就需要借助第三方的工具来实现容器的跨主机通信。为了解决容器跨主机通信问题,社区出现很多网络解决方案,不同的方案工作原理各有不同,对于网络环境要求也各不相同。常用的有flannel和calico。

k8s的容器虚拟化防落方案大体分为两种: 基于隧道方案和基于路由方案。
隧道方案:flannel的 vxlan模式、calico的ipip模式都是隧道模式。
隧道模式过程:隧道模式分为两个过程:分配网段和封包/解包两个过程。
路由方案:flannel的host-gw模式,calico的bgp模式都是路由方案。
路由过程:真个路由过程分为分配网段、广播路由两个阶段。

Flannel是CoreOS(ETCD的公司)推出的一个Overlay类型的容器网络插件,目前支持三种后端实现:UDPVXLANhost-gw。UDP是最开始支持的最简单的但是性能最差的,生产环境不会使用。

UDP方式

要使用UDP方式需要在Flannel的配置文件中指定Backend type为UDP,直接修改Flannel的方式实现:

  1. $ kubectl edit cm kube-flannel-cfg -n kube-system
  2. apiVersion: v1
  3. data:
  4. cni-conf.json: |
  5. {
  6. "cniVersion": "0.2.0",
  7. "name": "cbr0",
  8. "plugins": [
  9. {
  10. "type": "flannel",
  11. "delegate": {
  12. "hairpinMode": true,
  13. "isDefaultGateway": true
  14. }
  15. },
  16. {
  17. "type": "portmap",
  18. "capabilities": {
  19. "portMappings": true
  20. }
  21. }
  22. ]
  23. }
  24. net-conf.json: |
  25. {
  26. {
  27. "Network": "10.244.0.0/16",
  28. "Backend": {
  29. "Type": "udp" # 修改后端类型为 UDP
  30. }
  31. }
  32. kind: ConfigMap
  33. ......

采用UDP模式时后,默认端口为8285,即Flannel的监听端口。当采用该模式时,Flanneld进程在启动时会通过打开/dev/net/tun的方式生成一个TUN设备,TUN设备可以简单理解为Linux当中提供的一种内核网络与用户铜件通信的一种机制,即应用可以通过直接读写TUN设备的方式收发RAW IP包。所以需要将宿主机的/dev/net/tun文件挂载到容器中。

$ kubectl edit ds kube-flannel-ds-amd64 -n kube-system
......
  volumeMounts:
    - mountPath: /run/flannel
    name: run
    - mountPath: /etc/kube-flannel/
    name: flannel-cfg
    - mountPath: /dev/net
    name: tun
......
volumes:
- hostPath:
    path: /run/flannel
    type: ""
  name: run
- hostPath:
    path: /etc/cni/net.d
    type: ""
  name: cni
- hostPath:
    path: /dev/net  # 挂载宿主机的 /dev/net/tun 文件
    type: ""
  name: tun
......

然后Flanneld的Pod会自动重建,重建完成后,可以随便查看一个Pod日志:

$ kubectl logs -f kube-flannel-ds-amd64-5bk4dmd64 -n kube-system
I1128 08:26:49.663566       1 main.go:527] Using interface with name eth0 and address 10.151.30.11
I1128 08:26:49.663838       1 main.go:544] Defaulting external address to interface address (10.151.30.11)
I1128 08:26:49.857634       1 kube.go:126] Waiting 10m0s for node controller to sync
I1128 08:26:49.857805       1 kube.go:309] Starting kube subnet manager
I1128 08:26:50.858137       1 kube.go:133] Node controller sync successful
I1128 08:26:50.858324       1 main.go:244] Created subnet manager: Kubernetes Subnet Manager - ydzs-master
I1128 08:26:50.858357       1 main.go:247] Installing signal handlers
I1128 08:26:50.858933       1 main.go:386] Found network config - Backend type: udp
I1128 08:26:51.089114       1 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1128 08:26:51.089177       1 main.go:321] Running backend.
I1128 08:26:51.089227       1 main.go:339] Waiting for all goroutines to exit
I1128 08:26:51.089280       1 udp_network_amd64.go:100] Watching for new subnet leases
I1128 08:26:51.089350       1 udp_network_amd64.go:195] Subnet added: 10.244.4.0/24
I1128 08:26:51.089443       1 udp_network_amd64.go:195] Subnet added: 10.244.1.0/24
I1128 08:26:51.089487       1 udp_network_amd64.go:195] Subnet added: 10.244.2.0/24
I1128 08:26:51.089518       1 udp_network_amd64.go:195] Subnet added: 10.244.3.0/24
I1128 08:27:01.936553       1 udp_network_amd64.go:195] Subnet added: 10.244.3.0/24
I1128 08:27:03.341176       1 udp_network_amd64.go:195] Subnet added: 10.244.4.0/24
I1128 08:27:04.136280       1 udp_network_amd64.go:195] Subnet added: 10.244.2.0/24
I1128 08:27:07.863379       1 udp_network_amd64.go:195] Subnet added: 10.244.1.0/24

看到Found network config - Backend type: udp证明已经变成UDP模式。
Flanneld进程启动后通过ip a命令可以发现节点当中已经多了一个叫flannel0的网络设备:

$ ip -d link show flannel0
210: flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 500
    link/none  promiscuity 0
    tun addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

由于是UDP服务,需要通过netstat -lunp命令查看进程:

$ netstat -ulnp | grep flanneld
udp        0      0 10.151.30.11:8285       0.0.0.0:*                           24844/flanneld

现在有两个 Pod,分别在节点 node1 和节点node2 上面,现在让 pod-a(10.244.1.236)向 pod-b(10.244.2.123)发送一个请求报文(ping),来分析以下报文是如何从 pod-a 到达 pod-b 的:

$ kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP             NODE         NOMINATED NODE   READINESS GATES
pod-a                       1/1     Running   0          73s     10.244.1.236   node1   <none>           <none>
pod-b                       1/1     Running   0          38s     10.244.2.123   node2   <none>           <none>

1、在pod-a中发出ICMP请求报文,其源地址就是10.244.1.236,目的地址是10.244.2.123,此时通过pod-a内的路由表匹配到应该将该IP包发送到node1节点上网关10.244.1.1(cni0网桥)

$ kubectl exec pod-a -- route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.244.1.1      0.0.0.0         UG    0      0        0 eth0
10.244.0.0      10.244.1.1      255.255.0.0     UG    0      0        0 eth0
10.244.1.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0

在node1节点上可以查看到pod-a的网关地址10.244.1.1就是节点上的cni0网桥:

[root@node1 ~]# ifconfig -a
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.244.1.1  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::64c2:65ff:fe15:3669  prefixlen 64  scopeid 0x20<link>
        ether f6:f9:99:71:81:a2  txqueuelen 1000  (Ethernet)
        RX packets 33385207  bytes 24883992070 (23.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 31653673  bytes 19703556786 (18.3 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
......

2、这个时候,IP包的下一个目的地,就取决于宿主机上的路由规则,Flanneld进程已经在宿主机上创建了一系列的路由规则。

[root@node1 ~]# ip route
default via 10.151.30.11 dev eth0  proto static  metric 100
10.151.30.0/24 dev eth0  proto kernel  scope link  src 10.151.30.22  metric 100
10.244.0.0/16 dev flannel0
10.244.1.0/24 dev cni0  proto kernel  scope link  src 10.244.1.1
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1

此时到达cni0的IP包目标地址10.244.2.123匹配不到本机的cni0网桥对应的10.244.1.0/24网段,只能匹配到第三条10.244.0.0/16这条路由规则,这个时候内核将RAW IP包发送给flannel0设备。

flannel0设备它是一个TUN设备。在Linux中,TUN设备是一种工作在三层(Network Layer)的虚拟网络设备,TUN设备的功能非常简单,即在操作系统内核和用户应用程序之间传递IP包

3、由于flannel0是一个TUN设备,发送给flannel0接口的RAW IP包将被Flanneld进程接收到,然后在原有的基础上进行UDP封包,然后发送到node2节点上的Flanneld进行接包。

UDP封包的形式为:10.151.30.22:src port -> 10.151.30.23:8285

这里最关键的是UDP封包发送到目标10.244.2.123容器所在的节点。这时候需要了解一下子网(Subnet),Flannel管理的容器网络,一台宿主机上所有的容器都属于该宿主机被分配的一个子网,比如node1节点的子网是10.244.1.0/24,pod-a的IP地址就是10.244.1.236;node2节点的子网是10.244.2.0/24,pod-b的IP地址是10.244.2.123,这些子网信息是当Flanneldjin’c在启动时通过api-server保存到etcd当中,所以在发送报文时可以通过目的地址10.244.2.123匹配到对应的子网是10.244.2.0/24,这时候查询etcd得到这个子网对应的宿主机的IP地址10.151.30.23,也就是node2节点。

4、node2节点收到UDP报文过后经过Linux内核通过UDP端口8285将包交给节点上的Flanneld进程。

5、然后node2节点上的Flanneld进程将接收到的UDP包解包得到RAW IP包:10.244.1.236 -> 10.244.2.123

6、解包后的RAW IP包匹配到node2节点上的路由规则(10.244.2.0/24),内核将RAW IP包发送给cni0设备。

[root@node2 ~]# ip route
default via 10.151.30.11 dev eth0  proto static  metric 100
10.151.30.0/24 dev eth0  proto kernel  scope link  src 10.151.30.23  metric 100
10.244.0.0/16 dev flannel0
10.244.2.0/24 dev cni0  proto kernel  scope link  src 10.244.2.1
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1

7、cni0将IP包转发给连接在cni0网桥的pod-b

$ kubectl exec pod-a ping 10.244.2.123
PING 10.244.2.123 (10.244.2.123): 56 data bytes
64 bytes from 10.244.2.123: seq=0 ttl=62 time=1.452 ms
64 bytes from 10.244.2.123: seq=1 ttl=62 time=1.160 ms
64 bytes from 10.244.2.123: seq=2 ttl=62 time=0.853 ms

上面就是基于Flannel UDP模式的跨主机通信的基本流程,Flanneld主要有两方面的功能:

  • UDP封包解包
  • 节点上的路由表的动态更新

数据包通过tun设备从内核态复制到用户态的应用中的,然后在通过用户态复制到内核态,仅一次网络传输就进行了两次用户态和内核态的切换,显然这种效率是不高的。要提高效率最简单的方式就是把封包解包这些事情都交给内核去干,事实上Linux内核本身就提供了比较成熟的网络封包解包(隧道传输)实现方案VXLAN。Flanneld也实现了基于VXLAN的方案,这也是日常中使用最多的方案。

VXLAN方式

VXLAN,即Virtual Extensible LAN(虚拟可扩展局域网),是Linux内核本身就支持的一种网络虚拟化技术。所以说,VXLAN可以完全在内核态实现上述的封包和解包的工作,从而通过与前面相似的”隧道”机制,构建出覆盖网络(Overlay Network)。

在使用VXLAN模式时,需要将Flanneld的Backend类型修改为vxlan:

$ kubectl edit cm kube-flannel-cfg -n kube-system
apiVersion: v1
data:
  cni-conf.json: |
    {
      "cniVersion": "0.2.0",
      "name": "cbr0",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
        {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"  # 修改后端类型为 vxlan
      }
    }
kind: ConfigMap
......

类型修改完后,需要重建下flannel的所有pod才能生效。

$ kubectl delete pod -n kube-system -l app=flannel

重建完成后查看Pod的日志。出现Found network config - Backend type: vxlan的日志就证明已经配置成功了。

$ kubectl logs -f kube-flannel-ds-amd64-pfb8h -n kube-system
I1129 03:33:56.588549       1 main.go:527] Using interface with name eth0 and address 10.151.30.23
I1129 03:33:56.588893       1 main.go:544] Defaulting external address to interface address (10.151.30.23)
I1129 03:33:56.698562       1 kube.go:126] Waiting 10m0s for node controller to sync
I1129 03:33:56.698726       1 kube.go:309] Starting kube subnet manager
I1129 03:33:57.698910       1 kube.go:133] Node controller sync successful
I1129 03:33:57.698980       1 main.go:244] Created subnet manager: Kubernetes Subnet Manager - ydzs-node2
I1129 03:33:57.699000       1 main.go:247] Installing signal handlers
I1129 03:33:57.699375       1 main.go:386] Found network config - Backend type: vxlan
I1129 03:33:57.699553       1 vxlan.go:120] VXLAN config: VNI=1 Port=0 GBP=false DirectRouting=false
I1129 03:33:57.703715       1 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1129 03:33:57.703785       1 main.go:321] Running backend.
I1129 03:33:57.703825       1 main.go:339] Waiting for all goroutines to exit
I1129 03:33:57.703940       1 vxlan_network.go:60] watching for new subnet leases

flanneld在启动时会通过Netlink机制与linux内核通信,建立一个VTEP(Virtual Tunnel Access End Point)设备flannel.1(命名规则为flannel.[NVI],NVI默认为1),类似于交换机当中的一个网口,可以通过ip -d link命令查看VTEP设备flannel.1的配置信息:

$ ip -d link show flannel.1
6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT
    link/ether 72:f7:e9:40:97:1e brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 10.151.30.22 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 addrgenmode eui64

VTEP的local IP为10.151.30.22,destination port为8472,在节点上查看进程监听情况:

$ netstat -ulnp | grep 8472
udp        0      0 0.0.0.0:8472            0.0.0.0:*                           -

仔细看和 UDP 模式下查看 Flanneld 监听的端口是有区别的,最后一栏显示的不是进程的 ID 和名称,而是一个破折号“-”,这说明 UDP 的8472端口不是由用户态的进程在监听的,也证实了VXLAN模块工作在内核态模式下。

在UDP模式下,由Flanneld进程进行网络包的封包和解包的工作,而在VXLAN模式下解封包的事情交由内核处理。当Flanneld启动时,将创建VTEP设备flannel.1,并将VTEP设备的相关信息上报到ETCD中,而当在Flannel网络中有新的节点发现时,各个节点上的flanneld进程将依次执行一下流程:
1、在节点当中创建一条该节点所属网段的路由表,主要是能让Pod当中的流量路由到flannel.1接口。通过route -n可以查看节点已经有四条flannel.1接口的路由:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.151.30.11    0.0.0.0         UG    100    0        0 eth0
10.151.30.0     0.0.0.0         255.255.255.0   U     100    0        0 eth0
10.244.0.0      10.244.0.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.1.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.3.0      10.244.3.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.4.0      10.244.4.0      255.255.255.0   UG    0      0        0 flannel.1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

比如10.244.2.0这条路由规则,意思就是发往10.244.2.0/24网段的IP包,都需要经过flannel.1设备发出,而且最后被发送的网关地址是10.233.2.0。其实这个网关地址就是node2节点上VTEP设备的IP地址:

[root@node2 ~]# ifconfig
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.244.2.0  netmask 255.255.255.255  broadcast 0.0.0.0
        inet6 fe80::2467:52ff:fe6b:1bf9  prefixlen 64  scopeid 0x20<link>
        ether 26:67:52:6b:1b:f9  txqueuelen 0  (Ethernet)
        RX packets 42050890  bytes 27691009839 (25.7 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 36287976  bytes 46894346975 (43.6 GiB)
        TX errors 0  dropped 163 overruns 0  carrier 0  collisions 0
......

2、上面知道了目的VTEP设备的IP地址,这时候就需要知道目的MAC地址,才能把数据包发送过去,这时候其实Flanneld进程就会在节点当中维护所有节点的IP以及VTEP设备的静态ARP缓存。通过arp -n查看那当前节点当中已经缓存了另外四个节点以及VTEP的ARP信息。

$ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
10.244.3.0               ether   32:f1:e0:a9:97:ab   CM                    flannel.1
10.244.2.0               ether   26:67:52:6b:1b:f9   CM                    flannel.1
10.244.0.0               ether   0a:24:5e:40:ff:da   CM                    flannel.1
10.244.4.0               ether   4a:09:1f:42:ed:c1   CM                    flannel.1
......

这里可以看到 IP 地址 10.244.2.0 对应的 MAC 地址是 26:67:52:6b:1b:f9,这样就知道了目的 VTEP 设备的 MAC 地址。但是现在我们只是知道了目标设备的 MAC 地址,却不知道对应的宿主机的地址是什么?

3、这个时候Flanneld进程还会在节点当中添加一条该节点的转发表,通过bridge命令查看节点上VXLAN转发表(FDB entry),MAC为VTEP设备即flannel.1的MAC地址,IP为VTEP对应的对外IP(可通过Flanneld的启动参数--iface=eth0指定,若不指定则按默认网关查找网络接口对应的IP),可以看到已经有四条转发表:

$ bridge fdb show dev flannel.1
32:f1:e0:a9:97:ab dst 10.151.30.57 self permanent
26:67:52:6b:1b:f9 dst 10.151.30.23 self permanent
4a:09:1f:42:ed:c1 dst 10.151.30.59 self permanent
0a:24:5e:40:ff:da dst 10.151.30.11 self permanent

这样就找到上面目的VTEP设备的MAC地址对应的IP地址为10.151.30.23的主机,这就是node2节点,这就找到了要发往的目的地址。

这个时候容器跨节点网络通信实现的完整流程为:

  • 和UDP模式一样,pod-a(10.244.1.236)当中的IP包通过pod-a内的路由表被发送到cni0
  • 到达cni0当中的IP包通过匹配节点node1当中的路由表发现通往10.244.2.13的IP包应该交给flannel.1接口
  • flannel.1作为一个VTEP设备,收到报文后将按照VTEP的配置进行封包,通过node1节点上的arp和转发表得知10.244.2.123属于节点node2,并且会将node2节点对应的VTEP设备的MAC地址根据flannel.1设备创建时设置的参数(VNI、local IP、Port)进行VXLAN封包
  • 通过节点node2和node1之间的网络连接,VXLAN包到达node2的eth0接口
  • 通过端口8472,VXLAN包被转发给VTEP设备flannel.1进行解包
  • 解封装后的IP包匹配节点node2当中的路由表(10.244.2.0),内核将IP包转发给cni0
  • cni0将IP包转发给连接在cni0上的pod-b

host-gw

host-gw即Host Gateway,从名字就可以知道这种方式是通过把主机当做网关来实现跨节点网络通信的。

和UDP和VXLAN一样,首先将Backend中的type改为host-gw,更新完成后,随便查看一个 flannel 的 Pod 日志,如果出现如下所示的 Found network config - Backend type: host-gw 日志就证明已经是 host-gw 模式了:

$ kubectl logs -f kube-flannel-ds-amd64-642l6 -n kube-system
I1129 04:53:34.309992       1 main.go:527] Using interface with name eth0 and address 10.151.30.59
I1129 04:53:34.310292       1 main.go:544] Defaulting external address to interface address (10.151.30.59)
I1129 04:53:34.510618       1 kube.go:126] Waiting 10m0s for node controller to sync
I1129 04:53:34.607860       1 kube.go:309] Starting kube subnet manager
I1129 04:53:35.511530       1 kube.go:133] Node controller sync successful
I1129 04:53:35.511624       1 main.go:244] Created subnet manager: Kubernetes Subnet Manager - ydzs-node4
I1129 04:53:35.511646       1 main.go:247] Installing signal handlers
I1129 04:53:35.511899       1 main.go:386] Found network config - Backend type: host-gw
I1129 04:53:35.611040       1 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1129 04:53:35.611120       1 main.go:321] Running backend.
I1129 04:53:35.611165       1 main.go:339] Waiting for all goroutines to exit
I1129 04:53:35.611280       1 route_network.go:53] Watching for new subnet leases
I1129 04:53:35.611768       1 route_network.go:85] Subnet added: 10.244.0.0/24 via 10.151.30.11
W1129 04:53:35.612268       1 route_network.go:102] Replacing existing route to 10.244.0.0/24 via 10.244.0.0 dev index 6 with 10.244.0.0/24 via 10.151.30.11 dev index 2.
I1129 04:53:35.612705       1 route_network.go:85] Subnet added: 10.244.1.0/24 via 10.151.30.22
W1129 04:53:35.612777       1 route_network.go:88] Ignoring non-host-gw subnet: type=vxlan
I1129 04:53:35.612829       1 route_network.go:85] Subnet added: 10.244.2.0/24 via 10.151.30.23
W1129 04:53:35.612868       1 route_network.go:88] Ignoring non-host-gw subnet: type=vxlan
I1129 04:53:35.612935       1 route_network.go:85] Subnet added: 10.244.3.0/24 via 10.151.30.57
W1129 04:53:35.612995       1 route_network.go:88] Ignoring non-host-gw subnet: type=vxlan
I1129 04:53:35.808989       1 route_network.go:85] Subnet added: 10.244.3.0/24 via 10.151.30.57
W1129 04:53:35.809279       1 route_network.go:102] Replacing existing route to 10.244.3.0/24 via 10.244.3.0 dev index 6 with 10.244.3.0/24 via 10.151.30.57 dev index 2.
I1129 04:53:37.012832       1 route_network.go:85] Subnet added: 10.244.1.0/24 via 10.151.30.22
W1129 04:53:37.013132       1 route_network.go:102] Replacing existing route to 10.244.1.0/24 via 10.244.1.0 dev index 6 with 10.244.1.0/24 via 10.151.30.22 dev index 2.
I1129 04:53:37.713684       1 route_network.go:85] Subnet added: 10.244.2.0/24 via 10.151.30.23
W1129 04:53:37.714022       1 route_network.go:102] Replacing existing route to 10.244.2.0/24 via 10.244.2.0 dev index 6 with 10.244.2.0/24 via 10.151.30.23 dev index 2.

采用host-gw模式后,flanneld的唯一作用就是负责主机上路由表的动态更新,其实就是将每个flannel子网(Flannel subnet,比如10.244.1.0/24)的下一跳设置成该子网对应的宿主机的IP地址,flannel子网和主机的信息,都是保存在etcd中的。flanneld只需要WATCH这些数据的变化,然后实时更新路由表即可。主要流程如下:

  1. 同UDP、VXLAN模式一致,通过容器A的路由表,IP包到达cni0
  2. 到达cni0的IP包匹配到node1当中的路由规则(10.244.2.0),并且网关为10.151.30.23,即节点node2,所以内核将IP包发送给节点node2(10.151.30.23):

    $ route -n
    Kernel IP routing table
    Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
    0.0.0.0         10.151.30.11    0.0.0.0         UG    100    0        0 eth0
    10.151.30.0     0.0.0.0         255.255.255.0   U     100    0        0 eth0
    10.244.0.0      10.151.30.11    255.255.255.0   UG    0      0        0 eth0
    10.244.1.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
    10.244.2.0      10.151.30.23    255.255.255.0   UG    0      0        0 eth0
    10.244.3.0      10.151.30.57    255.255.255.0   UG    0      0        0 eth0
    10.244.4.0      10.151.30.59    255.255.255.0   UG    0      0        0 eth0
    172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
    
  3. IP包通过物理网络到达节点node2的eth0

  4. 到达node2节点的eth0的IP包匹配到节点当中的路由表(10.244.2.0/24),IP包被转发给cni0设备
  5. cni0将IP包转发给连接在cni0上的pod-b

这样就完成了整个跨主机通信流程,这个流程可能是最简单最容易理解的模式了,而且容器通信的过程还免除了额外的封包和解包带来的性能损耗,所以理论上性能肯定要更好,但是该模式使用过节点上的路由表来实现各个节点之间的网络通信,那么就得保证两个节点是可以直接路由过去的。按照内核当中的路由规则,网关必须在跟主机当中至少一个IP处于同一网段,故造成的结果就是采用host-gw这种模式的时候,集群中所有的节点必须处于同一个网络,这对于集群规模比较大时需要对节点进行网段划分会存在一定的局限性,另外一个则是随着集群当中节点的规模的增大,flanneld需要维护主机上成千上万跳路由表的动态更新也是一个压力。

除了flannel之外,Calico这种网络插件和flannel的host-gw模式基本一样,都是在每台宿主机上面添加子网和对应的宿主机的IP地址作为网关这样的路由信息,不同于flannel通过etcd和宿主机上的flannel来维护路由信息的做法,calico使用bgp来自动的在整个集群中分发路由信息。