Karmada 成员集群资源缓存实战:基于 karmada-search 与 ResourceRegistry 构建统一跨集群资源视图
发布时间:2026/9/17 22:14:49 作者:尧图编辑部 阅读量:1,286

Karmada 成员集群资源缓存实战基于 karmada-search 与 ResourceRegistry 构建统一跨集群资源视图【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada在 Karmada 多集群编排体系中管理员往往需要在多个成员集群之间查询同一类资源例如分布在 member1、member2、member3 上的 Pod 与 Deployment传统做法需要反复切换 kubeconfig 上下文既低效又缺乏全局视图。本文以 Karmada 官方设计文档《Caching member cluster resources for Karmada》为主体系统讲解其核心组件karmada-search、缓存范围声明资源ResourceRegistry、cache与opensearch两类后端存储并结合仓库源码pkg/search/controller.go、pkg/apis/search/v1alpha1/searchregistry_types.go 等剖析其底层实现。读完本文你将掌握如何通过一份ResourceRegistry声明缓存范围、如何理解缓存数据从成员集群流入 Karmada 控制面并经search/proxyAPI 暴露的完整链路以及该能力的安全边界。背景与动机多集群资源查询的痛点在多集群场景下管理员想要查询分布在多个集群中的资源时会遇到明显的阻碍需要频繁切换集群上下文查询每个集群都要切换 kubeconfig 的 current-context操作繁琐缺少全局资源视图无法一次性看到同一资源在多个集群中的分布情况查询效率低跨区域访问成员集群 API Server 的网络延迟不可控请求处理速度受限无法按标签跨集群过滤原生的单集群查询能力无法实现在多个集群中按 labels 获取资源这类需求。设计文档提出的解决方案是为 Karmada 增加一个缓存层caching layer将成员集群中指定范围的 Kubernetes 资源缓存到 Karmada 控制面管理员通过统一入口跨集群高效查询资源所有查询结果均来自缓存不再直接穿透到成员集群。对应地cmd/karmada-search/app/karmada-search.go 中对该组件的定位描述是The karmada-search starts an aggregated server. It provides capabilities such as global search and resource proxy in a multi-cloud environment.Goals目标设计文档明确了该缓存层的五个目标加速跨区域资源请求的处理速度查询请求在控制面本地缓存命中避免跨区域往返成员集群提供跨集群资源视图一次请求即可聚合多个集群中的资源兼容多集群的多种 Kubernetes 资源版本支持不同成员集群存在不同资源 API 版本的场景统一资源请求入口所有集群的资源查询都收敛到 Karmada 控制面的统一 API降低成员集群 API Server 压力缓存层承担读流量成员集群的 API Server 不再直接面对大量查询请求。Non-Goals设计文档中 Non-Goals 部分未展开具体条目但结合后续 Risks and Mitigations 可以明确其边界该功能面向的是管理员而非终端用户其定位是查询与视图能力而非面向最终用户的通用搜索门户。总体设计karmada-search 组件与 search.karmada.io API 组设计文档提出新增一个名为karmada-search的组件它提供新的 API 组search.karmada.io。选择 search 命名的原因文档提到是受 OpenSearch社区驱动的开源搜索与分析套件的启发强调其搜索与分析的定位。karmada-search组件当前支持两类后端存储backend storecache与opensearch其中cache类型被用作 Karmada 缓存层的默认后端存储。缓存层本身可以是本地内存也可以是外部数据库如 OpenSearch。karmada-search作为一个聚合 API Server 运行pkg/search/apiserver.go 展示了它在search.karmada.ioAPI 组下注册的 REST 资源resourceregistries与resourceregistries/statusResourceRegistry资源及其状态子资源search全局搜索入口由Controller提供proxying资源代理入口由ProxyController提供即文档提到的search/proxyREST API。核心 APIResourceRegistry——声明缓存什么、缓存谁的设计文档引入了一个新的资源类型ResourceRegistry它属于search.karmada.io组是集群级Cluster 级资源。它的作用正如其类型注释所写ResourceRegistry represents the configuration of the cache scope, mainly describes which resources in which clusters should be cached.—— 即缓存范围的配置需要用户在控制面中手动指定要缓存的集群和资源。该类型定义在 pkg/apis/search/v1alpha1/searchregistry_types.go与设计文档中的定义保持一致package v1alpha1 // genclient // genclient:nonNamespaced // k8s:deepcopy-gen:interfacesk8s.io/apimachinery/pkg/runtime.Object // ResourceRegistry represents the configuration of the cache scope, mainly describes which resources in // which clusters should be cached. type ResourceRegistry struct { metav1.TypeMeta json:,inline metav1.ObjectMeta json:metadata,omitempty // Spec represents the desired behavior of ResourceRegistry. Spec ResourceRegistrySpec json:spec,omitempty // Status represents the status of ResourceRegistry. // optional Status ResourceRegistryStatus json:status,omitempty } // ResourceRegistrySpec defines the desired state of ResourceRegistry. type ResourceRegistrySpec struct { // TargetCluster specifies the clusters where the cache system collect resource from. // required TargetCluster policyv1alpha1.ClusterAffinity json:targetCluster // ResourceSelectors specifies the resources type that should be cached by cache system. // required ResourceSelectors []ResourceSelector json:resourceSelectors // BackendStore specifies the location where to store the cached items. // optional BackendStore *BackendStoreConfig json:backendStore,omitempty } // ResourceSelector specifies the resources type and its scope. type ResourceSelector struct { // APIVersion represents the API version of the target resources. // required APIVersion string json:apiVersion // Kind represents the kind of the target resources. // required Kind string json:kind // Namespace of the target resource. // Default is empty, which means all namespaces. // optional Namespace string json:namespace,omitempty } // BackendStoreConfig specifies backend store. type BackendStoreConfig struct { // OpenSearch is a community-driven, open source search and analytics suite. // optional OpenSearch *OpenSearchConfig json:openSearch,omitempty } // OpenSearchConfig holds the necessary configuration for client to access and config an OpenSearch server. type OpenSearchConfig struct { // Addresses is a list of node endpoint(e.g. https://localhost:9200) to use. // required Addresses []string json:addresses // SecretRef represents the secret contains mandatory credentials to access the server. // The secret should hold credentials as follows: // - secret.data.userName // - secret.data.password // required SecretRef clusterv1alpha1.LocalSecretReference json:secretRef // More configurations such as transport, index should be added from here. } // ResourceRegistryStatus defines the observed state of ResourceRegistry type ResourceRegistryStatus struct { // Conditions contain the different condition statuses. // optional Conditions []metav1.Condition json:conditions,omitempty }关键字段说明字段位置必填语义与默认行为spec.targetClusterResourceRegistrySpec是指定从哪些集群采集资源类型复用策略 API 组的policyv1alpha1.ClusterAffinity支持按clusterNames或标签选择器匹配集群spec.resourceSelectorsResourceRegistrySpec是指定需要缓存的资源类型列表由ResourceSelector描述spec.backendStoreResourceRegistrySpec否指定缓存落盘位置不配置时使用默认的cache本地内存后端resourceSelector.apiVersionResourceSelector是目标资源的 API 版本如v1、apps/v1resourceSelector.kindResourceSelector是目标资源的 Kind如Pod、DeploymentresourceSelector.namespaceResourceSelector否目标资源所在的命名空间默认为空表示所有命名空间backendStore.openSearch.addressesOpenSearchConfig是OpenSearch 节点端点列表例如https://localhost:9200backendStore.openSearch.secretRefOpenSearchConfig是访问 OpenSearch 的凭据 Secret 引用要求 Secret 中包含userName与password两个 data 键值得一提的是文档定义中SecretRef的 data 键名写作userName/password而实际实现 pkg/search/backendstore/opensearch.go 中读取的是username/password两个键string(secret.Data[username])、string(secret.Data[password])以源码实现为准。该实现还表现出较好的容错性若 Secret 缺失或凭据为空会告警并尝试以无认证方式连接 OpenSearch。校验规则pkg/apis/search/validation/validation.go 定义了ResourceRegistry的准入校验逻辑名称须符合 DNS 子域名规范NameIsDNSSubdomaintargetCluster复用ValidateClusterAffinity校验集群亲和性每个ResourceSelector的apiVersion必须能通过schema.ParseGroupVersion解析namespace必须是合法命名空间名openSearch.addresses中每个地址必须是带非空 scheme且仅允许http/https与非空 host 的合法 URL。配置示例创建 ResourceRegistry CR设计文档给出了一个完整的示例。创建下面的ResourceRegistry后Karmada 会对spec.resourceSelectors中定义的资源执行 list/watch并将实时清单缓存到缓存层本地内存或数据库后续所有查询结果均来自缓存apiVersion: search.karmada.io/v1alpha1 kind: ResourceRegistry metadata: name: clustercache-sample spec: targetCluster: clusterNames: - member1 - member2 - member3 resourceSelectors: - kind: Pod apiVersion: v1 - kind: Ingress apiVersion: networking.k8s.io/v1 - kind: DaemonSet apiVersion: apps/v1 namespace: kube-system - kind: Deployment apiVersion: apps/v1该示例演示了三个要点多集群目标targetCluster.clusterNames一次性声明 member1、member2、member3 三个集群多资源类型resourceSelectors可同时声明多种资源Pod与Ingress、Deployment不限定命名空间即缓存全部命名空间而DaemonSet通过namespace: kube-system限定只缓存kube-system命名空间下的对象API 版本显式声明每个 selector 都必须给出apiVersion控制器会据此结合 RESTMapper 将apiVersion kind解析为GroupVersionResource见 pkg/search/controller.go 中getResources对restmapper.GetGroupVersionResource的调用。底层工作流ResourceRegistry Controller 如何把成员集群资源搬进缓存设计文档描述了list/watch 并缓存的宏观行为具体实现由 pkg/search/controller.go 中的Controller完成。它监听Cluster与ResourceRegistry两类对象的事件通过 workqueue 驱动doCacheCluster完成缓存编排核心步骤可概括为集群可用性检查clusterAbleToCache集群不存在、正在删除DeletionTimestamp非空或状态不 Ready 时停止该集群的 informer不进行缓存计算注册关系reconcileClusterWithRegistries列出全部ResourceRegistry用util.ClusterMatches判断哪些注册表与该集群匹配并对比集群当前已注册的注册表集合算出新增/移除的注册表以及新增/移除的待监听资源addedResources/removedResources卸载无引用集群STEP1若集群已不被任何ResourceRegistry引用cr.unregistry()停止该集群的 informer 管理器重建 informerSTEP2当注册表或监听资源集合发生变化时停止旧 informer 并为该集群构建新的多集群 informer 管理器InformerManager.ForCluster随后对每个待缓存资源的 GVR 注册sci.ForResource(gvr, handler)并sci.Start()WaitForCacheSync()API 兼容性检查注册 informer 前会通过cls.APIEnablement(gvk)检查该资源在成员集群中是否启用若APIDisabled则跳过并告警支持成员集群资源版本/能力差异呼应兼容多种 Kubernetes 资源版本的目标事件入缓存每个资源的 informer 事件Add/Update/Delete被路由到对应后端存储的事件处理器getRegistryBackendHandler。这里使用的InformerManager来自 pkg/util/fedinformer/genericmanager是 Karmada 用于管理多个成员集群 informer 的通用多集群 informer 管理器。后端存储cache默认本地内存与 OpenSearch设计文档说明karmada-search支持cache与opensearch两类后端。后端存储的统一抽象定义在 pkg/search/backendstore/store.go// BackendStore define BackendStore interface type BackendStore interface { ResourceEventHandlerFuncs() cache.ResourceEventHandler Close() }backendstore.Init初始化全局后端管理器AddBackend按集群注册后端若cfg nil或cfg.OpenSearch nil则使用默认后端NewDefaultBackend(cluster)——这正是文档所述以 cache 类型作为默认后端存储的落地实现。cache 后端默认pkg/search/backendstore/defaultstore.go 实现了默认后端它为每个集群创建一组cache.ResourceEventHandlerFuncs将 informer 的 Add/Update/Delete 事件中的*unstructured.Unstructured对象以日志V(4) 级别方式记录并保留在 informer 内置的缓存中。也就是说默认 cache 后端依托 informer 自带的本地内存索引存储资源实时清单这与文档所述cache本地内存或数据库中的本地内存形态对应。OpenSearch 后端当在ResourceRegistry中显式配置spec.backendStore.openSearch时控制器会为该集群创建 OpenSearch 后端见 pkg/search/backendstore/opensearch.go。其行为要点包括索引命名索引名为kubernetes-kind 小写defaultPrefix kubernetes首次写入某类资源时自动创建索引并应用内置 mappingmapping 中将metadata.annotations、labels、spec、status设为enabled: false不参与分词检索仅存储原始 JSON而name、namespace、resourceVersion配置了 keyword 子字段以便精确过滤写入策略Add/Update 事件统一走upsert以对象 UID 作为文档 ID 的 IndexRequestDelete 事件按 UID 发起 DeleteRequest实现中留有TODO: bulk upsert / bulk delete优化空间来源标记写入前会自动给对象注入clusterv1alpha1.CacheSourceAnnotationKey注解值为集群名用于标识缓存来源集群认证从secretRef指向的 Secret 读取凭据配置客户端缺失时降级为无认证并告警。需要说明的是从源码结构看当前 OpenSearch 后端仅负责把事件写入 OpenSearch 索引而对外查询Get/List/Watch仍通过 pkg/search/proxy/store/multi_cluster_cache.go 的MultiClusterCache走内存缓存路径——设计文档中的 OpenSearch 更多扮演分析型存储的角色其完整读写链路仍在演进中。统一查询入口search/proxy REST API 与 MultiClusterCache文档在 Risks and Mitigations 中明确指出被缓存的资源通过search/proxyREST API 暴露。karmada-search的 API Server 在search.karmada.io组下注册了proxying资源pkg/search/apiserver.go并支持对proxying路径执行 watch/proxy 等长连接请求见 cmd/karmada-search/app/karmada-search.go 的customLongRunningRequestCheck。从实现看聚合查询的核心是MultiClusterCachepkg/search/proxy/store/multi_cluster_cache.go它实现了标准存储接口StoreGet遍历各成员集群的 cluster cache以ResourceVersion0强制走缓存读取若对象在多个集群同时存在会返回 Conflictambiguous objects in clusters [...]并在返回对象上通过addCacheSourceAnnotation标注来源集群、将resourceVersion改写为多集群资源版本List按集群排序后逐个从各集群缓存拉取列表并合并支持limit分页multiClusterContinue记录下一批从哪个集群、以什么 resourceVersion 继续并会为缺失 resourceVersion 的集群回填版本号fillMissingClusterResourceVersionWatch为每个集群建立 watch 并汇聚到 watch 多路复用器watchMuxWithInvalidation统一以多集群 resourceVersion 返回事件当集群拓扑变化新增集群或故障集群恢复时主动失效所有活跃 watch 连接以触发客户端重连避免数据不一致对应 issue #6963ReadinessCheck在search-storage-cache-readinessPostStartHook 中等待所有注册集群的缓存就绪后才对外提供服务。由此管理员可经由统一入口完成跨集群的 get/list/watch得到带cache-source来源注解与多集群 resourceVersion 的聚合结果。安全风险与缓解Risks and Mitigations设计文档明确列出了三项安全考量这是使用该缓存能力时必须理解的边界search/proxy的权限即缓存访问权限该功能构建的缓存存有来自多个成员集群的任意资源并经由search/proxyREST API 暴露。只要用户对search/proxy拥有访问权限就可以直接读取缓存中的资源而无须把请求路由到成员集群——这意味着成员集群侧的细粒度 RBAC 在该路径上不再生效。可能绕过成员集群的 RBAC 限制由于查询请求不会路由到成员集群若某个 Secret 已被缓存到 Karmada 控制面即使成员集群中的用户因 RBAC 限制无法通过成员集群 API Server 访问该 Secret他仍可能通过 Karmada 控制面读到它。因此敏感资源如 Secret是否纳入缓存范围需要管理员审慎评估。面向管理员而非终端用户该功能为需要在多集群间查询、查看资源的管理员设计不应暴露给终端用户。若向终端用户开放此 API可能导致用户查看到不属于自己的资源。实践建议与源码索引综合设计与实现可以给出几条落地建议按需声明缓存范围resourceSelectors尽量收敛仅缓存确有全局查询诉求的资源类型并通过namespace限定范围控制控制面内存与 informer 开销敏感资源如v1/Secret谨慎纳入。利用默认 cache 后端快速起步不配置backendStore即可获得基于 informer 内存缓存的跨集群视图只有需要索引、检索能力时才考虑配置 OpenSearch 后端。关注集群生命周期联动集群删除或变为 NotReady 时控制器会自动停止对应 informer 并清理后端存储无需人工干预。以下是本文涉及的关键源码与文档位置便于继续深入设计文档docs/proposals/caching/README.md类型定义pkg/apis/search/v1alpha1/searchregistry_types.go准入校验pkg/apis/search/validation/validation.go缓存控制器pkg/search/controller.go后端存储抽象与实现pkg/search/backendstore/store.go、pkg/search/backendstore/defaultstore.go、pkg/search/backendstore/opensearch.goAPI Server 注册pkg/search/apiserver.go聚合查询存储pkg/search/proxy/store/multi_cluster_cache.go组件入口cmd/karmada-search/app/karmada-search.go通过ResourceRegistry声明缓存范围、由karmada-search控制器完成跨集群 list/watch、再经统一search/proxy入口聚合暴露Karmada 的成员集群资源缓存能力为多集群管理员提供了一条统一入口 全局视图 本地化读取的查询路径同时也要求使用者在权限与敏感数据层面做好防护。【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考