在上一篇介绍了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)》。