简介
flannel
是 coreos 开源的 Kubernetes CNI
实现。它使用 etcd
或者 Kubernetes API
存储整个集群的网络配置。每个 kubernetes节点上运行 flanneld
组件,它从 etcd
或者 Kubernetes API
获取集群的网络地址空间,并在空间内获取一个 subnet
,该节点上的容器 IP都从这个 subnet
中分配,从而保证不同节点上的 IP不会冲突。flannel通过不同的 backend
来实现跨主机的容器网络通信,目前支持 udp
, vxlan
, host-gw
等一系列 backend实现。
源码地址:https://github.com/flannel-io/flannel
SubnetManager
子网管理器,以下简称sm
在main方法中会初始化sm: sm, err := newSubnetManager(ctx)
这里kube子网管理为例:
func NewSubnetManager(ctx context.Context, apiUrl, kubeconfig, prefix, netConfPath string, setNodeNetworkUnavailable, useMultiClusterCidr bool) (subnet.Manager, error) {var cfg *rest.Configvar err error// Try to build kubernetes config from a master url or a kubeconfig filepath. If neither masterUrl// or kubeconfigPath are passed in we fall back to inClusterConfig. If inClusterConfig fails,// we fallback to the default config.cfg, err = clientcmd.BuildConfigFromFlags(apiUrl, kubeconfig)if err != nil {return nil, fmt.Errorf("fail to create kubernetes config: %v", err)}c, err := clientset.NewForConfig(cfg)if err != nil {return nil, fmt.Errorf("unable to initialize client: %v", err)}// The kube subnet mgr needs to know the k8s node name that it's running on so it can annotate it.// If we're running as a pod then the POD_NAME and POD_NAMESPACE will be populated and can be used to find the node// name. Otherwise, the environment variable NODE_NAME can be passed in.nodeName := os.Getenv("NODE_NAME")if nodeName == "" {podName := os.Getenv("POD_NAME")podNamespace := os.Getenv("POD_NAMESPACE")if podName == "" || podNamespace == "" {return nil, fmt.Errorf("env variables POD_NAME and POD_NAMESPACE must be set")}pod, err := c.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{})if err != nil {return nil, fmt.Errorf("error retrieving pod spec for '%s/%s': %v", podNamespace, podName, err)}nodeName = pod.Spec.NodeNameif nodeName == "" {return nil, fmt.Errorf("node name not present in pod spec '%s/%s'", podNamespace, podName)}}netConf, err := os.ReadFile(netConfPath)if err != nil {return nil, fmt.Errorf("failed to read net conf: %v", err)}sc, err := subnet.ParseConfig(string(netConf))if err != nil {return nil, fmt.Errorf("error parsing subnet config: %s", err)}if useMultiClusterCidr {err = readFlannelNetworksFromClusterCIDRList(ctx, c, sc)if err != nil {return nil, fmt.Errorf("error reading flannel networks from k8s api: %s", err)}}sm, err := newKubeSubnetManager(ctx, c, sc, nodeName, prefix, useMultiClusterCidr)if err != nil {return nil, fmt.Errorf("error creating network manager: %s", err)}sm.setNodeNetworkUnavailable = setNodeNetworkUnavailableif sm.disableNodeInformer {log.Infof("Node controller skips sync")} else {go sm.Run(context.Background())log.Infof("Waiting %s for node controller to sync", nodeControllerSyncTimeout)err = wait.Poll(time.Second, nodeControllerSyncTimeout, func() (bool, error) {return sm.nodeController.HasSynced(), nil})if err != nil {return nil, fmt.Errorf("error waiting for nodeController to sync state: %v", err)}log.Infof("Node controller sync successful")}return sm, nil
}
上面的逻辑大致如下:
通过配置得到的kubeconfig获取到pod访问客户端
通过节点环境变量获取到节点名称,如果没有则通过pod详情获取到节点名称
通过client-go库方法机制对集群中node进行监听,因为flannel是根据node来划分网段的
根据监听到的node的事件,放入到sm的events channel中
BackendManager
在main方法中,进行了以下操作:
bm := backend.NewManager(ctx, sm, extIface)
be, err := bm.GetBackend(config.BackendType)
if err != nil {log.Errorf("Error fetching backend: %s", err)cancel()wg.Wait()os.Exit(1)
}bn, err := be.RegisterNetwork(ctx, &wg, config)
通过上面会得到这样一个接口实例:
type Network interface {Lease() *subnet.LeaseMTU() intRun(ctx context.Context)
}
目前支持的backend类型有allpc,awsvpc,gce,hostgw,udp和vxlan。
以vxlan为例:
func (nw *network) Run(ctx context.Context) {wg := sync.WaitGroup{}log.V(0).Info("watching for new subnet leases")events := make(chan []subnet.Event)wg.Add(1)go func() {subnet.WatchLeases(ctx, nw.subnetMgr, nw.SubnetLease, events)log.V(1).Info("WatchLeases exited")wg.Done()}()defer wg.Wait()for {evtBatch, ok := <-eventsif !ok {log.Infof("evts chan closed")return}nw.handleSubnetEvents(evtBatch)}
}
上面代码逻辑大致如下:
-
调用SubnetManager.WatchLeases()监听整个集群网络的变更事件
根据不同事件刷新路由表,arp表和fdb表等。
网络设备
与flannel相关的几个虚拟网络上设备:
flannel.1:这是一个vxlan设备。也就是耳熟能详的vteh设备,负责网络数据包的封包和解封。
cni0:是一个linux bridge,用于连接同一个宿主机上的pod。
vethf12090da@if3:容器内eth0网卡的对端设备,从名字上看,在容器内eth0网卡的编号应为3。
流程原理
VxLAN的设计思想是:
在现有的三层网络之上,“覆盖”一层虚拟的、由内核VxLAN模块负责维护的二层网络,使得连接在这个VxLAN二层网络上的“主机”(虚拟机或容器都可以),可以像在同一个局域网(LAN)里那样自由通信。
为了能够在二层网络上打通“隧道”,VxLAN会在宿主机上设置一个特殊的网络设备作为“隧道”的两端,叫VTEP
VTEP原理如下:
flannel.1设备,就是VxLAN的VTEP,即有IP地址,也有MAC地址
容器服务的IP包,会先出现在docker0网桥,再路由到本机的flannel.1设备进行处理,
为了能够将“原始IP包”封装并发送到正常的主机,源VTEP设备收到原始IP包后,在上面加上一个目的MAC地址(也就是VTEP设备的MAC地址),封装成数据桢,发送给目的VTEP设备 ,封装过程只是加了一个二层头,不会改变“原始IP包”的内容,
Linux会再加上一个VxLAN头,VxLAN头里有一个重要的标志叫VNI,它是VTEP识别某个数据桢是不是应该归自己处理的重要标识。在Flannel中,VNI的默认值是1,这也是为什么宿主机的VTEP设备都叫flannel.1的原因
一个flannel.1设备只知道另一端flannel.1设备的MAC地址,却不知道对应的宿主机地址是什么。在linux内核里面,网络设备进行转发的依据,来自FDB的转发数据库
https://juejin.cn/post/6994825163757846565
http://just4coding.com/2021/11/03/flannel/