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 store):cache与opensearch,其中cache类型被用作 Karmada 缓存层的默认后端存储。缓存层本身可以是本地内存,也可以是外部数据库(如 OpenSearch)。
karmada-search作为一个聚合 API Server 运行,pkg/search/apiserver.go 展示了它在search.karmada.ioAPI 组下注册的 REST 资源:
resourceregistries与resourceregistries/status:ResourceRegistry资源及其状态子资源;search:全局搜索入口(由Controller提供);proxying:资源代理入口(由ProxyController提供),即文档提到的search/proxyREST API。
核心 API:ResourceRegistry——声明"缓存什么、缓存谁的"
设计文档引入了一个新的资源类型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:interfaces=k8s.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.targetCluster | ResourceRegistrySpec | 是 | 指定从哪些集群采集资源,类型复用策略 API 组的policyv1alpha1.ClusterAffinity,支持按clusterNames或标签选择器匹配集群 |
spec.resourceSelectors | ResourceRegistrySpec | 是 | 指定需要缓存的资源类型列表,由ResourceSelector描述 |
spec.backendStore | ResourceRegistrySpec | 否 | 指定缓存落盘位置;不配置时使用默认的cache(本地内存)后端 |
resourceSelector.apiVersion | ResourceSelector | 是 | 目标资源的 API 版本,如v1、apps/v1 |
resourceSelector.kind | ResourceSelector | 是 | 目标资源的 Kind,如Pod、Deployment |
resourceSelector.namespace | ResourceSelector | 否 | 目标资源所在的命名空间;默认为空,表示所有命名空间 |
backendStore.openSearch.addresses | OpenSearchConfig | 是 | OpenSearch 节点端点列表,例如https://localhost:9200 |
backendStore.openSearch.secretRef | OpenSearchConfig | 是 | 访问 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 子域名规范(
NameIsDNSSubdomain); targetCluster复用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 管理器; - 重建 informer(STEP2):当注册表或监听资源集合发生变化时,停止旧 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"),首次写入某类资源时自动创建索引并应用内置 mapping;mapping 中将metadata.annotations、labels、spec、status设为enabled: false(不参与分词检索,仅存储原始 JSON),而name、namespace、resourceVersion配置了 keyword 子字段以便精确过滤; - 写入策略:Add/Update 事件统一走
upsert(以对象 UID 作为文档 ID 的 IndexRequest),Delete 事件按 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)。
从实现看,聚合查询的核心是MultiClusterCache(pkg/search/proxy/store/multi_cluster_cache.go),它实现了标准存储接口Store:
- Get:遍历各成员集群的 cluster cache,以
ResourceVersion="0"强制走缓存读取;若对象在多个集群同时存在,会返回 Conflict("ambiguous objects in clusters [...]"),并在返回对象上通过addCacheSourceAnnotation标注来源集群、将resourceVersion改写为多集群资源版本; - List:按集群排序后逐个从各集群缓存拉取列表并合并;支持
limit分页(multiClusterContinue记录"下一批从哪个集群、以什么 resourceVersion 继续"),并会为缺失 resourceVersion 的集群回填版本号(fillMissingClusterResourceVersion); - Watch:为每个集群建立 watch 并汇聚到 watch 多路复用器(
watchMuxWithInvalidation),统一以多集群 resourceVersion 返回事件;当集群拓扑变化(新增集群或故障集群恢复)时,主动失效所有活跃 watch 连接以触发客户端重连,避免数据不一致(对应 issue #6963); - ReadinessCheck:在
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.go
- API 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),仅供参考