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

在上一篇介绍了flannel启动流程(main函数),只把整体流程介绍了一下并没有深入探讨,接下来几篇博客会深入介绍flannel到底做了哪些事件。今天就介绍创建网卡流程。

一、创建子网管理对象

1.1、创建管理对象

flannel自身没有存储功能,所以它要依赖第三方存储系统,例如etcd、kube api。这里以etcd为例子进行分析。在上一篇提到创建子网管理对象调用的函数为newSubnetManager()。

/**
 * 创建子网管理对象
 * @param 无
 * @return subnet.Manager 子网管理对象
 * @return error 错误信息对象
 */
func newSubnetManager() (subnet.Manager, error) {
    if opts.kubeSubnetMgr { // 如果kubeSubnetMgr是true则表示采用kubenets方式管理网络
        return kube.NewSubnetManager(opts.kubeApiUrl, opts.kubeConfigFile)
    }
    // 采用etcd方式管理网络 etcd相关配置信息
    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
    // 读取配置文件 获取子网配置信息 在获取网络租约时 会使用到在local_manager.go 函数tryAcquireLease
    // opts.subnetFile 默认路径是/run/flannel/subnet.env
    prevSubnet := ReadSubnetFromSubnetFile(opts.subnetFile)

    return etcdv2.NewLocalManager(cfg, prevSubnet)
}

说明:

1)opts.etcdEndPoints,是在启动flannel通过命令行参数传进来的。该参数是必须参数,例如:etcd-endpoints=http://192.63.63.70:2379。

2)ReadSubnetFormSubnetFile,第一次启动时不会该配置文件。在上一篇也有相关代码去写该配置文件。此配置文件主要保证flannel重启后获取的子网ip段与重启前是一致的。该配置文件内容如下:

FLANNEL_NETWORK=172.17.0.0/16
FLANNEL_SUBNET=172.17.9.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=false

3)调用etcd相关接口创建管理对象。

1.2、初始化etcd管理对象

func NewLocalManager(config *EtcdConfig, prevSubnet ip.IP4Net) (Manager, error) {
    r, err := newEtcdSubnetRegistry(config, nil) // 生成Registry对象 registry.go
    if err != nil {
        return nil, err
    }
    return newLocalManager(r, prevSubnet), nil
}

func newLocalManager(r Registry, prevSubnet ip.IP4Net) Manager {
    return &LocalManager{
        registry:       r,
        previousSubnet: prevSubnet,
    }
}

这里生产registry对象,newEtcdSubnetRegistry本质工作是设置与etcd连接配置,最后调用newLocalManager创建对象返回。

1.3、设置连接参数

newEtcdSubnetRegistry主要是设置与etcd通信相关参数,并没有真正发起连接。这部分内容逻辑比较简单,不深入说明,只需要知道registry.cli是客户端对象,当需要与etcd进行通信时就需要使用此对象。此处将相关代码罗列一下:

func newEtcdClient(c *EtcdConfig) (etcd.KeysAPI, error) {
    tlsInfo := transport.TLSInfo{
        CertFile: c.Certfile,
        KeyFile:  c.Keyfile,
        CAFile:   c.CAFile,
    }

    t, err := transport.NewTransport(tlsInfo, time.Second)
    if err != nil {
        return nil, err
    }

    cli, err := etcd.New(etcd.Config{
        Endpoints: c.Endpoints,
        Transport: t,
        Username:  c.Username,
        Password:  c.Password,
    })
    if err != nil {
        return nil, err
    }

    return etcd.NewKeysAPI(cli), nil
}

func newEtcdSubnetRegistry(config *EtcdConfig, cliNewFunc etcdNewFunc) (Registry, error) {
    r := &etcdSubnetRegistry{
        etcdCfg:      config,
        networkRegex: regexp.MustCompile(config.Prefix + `/([^/]*)(/|/config)?$`),
    }
    //回调函数设置
    if cliNewFunc != nil {
        r.cliNewFunc = cliNewFunc
    } else {
        r.cliNewFunc = newEtcdClient //并没有发起 tcp连接只是创建对象 用于后续使用
    }

    var err error
    r.cli, err = r.cliNewFunc(config)
    if err != nil {
        return nil, err
    }

    return r, nil
}

二、创建backend

2.1、获取backend相关配置

众所周知,flannel支持backend有多种,到底使用哪种backend呢?需要提前规划好且需要先存储到etcd中。在启动flannel时从etcd中获取相关配置。所以在main函数中有getConfig函数,该函数就是用于获取数据。

/**
 * 获取网络配置 (每一秒获取一次 直到成功)
 * @param ctx 上下文
 * @param sm  子网管理对象
 */
func getConfig(ctx context.Context, sm subnet.Manager) (*subnet.Config, error) {
    // Retry every second until it succeeds
    for {
        //通过etcd获取数据
        config, err := sm.GetNetworkConfig(ctx) // local_manager.go
        if err != nil {
            log.Errorf("Couldn't fetch network config: %s", err)
        } else if config == nil {
            log.Warningf("Couldn't find network config: %s", err)
        } else {
            log.Infof("Found network config - Backend type: %s", config.BackendType)
            return config, nil
        }
        select {
        case <-ctx.Done(): //当调用cancle()或者超时后 这里就返回了
            return nil, errCanceled
        case <-time.After(1 * time.Second):
            fmt.Println("timed out")
        }
    }
}

这里是一个死循环,只有获取到配置后才会退出,所以需要再次强调:再启动flannel时必须先把配置存储到etcd中。所以现在关键就是GetNetworkConfig,下面来看一下该函数具体做了哪些工作。

func (esr *etcdSubnetRegistry) getNetworkConfig(ctx context.Context) (string, error) {
    key := path.Join(esr.etcdCfg.Prefix, "config")
    resp, err := esr.client().Get(ctx, key, &etcd.GetOptions{Quorum: true}) //向etcd获取数据
    if err != nil {
        return "", err
    }
    return resp.Node.Value, nil
}

func (m *LocalManager) GetNetworkConfig(ctx context.Context) (*Config, error) {
    /**
     * registry.go文件
     * registry是实际指向etcd 下面通过getNetworkConfig实际是发送http请求到etcd,
     * 从etcd中获取数据
     */
    cfg, err := m.registry.getNetworkConfig(ctx) //此处的registry对象是上面NewLocalManager函数中创建的
    if err != nil {
        return nil, err
    }

    return ParseConfig(cfg) // 调用config.go文件中ParseConfig函数
}

 最终调用getNetworkConfig函数,该函数调用client().Get()发送http请求到etcd获取数据。这里采用的是代码方式,其实我们完全可以通过curl或者postman发送http请求到etcd中,关于如何使用postman操作etcd可参考本篇

2.2、创建backend

在上面流程中获取到配置,然后就可以根据配置创建backend对象。

func NewManager(ctx context.Context, sm subnet.Manager, extIface *ExternalInterface) Manager {
    return &manager{
        ctx:      ctx,
        sm:       sm,
        extIface: extIface,
        active:   make(map[string]Backend),
    }
}

/**
 * 获取Backend对象
 * @param  backendType 类型
 * @return Backend对象
 */
func (bm *manager) GetBackend(backendType string) (Backend, error) {
    bm.mux.Lock()
    defer bm.mux.Unlock()
    // betype 一般是udp、vxlan、hostgw
    betype := strings.ToLower(backendType)
    // see if one is already running 表示已经存在backend
    if be, ok := bm.active[betype]; ok {
        return be, nil
    }

    // first request, need to create and run it
    // 假设betype为vxlan则befunc为vxlan.go文件下的New函数
    befunc, ok := constructors[betype]
    if !ok {
        return nil, fmt.Errorf("unknown backend type: %v", betype)
    }

    be, err := befunc(bm.sm, bm.extIface) // befunc实际指向New函数
    if err != nil {
        return nil, err
    }
    bm.active[betype] = be //设置缓存

    //设置同步 并 启动协程 等待context结束操作
    bm.wg.Add(1)
    go func() {
        <-bm.ctx.Done() //阻塞在这里

        // TODO(eyakubovich): this obviosly introduces a race.
        // GetBackend() could get called while we are here.
        // Currently though, all backends' Run exit only
        // on shutdown

        bm.mux.Lock()
        delete(bm.active, betype)
        bm.mux.Unlock()

        bm.wg.Done()
    }()

    return be, nil
}

上面主要流程在GetBackend中,为了提升性能,flannel创建了一个map用于保存当前backend。具体创建backend函数为befunc函数指针,如果是vxlan类型befunc实际执行为New方法,具体如下:

/**
 * 创建backend对象
 * @param sm 子网管理对象
 * @param extIface 外部接口
 * @return 返回backend对象
 */
func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backend, error) {
    backend := &VXLANBackend{
        subnetMgr: sm,
        extIface:  extIface,
    }
    return backend, nil
}

至此backend就创建完成了。很多内容我在代码注释中已经充分说明,大家可仔细阅读相关注释。

三、总结

创建网卡流程比较繁琐,由于篇幅原因,这里分成两篇。请看下一篇《深入剖析Flannel-创建网卡(2)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值