从其中三个method可以很明显的看出可以执行的的动作,每次CNI插件调用backendServer时, 就会调用ResoueceManager进行具体的分配释放操作。对于ENIMultiIP这种模式来说,具体的实现类是eniIPResourceManager:
type eniIPResourceManager struct { pool pool.ObjectPool }其中只有pool一个成员函数,具体的实现类型是simpleObjectPool, 该pool维护了当前所有的ENI信息。当resouceManager进行分配释放网络设备的时候其实是从该pool中进行存取即可:
func (m *eniIPResourceManager) Allocate(ctx *networkContext, prefer string) (types.NetworkResource, error) { return m.pool.Acquire(ctx, prefer) } func (m *eniIPResourceManager) Release(context *networkContext, resID string) error { if context != nil && context.pod != nil { return m.pool.ReleaseWithReverse(resID, context.pod.IPStickTime) } return m.pool.Release(resID) } func (m *eniIPResourceManager) GarbageCollection(inUseSet map[string]interface{}, expireResSet map[string]interface{}) error { for expireRes := range expireResSet { if err := m.pool.Stat(expireRes); err == nil { err = m.Release(nil, expireRes) if err != nil { return err } } } return nil }由上述代码可见,resouceManager实际操作的都是simpleObjectPool这个对象。 我们看看这个pool到底做了那些操作。首先初始化该pool:
// NewSimpleObjectPool return an object pool implement func NewSimpleObjectPool(cfg Config) (ObjectPool, error) { if cfg.MinIdle > cfg.MaxIdle { return nil, ErrInvalidArguments } if cfg.MaxIdle > cfg.Capacity { return nil, ErrInvalidArguments } pool := &simpleObjectPool{ factory: cfg.Factory, inuse: make(map[string]types.NetworkResource), idle: newPriorityQueue(), maxIdle: cfg.MaxIdle, minIdle: cfg.MinIdle, capacity: cfg.Capacity, notifyCh: make(chan interface{}), tokenCh: make(chan struct{}, cfg.Capacity), } if cfg.Initializer != nil { if err := cfg.Initializer(pool); err != nil { return nil, err } } if err := pool.preload(); err != nil { return nil, err } log.Infof("pool initial state, capacity %d, maxIdle: %d, minIdle %d, idle: %s, inuse: %s", pool.capacity, pool.maxIdle, pool.minIdle, queueKeys(pool.idle), mapKeys(pool.inuse)) go pool.startCheckIdleTicker() return pool, nil }可以看到在创建的时候会根据传入的config依次初始化各成员变量, 其中
factory 成员用来分配网络设备,会调用ECS SDK进行分配资源,分配之后将信息存储在pool之中,具体的实现是eniIPFactory。
inuse 存储了当前所有正在使用的networkResource
idle 存储了当前所有空闲的networkResource, 即已经通过factory分配好,但是还未被某个pod实际使用。如果某个network resouce不再使用,也会归还到该idle之中。 通过这种方式,pool具备一定的缓充能力,避免频繁调用factory进行分配释放。idle为priorityQeueu类型,即所有空闲的networkResouce通过优先级队列排列,优先级队列的比较函数会比较reverse字段,reverse默认是入队时间,也就是该networkResouce的释放的时间,这样做能够尽量使一个IP释放之后不会被立马被复用。reverse字段对于一些statueSet的resouce也会进行一些特殊处理,因为statufulSet是有状态workload, 对于IP的释放也会特殊处理,保证其尽可能复用。
maxIdle, minIdle 分别表示上述idle队列中允许的最大和最小个数, minIdle是为了提供有一定的缓冲能力,但该值并不保证,最大是为了防止缓存过多,如果空闲的networkResouce太多没有被使用就会释放一部分,IP地址不止是节点级别的资源,也会占用整个vpc/vswitch/安全组的资源,太多的空闲可能会导致其他节点或者云产品分配不出IP。
capacity 是该pool的容量,最大能分配的networkResouce的个数。该值可以自己指定, 但如果超过该ECS能允许的最大个数就会被设置成允许的最大个数。
tokenCh 是个buffered channel, 容量大小即为上面capacity的值,被做token bucket。 pool初始化的时候会将其中放满元素,后面运行过程中中,只要能从该channel中读取到元素则意味着该pool还没有满。每次调用factory申请networkResouce之前会从该channel中读取一个元素, 每次调用factory释放networkDevice会从该channel中放入一个元素。之所有判断是否可读而不是是否可写是因为读操作可以通过select调用,防止阻塞,对于写操作如果没有容量之后就会阻塞在写channel的地方。
成员变量初始化完成之后会调用Initializer, 该函数会回调一个闭包函数,定义在newENIIPResourceManager中: 当程序启动时,resouceManager通过读取存储在本地磁盘也就是resouceDB中的信息获取当前正在使用的networkResouce,然后通过ecs获取当前所有eni设备及其ip, 依次遍历所有ip判断当前是否在使用,分别来初始化inuse和idle。这样可以保证程序重启之后可以重构内存中的pool数据信息。