技术干货分享 | 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的亲和关系

Block: 

Ø 维护已分配的IP地址和未分配的IP地址,Allocation数组,Uanllocated数组

Ø 维护block中运行的Pod

IPAMHandle:

Ø 保存pod与block的对应关系,在释放IP的过程中用于查找block

Ø HandleID由网络名和容器ID组合而成

IP Pool:

Ø 包括cidr内的所有IP

Ø 根据blocksize被划分为多个block

Ø 通过nodeSelector选择亲和的节点

Calico-IPAM流程图

分配IP

分配指定IP

自动分配IP

释放IP
源码分析

分配IP(cmdAdd)

在创建pod时,CNI插件会调用IPAM的cmdAdd函数进行IP地址的分配。CNI将配置文件从标准输入发送给IPAM,IPAM从中读取相关的配置信息,根据是否指定了IP地址,IPAM会采用两种不同的IP分配方式:

l 分配指定IP:pod配置中指定了需要使用的IP地址,接下来会调用AssignIP函数,尝试将指定的IP地址分配给pod。

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

分配指定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。

接下来使用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分配流程。

获取未决亲和关系(getPendingAffinity)

该函数使用传入的节点名和block cidr创建或获取pending block affinity。

l 如果创建成功,则直接返回创建的pending block affinity。

l 如果创建失败,则表明该block affinity已存在。通过api查询到该block affinity,将其状态修改为pending(处理中)后,更新它的状态,并返回修改后的pending block affinity。

声明亲和块(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,并返回声明冲突错误。

从块中分配IP(block.assign)

该函数尝试从block中分配一个指定的IP地址。

首先会校验block与节点的亲和性,在设置了StrictAffinity参数后,block必须与节点亲和才能够进行IP分配,否则会报错(亲和不匹配或无亲和)。

之后将IP地址转为序数(block的allocations数组的下标)。

l 若此IP已被分配,则直接返回错误。

l 若此IP未分配,则将handle id等属性添加到allocations数组的对应下标处。

最后遍历unallocated列表,找到刚才分配的IP序数后,将其去除,使用列表的后续项进行补位。

自动分配IP(AutoAssign)

自动分配IP的流程相比分配指定IP的流程复杂许多,主要分为四部分流程,第一部分为回收工作,每次都会执行;而后续的三个部分则是顺序执行,一旦在某个部分中获取了足够数量的IP地址就会直接返回,忽略后面的部分流程。

1. 释放与当前节点亲和、未被IP池使用的空块;

2. 从当前节点的亲和块分配IP;

3. 创建新的亲和块分配IP(可通过配置开启或关闭此流程);

4. 从所有可能的IP池中的任意块分配IP,忽略亲和性(可通过配置开启或关闭此流程)。

分配前准备

在这四部分流程之前,首先要确定使用的IP池、可用的亲和块和待释放的亲和块。

确定IP池(determinePools)

该函数使用list接口获取了所有开启状态的IP池,并使用cidr进行索引。

l 如果在配置文件中指定的IP池cidr无法查找到对应的IP池,就直接返回;否则将指定的IP池添加到requestedPools列表中,作为后续流程使用的IP池。

l 如果配置文件中没有指定IP池,那么所有状态为enable,且选择了当前节点的IP池将作为后续流程使用的IP池。

获取亲和块(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列表,将在下一部分流程中被回收。

第一部分:释放空亲和块

这部分流程负责释放已经无人使用的blockAffinity。这些blockAffinity由getAffineBlocks确定,不属于当前使用的IP池,对每一个待释放的blockAffinity执行如下操作:

l 首先list所有的IP池,根据blockAffinity的cidr遍历查找它所属的IP池;

l 如果该IP池选择了当前节点,则不释放blockAffinity,否则调用releaseBlockAffinity函数进行释放。释放操作返回块声明冲突、块非空或是块不存在错误时,跳过当前块,处理下一块;出现其他错误时,会进行100次以内的重试。

释放亲和关系(releaseBlockAffinity)

该函数将释放给定的block与节点的blockAffinity。

l 首先使用block cidr和节点名查询blockAffinity,并使用block cidr获取block对象;

l 若block不与当前节点亲和,则删除此block与当前节点的blockAffinity,返回块声明冲突错误;

l 若block非空(有IP正在被使用),则返回block非空错误;

l 将blockAffinity的状态更新为等待删除,删除block后再删除blockAffinity。

第二部分:从现存亲和块分配IP

释放了空的亲和块后,IPAM会尝试使用获取的blockAffinity分配IP,如果遍历了所有的亲和块仍未分配足够的IP,则进入下一部分,否则直接返回。分配过程具体如下:

l 首先使用blockAffinity的cidr和节点名获取blockAffinity对象;

l 再通过blockAffinity获取对应的block;

l 尝试从block中分配指定数量的IP,assignFromExistingBlock函数主要调用了从块中自动分配IP函数,分配完成后会添加对应的handle。

从亲和关系获取块(getBlockFromAffinity)

该函数根据blockAffinity获取对应的block。

l 首先根据blockAffinity的cidr获取block;

n 如果block不存在,则将blockAffinity的状态更新为pending,然后调用claimAffineBlock函数创建此block并确认blockAffinity,直接返回;

l 如果block的affinity属性为空,或是与blockAffinity不匹配,则删除blockAffinity(已过期);

l 如果blockAffinity的状态未确认,则更新block后确认blockAffinity。

从块中自动分配IP(block.autoAssign)

该函数与从块中分配IP类似,能随机分配指定数量的IP地址。

l 如果开启了strictAffinity或是affinityCheck选项,则需要检查block是否与当前节点亲和;

l 随后从block的unallocated数组中取出指定数量的序数(不一定有序);

l 遍历ordinal数组,将handle ID等属性添加到allocation数组对应下标处;

第三部分:创建新的亲和块并分配IP

如果开启了AutoAllocateBlocks选项,则可以新建block进行分配,否则只能将已分配好的(数量不足的)IP直接返回。

l 首先在给定的IP池范围内,随机找到一个未声明的block cidr;

l 根据block cidr获取当前节点的pending affinity;(参见分配指定IP)

l 使用pending affinity获取block;(参见第二部分)

l 如果这两步都成功,则认为成功创建了block,可以从中分配IP。

第四部分:从任意块分配IP

如果第三部分流程执行完后仍未分配足够的IP地址,则需要进入第四部分。如果开启了StrictAffinity的选项,则直接返回已分配的IP,未开启则能尝试从其他不与节点亲和的块中分配IP。

主要流程与第三部分相似,在指定的IP池中随机获取block。但不论block是否已被声明,是否与节点亲和,都尝试从其中分配IP。

释放IP(cmdDel)

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

根据句柄释放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池一致。

从块中根据句柄释IP(block.releaseByHandle)

该函数主要操作block对象的allocation和unallocated数组,将handle指定的IP释放。

l 首先根据handle id找到需要释放的IP的序数;

l 遍历allocation数组,获取已分配的需释放的IP的序数;

l 删除这些序数对应的属性;

l 将所有序数加入unallocated数组;

确保一致亲和性(ensureConsistentAffinity)

该函数用于在释放IP后校验“block所属的IP池”是否选择了“block亲和的节点”,如果IP池已不再选择该节点,则需清理blockAffinity对象。

l 获取block亲和的节点名,若节点名为空,则无需清理;

l 否则使用节点名获取节点对象,使用block cidr获取IP池对象;

l 若IP池为空,或IP池选择了该节点,则无需清理;

l 否则调用releaseBlockAffinity函数清理blockAffinity(参见第一部分)

总结

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地址非常紧张,这个过程也会有一定的性能损耗。

本文为原创文章,转载需表明出处。

 

关于我们–谐云HARMONYCLOUD

     杭州谐云科技有限公司成立于2016年7月,公司核心团队来自于浙江大学SEL实验室,谐云团队在云计算及相关领域具备深厚的技术积淀,在全球顶级开源社区Docker、Kubernetes、Cloud Foundry等项目贡献累计超过200万行代码,排名全球第四,国内第一。团队曾著书中国第一本深度解析容器云的专业书籍《Docker容器与容器云》,是国内为数不多掌握底层核心技术的容器云提供商。建设了目前中国最大的容器集群落地案例,支撑着国内最大的互联网电视云……

K8S中文社区微信公众号

评论 抢沙发

登录后评论

立即登录