kubernetes源码-EndpointSlice Controller 原理和源码分析
EndpointSlice Controller 原理和源码分析,源码为 kubernetes 的 release-1.22 分支 .
写在前面
我们在之前的 kube-proxy 中有看到过 EndpointSlice ,那什么是 EndpointSlice ,具体什么作用?
-
在 Kubernetes v1.19 中,此功能将默认启用。
-
EndpointSlice 是一个新 API,它提供了 Endpoint API 可伸缩和可拓展的替代方案。EndpointSlice 会跟踪 Service Pod 的 IP 地址、端口、readiness 和拓扑信息。
-
EndpointSlice API 大大提高了网络的可伸缩性,因为现在添加或删除 Pod 时,只需更新 1 个小的 EndpointSlice。尤其是成百上千个 Pod 支持单个 Service 时,差异将非常明显。
-
如果使用 Endpoint API,Service 只有一个 Endpoint 资源。这意味着它需要为 Service 的每个 Pod 都存储好 IP 地址和端口(网络端点),这需要大量的 API 资源。另外,kube-proxy 会在每个节点上运行,并监控 Endpoint 资源的任何更新。如果 Endpoint 资源中有一个端口发生更改,那么整个对象都会分发到 kube-proxy 的每个实例。
-
Endpoint API 另一个局限是,它会限制跟踪 Service 的网络端点数量。一般存储在 etcd 中的对象默认大小限制为 1.5MB。在某些情况下,它会将 Endpoint 资源限制为 5000 个 Pod IP。对于大多数用户而言,这没什么关系,但是对于接近这个大小的 Service 而言,就有大问题了。
-
为了说明这些问题的严重程度,这里举一个简单的例子。如果一个 Service 有 5000 个 Pod,它如果有 1.5MB 的 Endpoint 资源。当该列表中的某个网络端点发生了变化,那么就要将完整的 Endpoint 资源分发给集群中的每个节点。在具有 3000 个节点的大型集群中,这会是个很大的问题。每次更新将跨集群发送 4.5GB 的数据(1.5MB * 3000 , 即 Endpoint 大小 * 节点个数),并且每次端点更新都要发送这么多数据。想象一下,如果进行一次滚动更新,共有 5000 个 Pod 全部被替换,那么传输的数据量将超过 22 TB。本段内容引用于此
入口函数
入口函数位于 cmd/kube-controller-manager/app/discovery.go
这里比较奇怪为什么起名是 discovery ?纯属个人疑问。
|
|
构造函数
主要重点留意的几个变量和函数。
变量:
-
c.maxEndpointsPerSlice
每组切片的最大 endpoint 数量。
-
features.TopologyAwareHints
是否开启拓扑感知提示特性,就近路由,比如节点 A B 属于同一区域,C D 属于另一个区域,pod 在 A B C D 节点上各有一个,查看 A B 节点上面的 ipvs 规则,会发现,通往该 pod service 的流量的 ipvs 后端,只有 A B 节点上的 pod ip ,C D 同理 ,可以参考这篇文章,说得很直白:Kubernetes Service 开启拓扑感知(就近访问)能力。
函数:
-
c.triggerTimeTracker
计算 service 和 pods 最后一次更新时间,并存到缓存,然会 2 者中最后一次更新的时间。
-
c.reconciler
控制器的核心逻辑所在。
|
|
监听函数
监听 service pod node endpointSlice 对象。
service 对象
-
AddFunc
onServiceUpdate 缓存 service Selector ,并加入令牌桶队列。
-
UpdateFunc
onServiceUpdate 缓存 service Selector ,并加入令牌桶队列。
-
DeleteFunc
onServiceDelete 删除缓存的 service Selector ,并加入令牌桶队列。
pod 对象
-
AddFunc
addPod
根据 pod 获取 service 对象,并把对应的 service 加入到延迟队列。
-
UpdateFunc
updatePod 同上。
-
DeleteFunc
deletePod
如果 pod 对象不为 nil ,调用 addPod 事件函数处理。
node 对象
只有启用了 TopologyAwareHints 特性,才有对应的监听事件。
-
addNode
调用 c.checkNodeTopologyDistribution() 检查节点拓扑分布情况。
-
updateNode
检查节点状态,调用 c.checkNodeTopologyDistribution() 检查节点拓扑分布情况。
-
deleteNode
调用 c.checkNodeTopologyDistribution() 检查节点拓扑分布情况。
endpointSlice 对象
-
AddFunc
onEndpointSliceAdd
调用 c.queueServiceForEndpointSlice() 接口,获取 service 唯一 key ,并计算更新延迟,按照延迟时间加入到延迟队列。
-
UpdateFunc
onEndpointSliceUpdate
最终调用 c.queueServiceForEndpointSlice() 接口,获取 service 唯一 key ,并计算更新延迟,按照延迟时间加入到延迟队列。
-
DeleteFunc
onEndpointSliceDelete
判断是否需要被删除,如果不希望被删除,则调用 c.queueServiceForEndpointSlice() 接口,获取 service 唯一 key ,并计算更新延迟,按照延迟时间加入到延迟队列。
Run
跟其他控制器一样,不需要过多讲解。
|
|
核心逻辑
核心逻辑入口 syncService ,实际最终调用的是 r.finalize() 函数。
syncService
-
获取 service 对象。
-
根据 service 的标签获取 pods (这里获取到的 pods 就是 slicesToCreate 凭据的点)。
-
根据 service 命名空间和标签获取 apiserver 已有的所有关联的 endpointSlices 。
-
过滤掉被标记为删除的 endpointSlice 。
-
实际最终调用 c.reconciler.reconcile() 。
reconcile
核心逻辑函数,所有核心逻辑基本都在这里面实现了。
c.reconciler.reconcile()
存放切片的变量:数组 slicesToDelete , map slicesByAddressType
-
检查 endpointSlice 的 AddressType ,没匹配到类型的加入到 slicesToDelete 数组等待删除。匹配到响应的地址类型的 endpointSlice 加入到 slicesByAddressType 数组。
-
不同地址类型的 endpointSlice 都会调用 r.reconcileByAddressType() 函数去调谐,传的参数里面就包含了地址类型。
|
|
r.reconcileByAddressType()
-
数组 slicesToCreate 、 slicesToUpdate 、 slicesToDelete 。
-
构建一个用于存放 endpointSlice 存在状态的结构体 existingSlicesByPortMap 。
-
构建一个用于存放 endpointSlice 期望状态的结构体 desiredEndpointsByPortMap 。
-
确定每组 endpointSlice 是否需要更新,调用 r.reconcileByPortMapping() 计算需要更新的 endpointSlice ,并返回 slicesToCreate, slicesToUpdate, slicesToDelete, numAdded, numRemoved 对象(计算过程遍历每个 slice 并填满至设定好的 endpoint 个数,默认 100 个,总长度不满 100 的单独一个 slice )给 r.finalize() 函数处理。
|
|
调用 r.finalize() 创建、更新或删除指定的 endpointSlice 对象。
r.finalize()
-
当同时有需要删除和新增的 slice 时,会优先把要删除的 slice 名替换到需要新增的 slice 上,再执行 slice 更新(意图是减少开销? 比如,要新增 A B C 三个,要删除 D E 两个,会遍历需要新增的 slice ,把 A 名替换成 D 的,B 替换成 E 的,再执行更新)
-
之后依次执行新增,更新和删除 slices 。
|
|
总结
-
总的来说,跟其他的控制器的逻辑是差不多的,都是先监听相关资源的事件,然后调谐。
-
从上面的代码我们也不难看出,endpointslice 有个特点就是,默认情况下,每个 slice 都是满 100 个条目就 new 一个新的切片,把每个切片的容量都控制在 100 个条目以内。
-
我们看完 endpointslice ,该控制器具有新增,更新和删除 slices 的功能,但是我们还发现源码里头还有 endpointslicemirroring 控制器。
-
endpointslicemirroring:在某些场合,应用会创建定制的 Endpoints 资源。为了保证这些应用不需要并发的更改 Endpoints 和 EndpointSlice 资源,集群的控制面将大多数 Endpoints 映射到对应的 EndpointSlice 之上。
控制面对 Endpoints 资源进行映射的例外情况有:
-
Endpoints 资源上标签 endpointslice.kubernetes.io/skip-mirror 值为 true。
-
Endpoints 资源包含标签 control-plane.alpha.kubernetes.io/leader。
-
对应的 Service 资源不存在。
-
对应的 Service 的选择算符不为空。
-
-
endpointslicemirroring 控制器我们等有时间再看看,我们先看看其他组件。