• flannel网络


    flannel网络

    前言

    知识补充

    arp表

    在局域网内,通常是先执行ARP(地址解析协议)然后才进行路由。ARP用于将IP地址解析为MAC地址,以便在局域网上直接传输数据帧。具体流程如下:

    1. 源设备要发送数据包到目标设备,首先它会检查目标设备是否在同一子网(局域网)上。如果目标设备与源设备在同一子网上,那么源设备将首先查看本地的ARP缓存表,查找目标设备的MAC地址。
    2. 如果ARP缓存中没有目标设备的MAC地址,源设备将广播一个ARP请求,询问整个局域网中是否有设备知道目标IP地址对应的MAC地址。
    3. 目标设备接收到ARP请求后,会回应一个ARP响应,其中包含了目标设备的MAC地址。
    4. 源设备接收到ARP响应后,会将目标设备的MAC地址存储在ARP缓存中,以便后续通信。

    一旦源设备知道了目标设备的MAC地址,它就可以构建以太网数据帧并将数据包发送到目标设备,而无需进行路由。只有当目标设备不在同一子网上,需要通过路由器进行跨子网通信时,才会进行路由操作。

    所以,先执行ARP以解析MAC地址,然后进行路由以确保数据包最终到达目标设备。

    通常情况下,设备在同一子网内通信时会发送 ARP 请求以获取目标设备的 MAC 地址,然后将数据包发送到目标设备的 MAC 地址。这适用于局域网内的直接通信。设备不会自动将默认网关用作目标,除非要访问不在同一子网内的目标设备或外部网络。在这种情况下,设备将使用默认网关来路由数据包到其他子网或外部网络。

    原理

    在k8s网络中,pod内部通讯直接通过lo网卡就可以实现,因为他们是同一个命名空间,而同节点,不同pod之间的通讯是通过物理机的网桥进行实现的。

    其原理是在物理机上创建一个虚拟网桥,然后为每个POD都创建一对veth,一个绑定到POD的网络命名空间中,一个绑定到创建的虚拟网桥上(veth虚拟以太网)。POD与POD之间的访问只需要通过网桥就可以进行通讯。POD发起请求后,首先通过arp获取到服务端mac地址,请求会到达cni0网桥上(如果是k8s并且使用的cni,那么k8s会创建一个cni0网桥,然后把容器绑定到该网桥上,与docker0网桥区分开来。),网桥根据mac地址找到服务端绑定在物理机上的网卡然后转发。docker会创建一个docker0的网桥,docker run运行的容器都会绑定到这个网桥上。如果是k8s创建的容器,那么会根据策略,创建对应的网桥。

    对于跨节点的访问,因为网桥只绑定了节点内部的网卡,所以如果将请求还转发给网桥肯定是无法找到目的地的,那么就只有将请求发送给物理机的默认网卡(这里需要将物理网卡添加到网关中),但是由于目的地址是pod的ip,在没有路由规则或者nat的情况下,是无法路由到目的地的。那么这时候我们只需要通过某种方式将两个局域网(跨节点POD)之间的路由打通,就能实现跨节点的通讯了

    常用的做法是使用flannel或者calico进行将路由打通。

    flannel: vxlan,将要扩节点访问的请求,先通过本地路由到flanneld里,flanneld再对请求包进行封装(使用vxlan技术),然后发送给跨节点的flanneld程序中,对方flanneld程序对请求包进行解析,然后再转发数据到指定的pod中。

    实战

    下面只说使用vxlan 的时候的流量走向

    1. 进入容器内部使用arp -n 以及route -n 分别查看mac映射表以及路由表,由此我们可以知道,当跨节点通讯的时候,因为目标地址不是同网段所以mac地址会直接使用默认网关的mac地址,然后通过eth0网卡发出,因为eth0网卡另一头连着物理机的网桥,所以会发送给网桥。

      / # arp -n
       (10.233.64.1) at 6a:64:e3:ef:02:1c [ether]  on eth0
       (10.233.64.47) at 26:28:6e:f9:f0:fb [ether]  on eth0
      / # route -n
      Kernel IP routing table
      Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
      0.0.0.0         10.233.64.1     0.0.0.0         UG    0      0        0 eth0
      10.233.64.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
      10.233.64.0     10.233.64.1     255.255.192.0   UG    0      0        0 eth0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    2. 在物理机上,网关继续转发请求,运行 arp -n 以及route -n,查看mac映射表以及路由表,通过路由表我们可知目标地址网关为10.233.65.0​,那么对应的mac地址就是82:51:c7:9c:d1:d7 通过flannel.1转发。

      路由表
      10.233.65.0     10.233.65.0     255.255.255.0   UG    0      0        0 flannel.1
      
      arp表
      10.233.65.0              ether   82:51:c7:9c:d1:d7   CM                    flannel.1
      10.233.64.55             ether   be:e7:18:88:ff:39   C                     cni0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    3. 通过 bridge fdb show |grep 82:51:c7:9c:d1:d7 我们可以获取到目标地址的物理节点ip地址

      82:51:c7:9c:d1:d7 dev flannel.1 dst 10.20.31.107 self permanent
      
      • 1
    4. 通过ip -d link show flannel.1 查看flannel.1网桥的详细信息

      vxlan id 1 local 10.20.31.106 dev ens192 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
      
      • 1
    5. 由此可以明白吗,请求会使用vxlan策略,源ip为10.20.31.106 目标地址为10.20.31.107 端口为8472,通过ens192网卡转发出去。

    命令

    • bridge fdb show 查看 Linux 桥接设备上的桥接表 (Forwarding Database) 中的信息。该命令显示了在网络交换机或网桥上学到的 MAC 地址的记录,以及与这些地址关联的端口信息。

    • ip -d link show flannel.1 查看flannel.1的vxlan配置 输出vxlan id 1 local 10.20.31.106 dev ens192 srcport 0 0 dstport 8472​的意思是

      • vxlan id 1​: 这是 VXLAN 的虚拟网络标识符,通常用于标识不同的 VXLAN 网络。在这种情况下,VXLAN 的 ID 被设置为 1。
      • local 10.20.31.106​: 这指定了 VXLAN 接口的本地 IP 地址,即用于通信的本地 IP 地址。
      • dev ens192​: 这指定了 VXLAN 接口的底层物理网络设备,即 VXLAN 封装的数据将通过 ens192​ 网卡发送和接收。
      • srcport 0 0​: 这定义了 VXLAN 包的源端口范围。在此示例中,范围被设置为 0 到 0,这意味着 VXLAN 包的源端口将由系统动态分配。
      • dstport 8472​: 这指定了 VXLAN 包的目标端口,即用于 VXLAN 数据包的传输的目标端口号,通常为 8472。
      • nolearning​: 这表明 VXLAN 接口不会学习 MAC 地址,而是将数据包发送到指定的目标地址。
    • arp -n 获取ip-> mac的映射表

    • route -n 获取路由表

    源码分析

    flannel是一个daemonsets 类型的应用,它会在每台机器上都启动一个pod,使用hostNetwork: true 物理节点的网络,这样以便于管理arp表、route表、fdb表(vxlan)信息。

    注册网络策略

    获取配置

    flanneld在启动的时候,会读取配置文件(配置文件有两种获取方式,如果开启了kube-subnet-mgr​ 默认为false 便使用net-conf.json,否则直接使用etcdclient访问 /coreos.com/network/config 获取配置信息) ,里面存放了当前k8s集群POD的IP范围,以及要使用的策略(vxlan、udp…)。配置文件读取完成后,就开始根据配置文件里面的type进行定向注册网络策略工作。

    具体结构体如下

    type Config struct {
    	EnableIPv4    bool
    	EnableIPv6    bool
    	Network       ip.IP4Net
    	IPv6Network   ip.IP6Net
    	Networks      []ip.IP4Net
    	IPv6Networks  []ip.IP6Net
    	SubnetMin     ip.IP4
    	SubnetMax     ip.IP4
    	IPv6SubnetMin *ip.IP6
    	IPv6SubnetMax *ip.IP6
    	SubnetLen     uint
    	IPv6SubnetLen uint
    	BackendType   string          `json:"-"`
    	Backend       json.RawMessage `json:",omitempty"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    获取subset

    因为使用的kube-subnet-mgr​策略,所以下面以kube-subnet-mgr​策略的角度解析flannel是如何利用subset的。

    kube-subnet-mgr​ 策略是使用k8s中node资源里面PodCIDR​属性作为subset(该属性由kube controller manager设置)。

    func (ksm *kubeSubnetManager) AcquireLease(ctx context.Context, attrs *lease.LeaseAttrs) (*lease.Lease, error) {
    	var cachedNode *v1.Node
    	var err error
    	if ksm.disableNodeInformer {
    		cachedNode, err = ksm.client.CoreV1().Nodes().Get(ctx, ksm.nodeName, metav1.GetOptions{ResourceVersion: "0"})
    	} else {
    		cachedNode, err = ksm.nodeStore.Get(ksm.nodeName)
    	}
    
    	n := cachedNode.DeepCopy()
    	var bd, v6Bd []byte
    	bd, err = attrs.BackendData.MarshalJSON()
    	if err != nil {
    		return nil, err
    	}
    	var cidr, ipv6Cidr *net.IPNet
    	switch {
    	case len(n.Spec.PodCIDRs) == 0:
    		_, parseCidr, err := net.ParseCIDR(n.Spec.PodCIDR)
    		if err != nil {
    			return nil, err
    		}
    		if len(parseCidr.IP) == net.IPv4len {
    			cidr = parseCidr
    		} 
    	case len(n.Spec.PodCIDRs) < 3:
    		for _, podCidr := range n.Spec.PodCIDRs {
    			_, parseCidr, err := net.ParseCIDR(podCidr)
    			if err != nil {
    				return nil, err
    			}
    			if len(parseCidr.IP) == net.IPv4len {
    				cidr = parseCidr
    			} else if len(parseCidr.IP) == net.IPv6len {
    				ipv6Cidr = parseCidr
    			}
    		}
    	}
    	...
    	return lease, nil
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    获取到subset后会将该信息写入到**/run/flannel/subnet.env**中,该文件会被flannel-cni插件使用,当pod中的sandbox被创建时,容器运行时会循环调用cni,去配置该pod的网络,并分配ip地址(先创建sandbox然后再创建正常的容器)

    运行

    flannel会通过infomer监听node资源的变化,当node资源变化时,根据node的PodCIDR 重新生成对应的route、arp、fdb。

    func (nw *network) handleSubnetEvents(batch []lease.Event) {
    	for _, event := range batch {
    		sn := event.Lease.Subnet
    		v6Sn := event.Lease.IPv6Subnet
    		attrs := event.Lease.Attrs
    		switch event.Type {
    		case lease.EventAdded:
    			if event.Lease.EnableIPv4 {
    				if directRoutingOK {
    					log.V(2).Infof("Adding direct route to subnet: %s PublicIP: %s", sn, attrs.PublicIP)
    
    					if err := retry.Do(func() error {
    						return netlink.RouteReplace(&directRoute)
    					}); err != nil {
    						log.Errorf("Error adding route to %v via %v: %v", sn, attrs.PublicIP, err)
    						continue
    					}
    				} else {
    					log.V(2).Infof("adding subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))
    					if err := retry.Do(func() error {
    						return nw.dev.AddARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)})
    					}); err != nil {
    						log.Error("AddARP failed: ", err)
    						continue
    					}
    
    					if err := retry.Do(func() error {
    						return nw.dev.AddFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)})
    					}); err != nil {
    						log.Error("AddFDB failed: ", err)
    
    						// Try to clean up the ARP entry then continue
    						if err := retry.Do(func() error {
    							return nw.dev.DelARP(neighbor{IP: event.Lease.Subnet.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)})
    						}); err != nil {
    							log.Error("DelARP failed: ", err)
    						}
    
    						continue
    					}
    
    					// Set the route - the kernel would ARP for the Gw IP address if it hadn't already been set above so make sure
    					// this is done last.
    					if err := retry.Do(func() error {
    						return netlink.RouteReplace(&vxlanRoute)
    					}); err != nil {
    						log.Errorf("failed to add vxlanRoute (%s -> %s): %v", vxlanRoute.Dst, vxlanRoute.Gw, err)
    
    						// Try to clean up both the ARP and FDB entries then continue
    						if err := nw.dev.DelARP(neighbor{IP: event.Lease.Subnet.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
    							log.Error("DelARP failed: ", err)
    						}
    
    						if err := nw.dev.DelFDB(neighbor{IP: event.Lease.Attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
    							log.Error("DelFDB failed: ", err)
    						}
    
    						continue
    					}
    				}
    			}
    		case lease.EventRemoved:
    			if event.Lease.EnableIPv4 {
    				if directRoutingOK {
    					log.V(2).Infof("Removing direct route to subnet: %s PublicIP: %s", sn, attrs.PublicIP)
    					if err := retry.Do(func() error {
    						return netlink.RouteDel(&directRoute)
    					}); err != nil {
    						log.Errorf("Error deleting route to %v via %v: %v", sn, attrs.PublicIP, err)
    					}
    				} else {
    					log.V(2).Infof("removing subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))
    
    					// Try to remove all entries - don't bail out if one of them fails.
    					if err := retry.Do(func() error {
    						return nw.dev.DelARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)})
    					}); err != nil {
    						log.Error("DelARP failed: ", err)
    					}
    
    					if err := retry.Do(func() error {
    						return nw.dev.DelFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)})
    					}); err != nil {
    						log.Error("DelFDB failed: ", err)
    					}
    
    					if err := retry.Do(func() error {
    						return netlink.RouteDel(&vxlanRoute)
    					}); err != nil {
    						log.Errorf("failed to delete vxlanRoute (%s -> %s): %v", vxlanRoute.Dst, vxlanRoute.Gw, err)
    					}
    				}
    			}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
  • 相关阅读:
    【Linux】【开发】使用sed命令遇到的乱码问题
    【M1-Java】讲讲 StringBuffer和StringBuilder区别
    Nuxt - [高效简洁] 路由不存在时,跳转到自己定制的 404.vue 页面(当路由不存在时自动重定向到自定义的 404 组件)路由配置的解决方案
    我的创作纪念日
    路径规划 | 详解维诺图Voronoi算法(附ROS C++/Python/Matlab仿真)
    jmeter分布式压力测试搭建
    win32com打开带密码excel
    spring boot集成redis
    人工智能经常损失函数和优化算法
    计算机毕业设计springcloud商城源码
  • 原文地址:https://blog.csdn.net/a1023934860/article/details/133875750