Flannel 实质上就是一种 Overlay 网络,也就是将 TCP 数据包装在另一种网络包里面进行路由转发和通信,目前已经支持 UDP、VxLAN、AWS VPC 和 GCE 路由等数据转发方式。

Flannel 会在每一个宿主机上运行名为 flanneld 代理,其负责为宿主机预先分配一个子网,并为 Pod 分配 IP 地址。Flannel 使用 Kubernetes 或 etcd 来存储网络配置、分配的子网和主机公共 IP 等信息。数据包则通过 VXLAN、UDP 或 host-gw 这些类型的后端机制进行转发。

Flannel 规定宿主机下各个 Pod 属于同一个子网,不同宿主机下的 Pod 属于不同的子网。

Flannel 工作模式

支持 2 种实现:VxLAN、host-gw,

  • VxLAN 模式:使用 flannel.1 进行封包解包,内核原生支持,性能较强;
  • host-gw 模式:无需 flannel.1 这样的中间设备,直接宿主机当作子网的下一跳地址,性能最强;

host-gw 的性能损失大约在 10%左右,而其他所有基于 VxLAN“隧道”机制的网络方案,性能损失在 20%~30%左右。

VxLAN 模式

VxLAN 是 Linux 本身支持的一网种网络虚拟化技术。VxLAN 可以完全在内核态实现封装和解封装工作,从而通过隧道机制,构建出 Overlay 网络.

为了能够在二层网络上打通“隧道”,VxLAN 会在宿主机上设置一个特殊的网络设备作为隧道的两端,叫 VTEP:VxLAN Tunnel End Point

flannel.1 设备,就是 VxLAN 的 VTEP,即有 IP 地址,也有 MAC 地址。

与 UDP 模式类似,当 container-1 发出请求后,上的地址 10.1.16.3 的 IP 包,会先出现在 docker 网桥,再路由到本机的 flannel.1 设备进行处理(进站),为了能够将“原始 IP 包”封装并发送到正常的主机,VxLAN 需要找到隧道的出口:宿主机的 VTEP 设备,这个设备信息,由宿主机的 flanneld 进程维护

VTEP 设备之间通过二层数据桢进行通信 源 VTEP 设备收到原始 IP 包后,在上面加上一个目的 MAC 地址,封装成一个导去数据桢,发送给目的 VTEP 设备(获取 MAC 地址需要通过三层 IP 地址查询,这是 ARP 表的功能)

img
img

封装过程只是加了一个二层头,不会改变“原始 IP 包”的内容 这些 VTEP 设备的 MAC 地址,对宿主机网络来说没什么实际意义,称为内部数据桢,并不能在宿主机的二层网络传输,Linux 内核还需要把它进一步封装成为宿主机的一个普通的数据桢,好让它带着“内部数据桢”通过宿主机的 eth0 进行传输,Linux 会在内部数据桢前面,加上一个 VxLAN 头,VxLAN 头里有一个重要的标志叫 VNI,它是 VTEP 识别某个数据桢是不是应该归自己处理的重要标识。 在 Flannel 中,VNI 的默认值是 1,这也是为什么宿主机的 VTEP 设备都叫 flannel.1 的原因

一个 flannel.1 设备只知道另一端 flannel.1 设备的 MAC 地址,却不知道对应的宿主机地址是什么。在 linux 内核里面,网络设备进行转发的依据,来自 FDB 的转发数据库,这个 flannel.1 网桥对应的 FDB 信息,是由 flanneld 进程维护的 linux 内核再在 IP 包前面加上二层数据桢头,把 Node2 的 MAC 地址填进去。这个 MAC 地址本身,是 Node1 的 ARP 表要学习的,需 Flannel 维护,这时候 Linux 封装的“外部数据桢”的格式如下

img
img

然后 Node1 的 flannel.1 设备就可以把这个数据桢从 eth0 发出去,再经过宿主机网络来到 Node2 的 eth0 Node2 的内核网络栈会发现这个数据桢有 VxLAN Header,并且 VNI 为 1,Linux 内核会对它进行拆包,拿到内部数据桢,根据 VNI 的值,所它交给 Node2 的 flannel.1 设备

host-gw 模式

Flannel 第三种协议叫 host-gw,这是一种纯三层网络的方案,性能最高,即 Node 节点把自己的网络接口当做 pod 的网关使用,从而使不同节点上的 Node 进行通信,这个性能比 VxLAN 高,因为它没有额外开销。不过他有个缺点, 就是各 Node 节点必须在同一个网段中 。

img
img

howt-gw 模式的工作原理,就是将每个 Flannel 子网的下一跳,设置成了该子网对应的宿主机的 IP 地址,也就是说,host 充当了这条容器通信路径的 Gateway,这正是 host-gw 的含义。

所有的子网和主机的信息,都保存在 Etcd 中,flanneld 只需要 watch 这些数据的变化,实时更新路由表就行了。核心是 IP 包在封装成桢的时候,使用路由表的下一跳设置上的 MAC 地址,这样可以经过二层网络到达目的宿主机。

另外,如果两个 pod 所在节点在同一个网段中 ,可以让 VxLAN 也支持 host-gw 的功能, 即直接通过物理网卡的网关路由转发,而不用隧道 flannel 叠加,从而提高了 VxLAN 的性能,这种 flannel 的功能叫 directrouting。

Flannel 通信过程

VxLAN 模式

CNI 配置文件 /etc/cni/net.d/09-flannel.conf 内容如下:

1
2
3
4
5
6
7
8
9
{
    "name": "cbr0",
    "cniVersion": "0.3.1",
    "type": "flannel",

    "delegate": {
        "isDefaultGateway": true
    }
}

节点上每个 pod 会有一对 veth pair 设备,其中一端放在 podnetwork namespace 中,另一端在宿主机上接在 cni0 网桥上。flanneld 启动时创建了 vxlan 设备: flannel.1

node1上的flannel网络信息如下,分配的subnet10.230.41.1/24:

1
2
3
4
5
[root@node1 ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.230.0.0/16
FLANNEL_SUBNET=10.230.41.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=false

node2上的flannel网络信息如下, 分配的subnet10.230.93.1/24:

1
2
3
4
5
[root@node2 ~]# cat /run/flannel/subnet.env  
FLANNEL_NETWORK=10.230.0.0/16  
FLANNEL_SUBNET=10.230.93.1/24  
FLANNEL_MTU=1450  
FLANNEL_IPMASQ=false

我们来看 10.230.41.1710.230.93.2 发送数据包的过程。

10.230.93.210.230.41.17 不在同一二层网络,因而需要查找路由来决定由哪个设备发送到哪里。10.230.41.17 的路由如下:

1
2
3
4
[root@master1 ~]# kubectl exec -it busybox2-6f8fdb784d-r6ln2 -- ip route  
default via 10.230.41.1 dev eth0  
10.230.0.0/16 via 10.230.41.1 dev eth0  
10.230.41.0/24 dev eth0 scope link  src 10.230.41.17

匹配到默认路由,因而需要发送到网关 10.230.41.110.230.41.1 配置在网桥 cni0 上。内核通过 ARP 请求获得 10.230.41.1MAC 地址, 将数据包转发到 cni0 上。

1
2
3
4
5
6
7
[root@node1 ~]# ip addr show dev cni0  
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000  
    link/ether 86:99:b6:37:95:b2 brd ff:ff:ff:ff:ff:ff  
    inet 10.230.41.1/24 brd 10.230.41.255 scope global cni0  
       valid_lft forever preferred_lft forever  
    inet6 fe80::8499:b6ff:fe37:95b2/64 scope link  
       valid_lft forever preferred_lft forever

flanneld 在加入集群时会为每个其他节点生成一条 on-link 路由,on-link 路由表示是直连路由,匹配该条路由的数据包将触发 ARP 请求获取目的 IP 的 MAC 地址。在 node1 上查看路由信息:

1
2
[root@node1 ~]# ip route show dev flannel.1  
10.230.93.0/24 via 10.230.93.0 onlink

cni0 设备根据这条路由将数据包转给 vxlan 设备 flannel.1,并且接收端的 IP 地址为 10.230.93.0, 需要通过 ARP 获取 MAC 地址。

flannel.1 的信息如下, 可以看到没有开启 l2missl3miss:

1
2
3
4
[root@node1 ~]# ip -d link show flannel.1  
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default  
    link/ether a6:f7:8b:a4:60:b0 brd ff:ff:ff:ff:ff:ff promiscuity 0  
    vxlan id 1 local 10.240.0.101 dev eth1 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  

vxlan设备需要对接收到的数据包进行VXLAN协议封装。它需要知道对端10.230.93.0MAC地址。而flanneld在启动时已经根据从etcdkubernetes API获取到的信息写入到ARP表中:

1
2
[root@node1 ~]# ip neigh show dev flannel.1  
10.230.93.0 lladdr 2a:02:24:58:e9:07 PERMANENT  

这样获取到10.230.93.0MAC地址后,就可以完成内层数据的封装。数据包封装完成后,它需要获得对应这个MAC地址的VTEPIP地址。flanneld已经在启动时写入FDB条目:

1
2
[root@node1 ~]# bridge fdb show dev flannel.1  
2a:02:24:58:e9:07 dst 10.240.0.102 self permanent

可以看到2a:02:24:58:e9:07对应的VTEP IP10.240.0.102。这时flannel.1这个vxlan设备知道数据包要发送的目的IP,根据主机的路由策略从eth1设备发出。主机路由信息如下:

1
2
3
4
5
6
7
8
[root@node1 ~]# ip route  
default via 10.0.2.2 dev eth0  
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15  
10.230.41.0/24 dev cni0 proto kernel scope link src 10.230.41.1  
10.230.93.0/24 via 10.230.93.0 dev flannel.1 onlink  
10.240.0.0/24 dev eth1 proto kernel scope link src 10.240.0.101  
169.254.0.0/16 dev eth0 scope link metric 1002  
169.254.0.0/16 dev eth1 scope link metric 1003  

数据包到达node2eth1后,eth1将收到VXLAN数据包, 数据包中的MAC地址为:2a:02:24:58:e9:07, 正是node2节点上flannel.1的地址, 将它转给flannel.1设备:

1
2
3
4
5
6
7
[root@node2 ~]# ip addr show flannel.1  
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default  
    link/ether 2a:02:24:58:e9:07 brd ff:ff:ff:ff:ff:ff  
    inet 10.230.93.0/32 scope global flannel.1  
       valid_lft forever preferred_lft forever  
    inet6 fe80::2802:24ff:fe58:e907/64 scope link  
       valid_lft forever preferred_lft forever  

flannel.1解包之后,根据内层目的地址:10.240.93.2查找路由转发到cni0:

1
2
3
4
5
6
[root@node2 ~]# ip route  
default via 10.0.2.2 dev eth0 proto dhcp metric 100  
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100  
10.230.41.0/24 via 10.230.41.0 dev flannel.1 onlink  
10.230.93.0/24 dev cni0 proto kernel scope link src 10.230.93.1  
10.240.0.0/24 dev eth1 proto kernel scope link src 10.240.0.102 metric 101  

cni0再通过ARP请求获得10.230.93.2MAC地址,从而将数据包转发到相应的POD中的veth pair设备,从而到达容器中。

回包的路径是一样的,不再详述。

下面简要分析一下flanneld的源码实现。

main函数中首先调用newSubnetManager创建SubnetManager

1
2
3
4
5
6
sm, err := newSubnetManager()  
if err != nil {  
    log.Error("Failed to create SubnetManager: ", err)  
    os.Exit(1)  
}  
log.Infof("Created subnet manager: %s", sm.Name())  

SubnetManager用于向网络配置存储租用或续组subnet。每个节点都会有自己的一个subnet,保证了节点之间的IP不会冲突。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func newSubnetManager() (subnet.Manager, error) {  
    if opts.kubeSubnetMgr {  
        return kube.NewSubnetManager(opts.kubeApiUrl, opts.kubeConfigFile)  
    }  
  
    cfg := &etcdv2.EtcdConfig{  
        Endpoints: strings.Split(opts.etcdEndpoints, ","),  
        Keyfile:   opts.etcdKeyfile,  
        Certfile:  opts.etcdCertfile,  
        CAFile:    opts.etcdCAFile,  
        Prefix:    opts.etcdPrefix,  
        Username:  opts.etcdUsername,  
        Password:  opts.etcdPassword,  
    }  
  
    // Attempt to renew the lease for the subnet specified in the subnetFile  
    prevSubnet := ReadSubnetFromSubnetFile(opts.subnetFile)  
  
    return etcdv2.NewLocalManager(cfg, prevSubnet)  
}  

如果命令行参数中指定了kube-subnet-mgr, 则使用kubernetes API作为全局网络配置存储,否则使用etcd

接着调用getConfig从全局配置存储获取网络配置, 包括容器集群的网络信息,backend的配置等等:

1
2
3
4
5
6
// Fetch the network config (i.e. what backend to use etc..).  
config, err := getConfig(ctx, sm)  
if err == errCanceled {  
    wg.Wait()  
    os.Exit(0)  
}  

比如,我的实验环境写到etcd的配置内容为:

1
{"Network":"10.230.0.0/16","SubnetLen":24, "Backend":{"Type": "vxlan"}}  

接下来,main函数会调用backend.NewManager

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Create a backend manager then use it to create the backend and register the network with it.  
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, config)  
if err != nil {  
    log.Errorf("Error registering network: %s", err)  
    cancel()  
    wg.Wait()  
    os.Exit(1)  
}

开头时也介绍过,flannel通过backend机制来支持各种不同的跨主机通信方式。不同的实现方式会在init函数中向backend注册自己的构造函数。比如,package vxlaninit函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func init() {  
    backend.Register("vxlan", New)  
}  
  
const (  
    defaultVNI = 1  
)  
  
type VXLANBackend struct {  
    subnetMgr subnet.Manager  
    extIface  *backend.ExternalInterface  
}  
  
func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backend, error) {  
    backend := &VXLANBackend{  
        subnetMgr: sm,  
        extIface:  extIface,  
    }  
  
    return backend, nil  
}  

be.RegisterNetwork会调用到package vxlanRegisterNetwork:

 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
func (be *VXLANBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {  
    // Parse our configuration  
    cfg := struct {  
        VNI           int  
        Port          int  
        GBP           bool  
        DirectRouting bool  
    }{  
        VNI: defaultVNI,  
    }  
  
    if len(config.Backend) > 0 {  
        if err := json.Unmarshal(config.Backend, &cfg); err != nil {  
            return nil, fmt.Errorf("error decoding VXLAN backend config: %v", err)  
        }  
    }  
    log.Infof("VXLAN config: VNI=%d Port=%d GBP=%v DirectRouting=%v", cfg.VNI, cfg.Port, cfg.GBP, cfg.DirectRouting)  
  
    devAttrs := vxlanDeviceAttrs{  
        vni:       uint32(cfg.VNI),  
        name:      fmt.Sprintf("flannel.%v", cfg.VNI),  
        vtepIndex: be.extIface.Iface.Index,  
        vtepAddr:  be.extIface.IfaceAddr,  
        vtepPort:  cfg.Port,  
        gbp:       cfg.GBP,  
    }  
  
    dev, err := newVXLANDevice(&devAttrs)  
    if err != nil {  
        return nil, err  
    }  
    dev.directRouting = cfg.DirectRouting  
  
    subnetAttrs, err := newSubnetAttrs(be.extIface.ExtAddr, dev.MACAddr())  
    if err != nil {  
        return nil, err  
    }  
  
    lease, err := be.subnetMgr.AcquireLease(ctx, subnetAttrs)  
    switch err {  
    case nil:  
    case context.Canceled, context.DeadlineExceeded:  
        return nil, err  
    default:  
        return nil, fmt.Errorf("failed to acquire lease: %v", err)  
    }  
  
    // Ensure that the device has a /32 address so that no broadcast routes are created.  
    // This IP is just used as a source address for host to workload traffic (so  
    // the return path for the traffic has an address on the flannel network to use as the destination)  
    if err := dev.Configure(ip.IP4Net{IP: lease.Subnet.IP, PrefixLen: 32}); err != nil {  
        return nil, fmt.Errorf("failed to configure interface %s: %s", dev.link.Attrs().Name, err)  
    }  
  
    return newNetwork(be.subnetMgr, be.extIface, dev, ip.IP4Net{}, lease)  
}  

RegisterNetwork函数会调用newVXLANDevice创建一个vxlan设备,就对应我们实验环境中的flannel.1。从代码也可以看到flannel.1设备名中的1指的是VNI, 我们可以通过在全局配置存储中设置为其他值。然后获取本地VTEPIP地址以及vxlan设备的MAC地址填充到subnetAttrs结构调用be.subnetMgr.AcquireLease。这最终会调用到package etcdv2tryAcquireLeasetryAcquireLease则会调用m.registry.createSubnet或者m.registry.updateSubnet去向etcd中写入相应的Subnet信息,完成相应Subnet的租用。这时,如果已经有其他节点的flanneldwatch etcd上的subnets的key,则会触发添加路由、ARPFDB条目的逻辑。这个下面我们再详细描述具体实现。之后,调用dev.Configurevxlan设备配置一个掩码为32的地址防止广播路由创建。

RegisterNetwork返回后,main函数会调用WriteSubnetFile将获取到的网络信息写入subnetFile中,默认是/run/flannel/subnet.env,后续flanneld再启动时就会优先尝试使用这个文件中记录的信息去续组subnet:

1
2
3
4
5
6
if err := WriteSubnetFile(opts.subnetFile, config.Network, opts.ipMasq, bn); err != nil {  
    // Continue, even though it failed.  
    log.Warningf("Failed to write subnet file: %s", err)  
} else {  
    log.Infof("Wrote subnet file to %s", opts.subnetFile)  
}  

接着,main函数中启动一个goroutine去运行bn.Run:

1
2
3
4
5
6
7
// Start "Running" the backend network. This will block until the context is done so run in another goroutine.  
log.Info("Running backend.")  
wg.Add(1)  
go func() {  
    bn.Run(ctx)  
    wg.Done()  
}()  

这会调用到package vxlanRun实现,它会调用subnet.WatchLeases去获取全局范围的subnet情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 {  
        select {  
        case evtBatch := <-events:  
            nw.handleSubnetEvents(evtBatch)  
  
        case <-ctx.Done():  
            return  
        }  
    }  
}  

package subnetWatchLeases函数中会一直循环调用sm.WatchLeasessm.WatchLeases首次运行时会获取到当前etcd中已有的subnet信息,之后则开始watch etcdsubnets key获得变更的subnet信息。这些subnet信息传送给channel:receiver:

 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
func WatchLeases(ctx context.Context, sm Manager, ownLease *Lease, receiver chan []Event) {  
    lw := &leaseWatcher{  
        ownLease: ownLease,  
    }  
    var cursor interface{}  
  
    for {  
        res, err := sm.WatchLeases(ctx, cursor)  
        if err != nil {  
            if err == context.Canceled || err == context.DeadlineExceeded {  
                return  
            }  
  
            log.Errorf("Watch subnets: %v", err)  
            time.Sleep(time.Second)  
            continue  
        }  
  
        cursor = res.Cursor  
  
        var batch []Event  
  
        if len(res.Events) > 0 {  
            batch = lw.update(res.Events)  
        } else {  
            batch = lw.reset(res.Snapshot)  
        }  
  
        if len(batch) > 0 {  
            receiver <- batch  
        }  
    }  
}  

receiver的接收端协程则调用nw.handleSubnetEvents(evtBatch)来处理这些消息:

  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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
func (nw *network) handleSubnetEvents(batch []subnet.Event) {  
    for _, event := range batch {  
        sn := event.Lease.Subnet  
        attrs := event.Lease.Attrs  
        if attrs.BackendType != "vxlan" {  
            log.Warningf("ignoring non-vxlan subnet(%s): type=%v", sn, attrs.BackendType)  
            continue  
        }  
  
        var vxlanAttrs vxlanLeaseAttrs  
        if err := json.Unmarshal(attrs.BackendData, &vxlanAttrs); err != nil {  
            log.Error("error decoding subnet lease JSON: ", err)  
            continue  
        }  
  
        // This route is used when traffic should be vxlan encapsulated  
        vxlanRoute := netlink.Route{  
            LinkIndex: nw.dev.link.Attrs().Index,  
            Scope:     netlink.SCOPE_UNIVERSE,  
            Dst:       sn.ToIPNet(),  
            Gw:        sn.IP.ToIP(),  
        }  
        vxlanRoute.SetFlag(syscall.RTNH_F_ONLINK)  
  
        // directRouting is where the remote host is on the same subnet so vxlan isn't required.  
        directRoute := netlink.Route{  
            Dst: sn.ToIPNet(),  
            Gw:  attrs.PublicIP.ToIP(),  
        }  
        var directRoutingOK = false  
        if nw.dev.directRouting {  
            routes, err := netlink.RouteGet(attrs.PublicIP.ToIP())  
            if err != nil {  
                log.Errorf("Couldn't lookup route to %v: %v", attrs.PublicIP, err)  
                continue  
            }  
            if len(routes) == 1 && routes[0].Gw == nil {  
                // There is only a single route and there's no gateway (i.e. it's directly connected)  
                directRoutingOK = true  
            }  
        }  
  
        switch event.Type {  
        case subnet.EventAdded:  
            if directRoutingOK {  
                log.V(2).Infof("Adding direct route to subnet: %s PublicIP: %s", sn, attrs.PublicIP)  
  
                if err := 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 := nw.dev.AddARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {  
                    log.Error("AddARP failed: ", err)  
                    continue  
                }  
  
                if err := 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 := 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 := 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 subnet.EventRemoved:  
            if directRoutingOK {  
                log.V(2).Infof("Removing direct route to subnet: %s PublicIP: %s", sn, attrs.PublicIP)  
                if err := 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 := nw.dev.DelARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {  
                    log.Error("DelARP failed: ", err)  
                }  
  
                if err := nw.dev.DelFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {  
                    log.Error("DelFDB failed: ", err)  
                }  
  
                if err := netlink.RouteDel(&vxlanRoute); err != nil {  
                    log.Errorf("failed to delete vxlanRoute (%s -> %s): %v", vxlanRoute.Dst, vxlanRoute.Gw, err)  
                }  
            }  
        default:  
            log.Error("internal error: unknown event type: ", int(event.Type))  
        }  
    }  
}  

这里我们忽略directRouting相关内容。EventAdded表示有新的节点上线,首先调用nw.dev.AddARPvxlan设备添加ARP条目,MACIP分别为新上线节点上vxlan设备的MAC地址以及上面所配置的32位掩码的IP地址。接着调用nw.dev.AddFDBvxlan设备上添加FDB条目,MACIP分别为新上线节点上的vxlan设备的MAC地址以及新节点上的VTEP的IP地址。最后,再调用netlink.RouteReplace(&vxlanRoute)去添加经由32位掩码地址到达新上线subnet的路由。代码注释里也说明了,最后再添加路由是为了防止在ARP缓存没有填加的情况下发起ARP请求。

EventRemoved表示有节点下线,这里分别调用nw.dev.DelARP,nw.dev.DelFDB,netlink.RouteDel删除相应的ARP,FDB和路由条目。

main函数的逻辑里接下来还会调用MonitorLease去定期续租subnet,这里不再详述。