深入剖析Flannel-创建网卡(2)

在上一篇只介绍到了backend对象创建完毕,但是并没有真正生成网卡。本篇将介绍flannel是如何创建网卡且对其进行配置。

一、注册网络

我们在main函数中得知,创建网卡调用的函数为RegisterNetwork。

/**
 * 注册网络
 * @param ctx 上下文
 * @param config 子网配置
 */
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,
    }
    // 创建VXLAN设备
    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
    }
    //获取租约 local_manager.go 实际上是向etcd存储信息网卡信息 默认有效时长为24h
    lease, err := be.subnetMgr.AcquireLease(ctx, subnetAttrs) // local_manager.go
    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)
    // 配置vxlan网卡ip并且设置成up状态 执行完这步骤 ifconfig才可以看到 之前只能通过ip link show查看
    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) //new结构体
}

上面函数逻辑比较简单,创建网卡、获取租约信息、vxlan配置ip、返回对象。这里不在深入介绍了。 

二、创建Vxlan设备

flannel调用newVXLANDevice函数进行vxlan设备创建,下面来看一下该函数具体是如何做到的?

2.1、newVXLANDevice

/**
 * 创建vxlan设备
 * @param devAttrs 设备属性
 * @return 返回vxlanDevice对象
 */
func newVXLANDevice(devAttrs *vxlanDeviceAttrs) (*vxlanDevice, error) {
    link := &netlink.Vxlan{
        LinkAttrs: netlink.LinkAttrs{
            Name: devAttrs.name,
        },
        VxlanId:      int(devAttrs.vni),
        VtepDevIndex: devAttrs.vtepIndex,
        SrcAddr:      devAttrs.vtepAddr,
        Port:         devAttrs.vtepPort,
        Learning:     false,
        GBP:          devAttrs.gbp,
    }
    //创建网卡 此处使用link单词 是和linux中ip命令行 link保持一致
    link, err := ensureLink(link)
    if err != nil {
        return nil, err
    }
    return &vxlanDevice{
        link: link,
    }, nil
}

 首先创建link对象,用于保存网卡设备信息,然后调用ensureLink进行网卡创建,最后返回vxLanDevice对象。流程比较简单,接下来看一下ensureLink实现内容。

2.2、ensureLink

/**
 * 创建网卡
 * @param vxlan vxlan信息
 */
func ensureLink(vxlan *netlink.Vxlan) (*netlink.Vxlan, error) {
    err := netlink.LinkAdd(vxlan) //link_linux.go
    if err == syscall.EEXIST {
        // it's ok if the device already exists as long as config is similar
        log.V(1).Infof("VXLAN device already exists")
        // 获取已有vxlan设备信息
        existing, err := netlink.LinkByName(vxlan.Name)
        if err != nil {
            return nil, err
        }
        // 比较新旧网卡信息
        incompat := vxlanLinksIncompat(vxlan, existing)
        if incompat == "" { // 表示完全相同 则使用已有的设备
            log.V(1).Infof("Returning existing device")
            return existing.(*netlink.Vxlan), nil
        }

        // delete existing 不相同则删除
        log.Warningf("%q already exists with incompatable configuration: %v; recreating device", vxlan.Name, incompat)
        if err = netlink.LinkDel(existing); err != nil {
            return nil, fmt.Errorf("failed to delete interface: %v", err)
        }

        // create new 创建新的vxlan设备
        if err = netlink.LinkAdd(vxlan); err != nil {
            return nil, fmt.Errorf("failed to create vxlan interface: %v", err)
        }
    } else if err != nil {
        return nil, err
    }
    // 根据索引进行查找设备
    ifindex := vxlan.Index
    link, err := netlink.LinkByIndex(vxlan.Index)
    if err != nil {
        return nil, fmt.Errorf("can't locate created vxlan device with index %v", ifindex)
    }

    var ok bool
    if vxlan, ok = link.(*netlink.Vxlan); !ok {
        return nil, fmt.Errorf("created vxlan device with index %v is not vxlan", ifindex)
    }

    return vxlan, nil
}

 说明:

1)通过netlink.LinkAdd函数进行网卡创建,该函数调用第三方库。如果netlink.LinkAdd返回值为syscall.EEXIST,则会再次进行处理。如果返回其他错误则直接退出

2)在调用netlink.LinkAdd函数时如果创建成功会对vxlan.Index进行赋值。通过唯一索引值用于管理设备对象。我们可以通过命令行ip link进行查看,显示数字就是这里的index。最后根据index再次进行查找设备对象,若查找成功则返回该对象。

3)这里说明一下netlink.LinkAdd是第三方库代码,由它去创建vxlan设备,内部实际使用的是系统调用。大家可自行查阅相关代码。

三、获取租约信息

获取租约信息(实际是获取ip)并且将其存储到etcd中,具体流程如下:

/**
 * 获取租约信息
 * @param ctx 上下文
 * @param attrs 属性信息
 * @return 返回租约信息
 */
func (m *LocalManager) AcquireLease(ctx context.Context, attrs *LeaseAttrs) (*Lease, error) {
    config, err := m.GetNetworkConfig(ctx) //获取配置信息
    if err != nil {
        return nil, err
    }

    for i := 0; i < raceRetries; i++ {
        l, err := m.tryAcquireLease(ctx, config, attrs.PublicIP, attrs)
        switch err {
        case nil:
            return l, nil
        case errTryAgain:
            continue
        default:
            return nil, err
        }
    }

    return nil, errors.New("Max retries reached trying to acquire a subnet")
}

通过GetNetworkConfig向etcd进行获取查询,然后根据配置尝试获取租约信息。接下来看一下tryAcquireLease函数具体实现,由于该函数内容较长,我们分三部分进行说明:

/**
 * 获取租约
 * @param ctx 上下文
 * @param config 配置信息
 * @param extIaddr 外部ip
 * @param attrs 租约属性信息
 * @return 返回租约对象
 */
func (m *LocalManager) tryAcquireLease(ctx context.Context, config *Config, extIaddr ip.IP4, attrs *LeaseAttrs) (*Lease, error) {
    // 获取租约信息 实际是向etcd获取子网信息
    leases, _, err := m.registry.getSubnets(ctx) // registry.go getSubnets
    if err != nil {
        return nil, err
    }

    // Try to reuse a subnet if there's one that matches our IP
    // 判断是否存已经创建的租约信息 这里查找实际是数组查找
    if l := findLeaseByIP(leases, extIaddr); l != nil {
        // Make sure the existing subnet is still within the configured network
        if isSubnetConfigCompat(config, l.Subnet) {
            log.Infof("Found lease (%v) for current IP (%v), reusing", l.Subnet, extIaddr)

            ttl := time.Duration(0)
            if !l.Expiration.IsZero() {
                // Not a reservation
                ttl = subnetTTL
            }
            exp, err := m.registry.updateSubnet(ctx, l.Subnet, attrs, ttl, 0) //更新子网信息
            if err != nil {
                return nil, err
            }

            l.Attrs = *attrs
            l.Expiration = exp
            return l, nil
        } else { // 删除已有子网信息
            log.Infof("Found lease (%v) for current IP (%v) but not compatible with current config, deleting", l.Subnet, extIaddr)
            if err := m.registry.deleteSubnet(ctx, l.Subnet); err != nil {
                return nil, err
            }
        }
    }

说明:

1)首先调用getSubnets函数向etcd查询所有注册的子网信息,并且返回lease集合

2)然后在lease中遍历查找是否存在子网信息,如果找到了则继续判断是否兼容,如果兼容则进行更新操作updateSubnet反之进行删除。

接下这段代码和上面功能很类似,处理逻辑也是一样的,只不过数据来源不一样:

// no existing match, check if there was a previous subnet to use
// 预先分配的子网 在main.go中函数newSubnetManager进行创建
var sn ip.IP4Net
if !m.previousSubnet.Empty() {
    // use previous subnet
    if l := findLeaseBySubnet(leases, m.previousSubnet); l != nil {
        // Make sure the existing subnet is still within the configured network
        if isSubnetConfigCompat(config, l.Subnet) {
            log.Infof("Found lease (%v) matching previously leased subnet, reusing", l.Subnet)

            ttl := time.Duration(0)
            if !l.Expiration.IsZero() {
                // Not a reservation
                ttl = subnetTTL
            }
            exp, err := m.registry.updateSubnet(ctx, l.Subnet, attrs, ttl, 0)
            if err != nil {
                return nil, err
            }

            l.Attrs = *attrs
            l.Expiration = exp
            return l, nil
        } else {
            log.Infof("Found lease (%v) matching previously leased subnet but not compatible with current config, deleting", l.Subnet)
            if err := m.registry.deleteSubnet(ctx, l.Subnet); err != nil {
                return nil, err
            }
        }
    } else {
        // Check if the previous subnet is a part of the network and of the right subnet length
        if isSubnetConfigCompat(config, m.previousSubnet) {
            log.Infof("Found previously leased subnet (%v), reusing", m.previousSubnet)
            sn = m.previousSubnet
        } else {
            log.Errorf("Found previously leased subnet (%v) that is not compatible with the Etcd network config, ignoring", m.previousSubnet)
        }
    }
}

 这里数据来源就是/run/flannel/subnet.env。如果以上两种场景都不满足,则表示需要创建一个子网:

    if sn.Empty() {
        // no existing match, grab a new one 创建一个新的subnets
        sn, err = m.allocateSubnet(config, leases)
        if err != nil {
            return nil, err
        }
    }
    // 实际是向etcd存储信息 存活时间是24h 这样etcd中就有subnets信息
    exp, err := m.registry.createSubnet(ctx, sn, attrs, subnetTTL)
    switch {
    case err == nil:
        log.Infof("Allocated lease (%v) to current node (%v) ", sn, extIaddr)
        return &Lease{
            Subnet:     sn,
            Attrs:      *attrs,
            Expiration: exp,
        }, nil
    case isErrEtcdNodeExist(err):
        return nil, errTryAgain
    default:
        return nil, err
    }
}

 注意这里创建完子网信息后需要写入到etcd中-调用createSubnet函数。

四、总结

至此,vxlan网卡创建、配置均已经介绍完毕了。后续只需要启动该网络设备即可。是不是比较简单呢?但是我们有一项工作没有说清楚,当有新的容器加入网络或者移除网络,那么flannel是如何感知的呢?请看下一篇《深入剖析Flannel-监控》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值