技术干货分享 | Calico IPAM源码解析


导语:
Calico是一个纯三层的方案,为虚拟机及容器提供多主机间通信。Calico的网络传输性能主要受底层网络、路由表、IPIP模块的影响。那么IP地址分配的性能有哪些问题要考虑呢?
在大规模集群的场景下,Calico IP地址的分配速率是否受到集群规模的限制?IP地址和Block size怎么配置才能保持高速的IP地址分配?另外,Calico的IP地址在Node节点异常时,IP地址如何回收?什么时候有可能产生IP地址冲突?为了解答这些疑问,需要熟悉CalicoIP地址分配的执行流程。
本文主要针对calico v3.12版本,datastore为k8s的代码进行分析。

主要对象简介

BlockAffinity:
Ø 存储block和Node的亲和关系
1,jpg.png


Block:
Ø 维护已分配的IP地址和未分配的IP地址,Allocation数组,Uanllocated数组
Ø 维护block中运行的Pod
2.png


IPAMHandle:
Ø 保存pod与block的对应关系,在释放IP的过程中用于查找block
Ø HandleID由网络名和容器ID组合而成

IP Pool:
Ø 包括cidr内的所有IP
Ø 根据blocksize被划分为多个block
Ø 通过nodeSelector选择亲和的节点
3.png


Calico-IPAM流程图

分配IP
分配指定IP
4.png


自动分配IP
5.png


释放IP
6.png


源码分析

分配IP(cmdAdd)
在创建pod时,CNI插件会调用IPAM的cmdAdd函数进行IP地址的分配。CNI将配置文件从标准输入发送给IPAM,IPAM从中读取相关的配置信息,根据是否指定了IP地址,IPAM会采用两种不同的IP分配方式:
l 分配指定IP:pod配置中指定了需要使用的IP地址,接下来会调用AssignIP函数,尝试将指定的IP地址分配给pod。
7.png


自动分配IP:pod配置中没有指定IP地址,则尝试读取配置中的IP池信息,List集群内所有的IP池对象,遍历匹配IP池的名称或CIDR。获取匹配的IP池(可能为空)后,调用AutoAssign函数进行IP分配。
8.png


分配指定IP(AssignIP)
首先根据参数内的IP地址,List集群内所有状态为enable的IP池对象,遍历查找包含请求的IP地址的IP池,然后根据IP池的blocksize计算出IP地址对应的block cidr。
例如10.10.0.17位于IP池10.10.0.0/24(blocksize=28)中,对应的block cidr为10.10.0.16/28。
9.png


接下来使用block cidr查询对应的block:
l 若查询结果为空,即block不存在,则调用getPendingAffinity函数(获取未决亲和)查询或创建block与当前节点的block affinity,然后创建此block并尝试声明此亲和性。声明成功后,从新创建的block中分配指定的IP。如果block已经被其他节点声明了亲和性(被其他节点抢先创建并声明了亲和性),或者block的数据正处于更新中,那么IPAM会自动进行重试(上限为100次)。
l 若查询到了对应的block,则尝试从block中分配指定的IP。
分配IP成功后,创建IP地址的handle id(网络名 容器名)留待释放IP时使用,最后更新block的数据,结束IP分配流程。
10.png


获取未决亲和关系(getPendingAffinity)
该函数使用传入的节点名和block cidr创建或获取pending block affinity。
l 如果创建成功,则直接返回创建的pending block affinity。
l 如果创建失败,则表明该block affinity已存在。通过api查询到该block affinity,将其状态修改为pending(处理中)后,更新它的状态,并返回修改后的pending block affinity。
11.png


声明亲和块(claimAffineBlock)
该函数使用传入的pending block affinity结构体尝试创建或获取对应的block。
首先使用pending block affinity中的cidr构建block对象,发送创建block的请求。
l 如果创建成功,则将pending block affinity的状态更新为confirmed(已确认),返回block。
l 如果创建失败,则表明该block已存在。通过api查询获得block对象,检验其是否与当前节点亲和。
n 如果亲和性匹配,则确认该pending block affinity,返回block。
n 如果亲和性不匹配,则需要删除该pending block affinity,并返回声明冲突错误。
12.png


从块中分配IP(block.assign)
该函数尝试从block中分配一个指定的IP地址。
首先会校验block与节点的亲和性,在设置了StrictAffinity参数后,block必须与节点亲和才能够进行IP分配,否则会报错(亲和不匹配或无亲和)。
13.png


之后将IP地址转为序数(block的allocations数组的下标)。
l 若此IP已被分配,则直接返回错误。
l 若此IP未分配,则将handle id等属性添加到allocations数组的对应下标处。
最后遍历unallocated列表,找到刚才分配的IP序数后,将其去除,使用列表的后续项进行补位。
14.png


自动分配IP(AutoAssign)
自动分配IP的流程相比分配指定IP的流程复杂许多,主要分为四部分流程,第一部分为回收工作,每次都会执行;而后续的三个部分则是顺序执行,一旦在某个部分中获取了足够数量的IP地址就会直接返回,忽略后面的部分流程。
1. 释放与当前节点亲和、未被IP池使用的空块;
2. 从当前节点的亲和块分配IP;
3. 创建新的亲和块分配IP(可通过配置开启或关闭此流程);
4. 从所有可能的IP池中的任意块分配IP,忽略亲和性(可通过配置开启或关闭此流程)。
分配前准备
在这四部分流程之前,首先要确定使用的IP池、可用的亲和块和待释放的亲和块。
15.png


确定IP池(determinePools
该函数使用list接口获取了所有开启状态的IP池,并使用cidr进行索引。
l 如果在配置文件中指定的IP池cidr无法查找到对应的IP池,就直接返回;否则将指定的IP池添加到requestedPools列表中,作为后续流程使用的IP池。
l 如果配置文件中没有指定IP池,那么所有状态为enable,且选择了当前节点的IP池将作为后续流程使用的IP池。
16.png


获取亲和块(getAffineBlocks)
该函数list了所有blockAffinity对象,遍历进行如下判断:
l 如果上一步返回的IP池列表为空,则将所有blockAffinity的cidr加入IP池内的亲和块cidr列表,用于后续分配IP;
l 如果上一步返回了非空的IP池列表,则遍历blockAffinity进行判断
n 如果blockAffinity的block cidr属于上一步确定的任意一个IP池,则将其加入IP池内的亲和块cidr列表,用于后续分配IP;
n 如果blockAffinity的block cidr不属于上一步确定的任意一个IP池,则将其加入IP池外的亲和块cidr列表,将在下一部分流程中被回收。
17.png


第一部分:释放空亲和块
这部分流程负责释放已经无人使用的blockAffinity。这些blockAffinity由getAffineBlocks确定,不属于当前使用的IP池,对每一个待释放的blockAffinity执行如下操作:
l 首先list所有的IP池,根据blockAffinity的cidr遍历查找它所属的IP池;
l 如果该IP池选择了当前节点,则不释放blockAffinity,否则调用releaseBlockAffinity函数进行释放。释放操作返回块声明冲突、块非空或是块不存在错误时,跳过当前块,处理下一块;出现其他错误时,会进行100次以内的重试。
18.png


释放亲和关系(releaseBlockAffinity)
该函数将释放给定的block与节点的blockAffinity。
l 首先使用block cidr和节点名查询blockAffinity,并使用block cidr获取block对象;
l 若block不与当前节点亲和,则删除此block与当前节点的blockAffinity,返回块声明冲突错误;
l 若block非空(有IP正在被使用),则返回block非空错误;
l 将blockAffinity的状态更新为等待删除,删除block后再删除blockAffinity。
19.png


第二部分:从现存亲和块分配IP
释放了空的亲和块后,IPAM会尝试使用获取的blockAffinity分配IP,如果遍历了所有的亲和块仍未分配足够的IP,则进入下一部分,否则直接返回。分配过程具体如下:
l 首先使用blockAffinity的cidr和节点名获取blockAffinity对象;
l 再通过blockAffinity获取对应的block;
l 尝试从block中分配指定数量的IP,assignFromExistingBlock函数主要调用了从块中自动分配IP函数,分配完成后会添加对应的handle。
20.png


从亲和关系获取块(getBlockFromAffinity)
该函数根据blockAffinity获取对应的block。
l 首先根据blockAffinity的cidr获取block;
n 如果block不存在,则将blockAffinity的状态更新为pending,然后调用claimAffineBlock函数创建此block并确认blockAffinity,直接返回;
l 如果block的affinity属性为空,或是与blockAffinity不匹配,则删除blockAffinity(已过期);
l 如果blockAffinity的状态未确认,则更新block后确认blockAffinity。
21.png


从块中自动分配IP(block.autoAssign)
该函数与从块中分配IP类似,能随机分配指定数量的IP地址。
l 如果开启了strictAffinity或是affinityCheck选项,则需要检查block是否与当前节点亲和;
l 随后从block的unallocated数组中取出指定数量的序数(不一定有序);
l 遍历ordinal数组,将handle ID等属性添加到allocation数组对应下标处;
22.png


第三部分:创建新的亲和块并分配IP
如果开启了AutoAllocateBlocks选项,则可以新建block进行分配,否则只能将已分配好的(数量不足的)IP直接返回。
l 首先在给定的IP池范围内,随机找到一个未声明的block cidr;
l 根据block cidr获取当前节点的pending affinity;(参见分配指定IP)
l 使用pending affinity获取block;(参见第二部分)
l 如果这两步都成功,则认为成功创建了block,可以从中分配IP。
23.png


第四部分:从任意块分配IP
如果第三部分流程执行完后仍未分配足够的IP地址,则需要进入第四部分。如果开启了StrictAffinity的选项,则直接返回已分配的IP,未开启则能尝试从其他不与节点亲和的块中分配IP。
主要流程与第三部分相似,在指定的IP池中随机获取block。但不论block是否已被声明,是否与节点亲和,都尝试从其中分配IP。
25.png


释放IP(cmdDel)

删除pod时,CNI调用IPAM的cmdDel函数来释放pod的IP。首先使用网络名和容器ID构建出handle id,这是分配IP时记录下来的句柄,通过它释放pod使用的IP地址。
26.png


根据句柄释放IP(releaseByHandle)
l 首先使用handle id查询对应的block cidr(在ReleaseByHandle函数中获得,随后调用releaseByHandle函数);
l 通过block cidr查找block;
n 如果block不存在,则认为IP已被释放,直接返回;
l 根据handle id从block中释放指定的IP;
n 释放完成后如果block为空且不存在blockAffinity,则直接删除block;
n 否则更新block内容;
l 移除对应的handle;
l 确保block的亲和性与IP池一致。
27.png


从块中根据句柄释IP(block.releaseByHandle)
该函数主要操作block对象的allocation和unallocated数组,将handle指定的IP释放。
l 首先根据handle id找到需要释放的IP的序数;
l 遍历allocation数组,获取已分配的需释放的IP的序数;
l 删除这些序数对应的属性;
l 将所有序数加入unallocated数组;
29.png


确保一致亲和性(ensureConsistentAffinity)
该函数用于在释放IP后校验“block所属的IP池”是否选择了“block亲和的节点”,如果IP池已不再选择该节点,则需清理blockAffinity对象。
l 获取block亲和的节点名,若节点名为空,则无需清理;
l 否则使用节点名获取节点对象,使用block cidr获取IP池对象;
l 若IP池为空,或IP池选择了该节点,则无需清理;
l 否则调用releaseBlockAffinity函数清理blockAffinity(参见第一部分)
30.png


总结
Calico 在分配IP地址时,会先寻找当前Node亲和的IP Block,然后在IP Block中给Pod分配IP,Blocksize默认为26。Blocksize不宜太大,会影响到Calico在Block中查找可用IP地址的性能。
如果没有找到亲和的IP Block,会在尝试申请新的IP Block,一个IPPool中的IPBlock不能太多太小,Calico逐个尝试申请IP Block会逐个去get 操作,太多次Get也影响IP地址分配速率。
如果不能申请新的IP Block,Calico会去apiserver查询其他Node亲和的IPBlock,此时会产生IP地址借用的情况 ,借用的过程也是一个多次尝试的过程,如果其他大部分的Block处于饱和状态,而且IP地址非常紧张,这个过程也会有一定的性能损耗。

本文为谐云原创,如需转载请标明出处。
更多技术干货请关注
qrcode_for_gh_136b7c2326c4_258.jpg
24.png

0 个评论

要回复文章请先登录注册