Flannel 源码走读~~最简单的CNI网络插件

1. 关键Interface

  • backend.Backend:每个Backend代表一种后端机制,如UDP、host-gw、vxlan等等
    • RegisterNetwork:通过配置文件创建backend.Network对象
  • backend.Network:flannel 要管理的网络地址范围,表示整个PODIP的网段
    • Run:一个独立的线程,监听来自subnet.Manager.WatchLeases的消息进行处理
  • subnet.Manager:当前节点Flannel管理的一小段PodIP
    • ETCDV2kube 俩种实现
    • 用来监听子网的变更消息

2. 入口函数

  • 根据配置决定ExternalInterface,即Flannel使用的出口网卡
  • 创建SubnetManager
  • 调用 GetBackend创建Backend对象,该对象包含了SubnetManager和Network
    • 构造函数中传递了SubnetManager
    • 调用backend.RegisterNetwork进行注册 –> 创建了backend.Network
  • 根据配置同步IPTable规则
    • MASQ规则
    • Forward规则
  • 生成Subnet文件
  • 启动后端网络:Network.Run

3. 子网管理接口

Manager接口当前包括:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type Manager interface {
	GetNetworkConfig(ctx context.Context) (*Config, error)
    // 获取k8s为节点分配SubNet网段
	AcquireLease(ctx context.Context, attrs *LeaseAttrs) (*Lease, error)
    // 对接k8s时这个接口实际上没有用到
	RenewLease(ctx context.Context, lease *Lease) error
    // 对接k8s时这个接口实际上没有用到
	WatchLease(ctx context.Context, sn ip.IP4Net, cursor interface{}) (LeaseWatchResult, error)
    // backend.Network对象调用,用来获取监听到Lease.Event消息
	WatchLeases(ctx context.Context, cursor interface{}) (LeaseWatchResult, error)

	Name() string
}

Manager接口包括两种实现:

  • etcdv2:基于etcd原生API工作
  • Kubernetes:基于Kubernetes的API Server的Informer进行工作

1. 创建KubeSubnetManager

基于Kubernetes的subnet.Manager的工作流程如下:

  • 创建接口:subnet.kube.NewSubnetManager

    • 创建kubeclient
    • 获取当前节点的NodeName
    • 读取Flannel的配置文件
    • 调用 newKubeSubnetManager 接口,该接口会返回kubeSubnetManager对象
    • 运行kubeSubnetManager对象的Run方法,即启动对应nodeController
    • 等待nodeController初始化完成,最终返回kubeSubnetManager对象
  • newKubeSubnetManager 接口,初始化kubeSubnetManager对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    type kubeSubnetManager struct {
    	annotations    annotations     // Flannel 在对应节点上添加的注解
    	client         clientset.Interface  // go-client客户端
    	nodeName       string               // nodeName
    	nodeStore      listers.NodeLister
    	nodeController cache.Controller
    	subnetConf     *subnet.Config		
    	events         chan subnet.Event // 包括:EventAdded、EventRemoved 两种事件
    }
    

    该接口核心工作就是调用go-client的NewIndexerInformer,如下:

     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
    
    	indexer, controller := cache.NewIndexerInformer(
            // 每个flannel所要list和watch的资源,这里只关心Node
    		&cache.ListWatch{
    			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
    				return ksm.client.CoreV1().Nodes().List(ctx, options)
    			},
    			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
    				return ksm.client.CoreV1().Nodes().Watch(ctx, options)
    			},
    		},
            // 指定k8s的资源类型
    		&v1.Node{},
            // 指定re-list的周期,当指定了一个非0值时会周期性的收到OnUpdate消息,
    		resyncPeriod,
            // 指定event的回调函数
    		cache.ResourceEventHandlerFuncs{
    			AddFunc: func(obj interface{}) {
    				ksm.handleAddLeaseEvent(subnet.EventAdded, obj)
    			},
    			UpdateFunc: ksm.handleUpdateLeaseEvent,
    			DeleteFunc: func(obj interface{}) {
    				node, isNode := obj.(*v1.Node) // 类型断言,判断Event是否是*v1.Node
    				// We can get DeletedFinalStateUnknown instead of *api.Node here and we need to handle that correctly.
    				if !isNode {
    					deletedState, ok := obj.(cache.DeletedFinalStateUnknown)
    					if !ok {
    						log.Infof("Error received unexpected object: %v", obj)
    						return
    					}
    					node, ok = deletedState.Obj.(*v1.Node)
    					if !ok {
    						log.Infof("Error deletedFinalStateUnknown contained non-Node object: %v", deletedState.Obj)
    						return
    					}
    					obj = node
    				}
    				ksm.handleAddLeaseEvent(subnet.EventRemoved, obj)
    			},
    		},
    		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
    	)
    

    从上面看到处理Node消息的核心接口包括:

    • handleUpdateLeaseEvent:节点变更flannel相关Annotations的消息,生成subnet.EventAdded消息
    • handleAddLeaseEvent:Node节点添加和删除相关的消息

    上述两个接口均通过nodeToLease接口生成subnet.Lease对象,然后将消息再次转发到kubeSubnetManager.events管道。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    type LeaseAttrs struct {
    	PublicIP    ip.IP4                                // 取自flannel.alpha.coreos.com/public-ip
    	BackendType string          `json:",omitempty"`   // 取自flannel.alpha.coreos.com/backend-type表示后端类型
    	BackendData json.RawMessage `json:",omitempty"`   // 取自flannel.alpha.coreos.com/backend-data是一个JSON,包括:VtepMAC等
    }
    
    type Lease struct {
    	Subnet     ip.IP4Net                              // 取自节点的 Spec.PodCIDR ??这个是谁分配的??
    	Attrs      LeaseAttrs
    	Expiration time.Time
    	Asof uint64
    }
    

    PS:当节点flannel.alpha.coreos.com/kube-subnet-manager=false,flanneld不会管理该节点的Pod网络。

4. VXLan Backend接口

1. 创建Network对象

当Flanneld在Main中调用backend.vxlan.New创建VXLan的Backend对象后,会紧接着调用RegisterNetwork接口来创建对应该节点的子网:

  • 根据参数创建VXLAN设备

  • 根据实际设备的Mac地址等参数,生成subnet.LeaseAttrs,并去调用SubnetManager.AcquireLease

    • SubnetManager.AcquireLease主要作用是获取kube-controller分配给该节点cidr,即子网网段,而KubeSubnetManager的实现中就是从Node对象的Spec.PodCIDR字段去获取。
    • SubnetManager.AcquireLease还会Patch节点状态的信息,并且将节点的NetworkUnavailable状态设置为False,最后返回一个SubnetLease对象
  • 结构体network

    1
    2
    3
    4
    5
    6
    7
    8
    
    nw := &network{
    	SimpleNetwork: backend.SimpleNetwork{
    		SubnetLease: lease,
    		ExtIface:    extIface,
    	},
    	subnetMgr: subnetMgr,
    	dev:       dev,
    }
    

2. 节点消息

创建Network对象之后,Main函数会调用Network对象的Run接口,开始接收KubeSubnetManager监听到的Subnet.Event

每个flanneld中都会维护整个集群的Lease对象在以下结构体中,并且包括update、remove等接口:

1
2
3
4
type leaseWatcher struct {
	ownLease *Lease   // 当前节点Lease信息
	leases   []Lease  // 其他节点Lease信息
}

用于处理消息的接口是handleSubnetEvents,该接口进行以下工作:

  • 添加ARP规则
  • 添加FDB
  • 管理路由表

5. podCIDR的分配

使用KubeSubnetManager作为管理服务时,Flannel通过node节点的podCIDR判断当前节点的子网网段。这个值是kube-controller分配的,并且随意更改。

默认情况下,kube-controller不会自动分配podCIDR,用户需要添加启动参数:–allocate-node-cidrs=true 和 –cluster-cidr=${POD_IP}

需要注意:Service-CIDR的范围应该小于Pod-Cluster-CIDR,否则可能会Controller分配地址段会失败。

PS:kubeadm 部署时使用–pod-network-cidr参数和podSubnet字段能达成等价的效果。