Karmada 依赖自动传播(PropagateDeps)深度指南:让 ConfigMap、Secret 随工作负载自动分发到成员集群
【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada
导读
在多集群编排场景下,一个 Deployment 往往依赖 ConfigMap、Secret、PVC、Service 等周边资源。Karmada 的"依赖自动传播"(Dependencies Automatically Propagation,特性门控PropagateDeps)允许你只配置工作负载自身的 PropagationPolicy,让依赖资源自动跟随工作负载被分发到相同的成员集群。本文以 Karmada 官方提案 docs/proposals/dependencies-automatically-propagation/README.md 为主体,结合当前仓库源码与测试,完整讲解该特性的设计动机、API 变更、核心控制器实现原理与端到端使用示例,读完即可在真实集群中配置并验证该能力。
为什么需要依赖自动传播
在 Kubernetes 中,Pod 可以通过数据卷(volume)或环境变量引用 ConfigMap 与 Secret;一组 Pod 也可以由 Service 暴露为服务,并由 Ingress 对外提供访问。这意味着:当使用 Karmada 把一个 Deployment 部署到成员集群时,它所依赖的 ConfigMap、Secret,以及依赖它的 Service、Ingress,都必须"跟随"Pod 一起进入相同的成员集群,否则应用将无法正常运行。
用户通常不会直接创建 Pod,而是通过 Deployment、StatefulSet、Job 等工作负载资源间接创建。以 Deployment 为例,它的依赖资源包括 ConfigMap、Secret、PVC、Service 等,其依赖关系如下图所示:
在引入自动传播之前,Karmada 提供两种方式传播依赖资源:
- 所有依赖资源与 Deployment 共享同一个 PropagationPolicy:即把 ConfigMap、Secret 也写进
resourceSelectors一起传播。 - 为每个依赖资源单独创建 PropagationPolicy:需要用户自行保证依赖资源被调度到需要的集群。
这两种方式在多数场景下可行,但各有明显缺点:
- 方式一将依赖资源与某一个 Deployment"绑定",该 ConfigMap/Secret 被一个 Deployment 引用后,就无法再被其他 Deployment 复用,灵活性差。
- 方式二要求用户持续关注 Deployment 的调度结果,人为维护成本高、易出错。
PropagateDeps提案的目标,就是提供一种"智能"策略:当用户传播一个工作负载时,Karmada 自动把其依赖资源传播到所需集群,免去手工维护。
提案目标与边界
Goals(目标)
- 提供一种策略,自动把工作负载资源的依赖资源传播到所需集群。
Non-Goals(非目标)
- 不废弃原有的默认传播流程:ConfigMap、Secret、Service 等资源依然可以像往常一样通过 PropagationPolicy 单独传播。
- 不改变已有的独立传播语义,两种方式长期共存。
特性启用方式
依赖自动传播能力通过karmada-controller-manager的特性门控控制,启用命令为:
--feature-gates=PropagateDeps=true从当前仓库源码看,该特性已进入 Beta 阶段且默认开启。在 pkg/features/features.go 中定义:
// PropagateDeps indicates if relevant resources should be propagated automatically PropagateDeps featuregate.Feature = "PropagateDeps"其默认值在 pkg/features/features.go 中注册:
PropagateDeps: {Default: true, PreRelease: featuregate.Beta},也就是说,当前仓库版本中PropagateDeps默认即为 true(Beta 特性默认开启)。如果你使用的版本默认关闭,则需要在部署karmada-controller-manager时显式追加上述参数。
整体方案架构
提案引入了一个新控制器——Dependencies Distributor(依赖分发器),用于智能传播依赖资源。当用户传播一个 Deployment 时,Karmada 各控制器协作完成依赖分发。整个方案分为五步:
PropagationPolicyAPI 增加PropagateDeps字段;ResourceBindingAPI 增加PropagateDeps与RequiredBy字段;- 新增 Dependencies Distributor 控制器智能传播依赖资源;
- 新增 ResourceInterpreter 能力解析给定对象的依赖资源;
- 配套文档与架构图补充。
为便于说明,提案把资源绑定分为两类:
- 独立资源绑定(independent resource binding):资源被某个 PropagationPolicy 匹配后创建的绑定;
- 附属资源绑定(attached resource binding):控制器传播依赖资源时创建的绑定,每个依赖资源对应一个独立的附属绑定。
整体架构如下:
需要特别注意的三个设计约束:
- 每个依赖资源拥有独立的附属资源绑定,因此同一个 Secret 可以被多个 Deployment 引用,天然支持资源共享。
- 控制器从**原始资源模板(raw resource template)**中解析依赖,也就是说,由 OverridePolicy 引入的依赖无法被识别——解析发生在覆盖策略生效之前。
- 用户的 User Story 场景为:创建 Deployment 与 PropagationPolicy 且
propagateDeps为true时,即使 Deployment 引用的 ConfigMap/Secret 尚未创建,一旦它们被创建,也会被自动传播到与 Deployment 相同的集群。
API 变更详解
提案对PropagationPolicy与ResourceBinding两个 API 进行扩展(ClusterPropagationPolicy与ClusterResourceBinding同步扩展)。
PropagationPolicy:新增 PropagateDeps
在 pkg/apis/policy/v1alpha1/propagation_types.go 中,PropagationSpec新增PropagateDeps字段:
// PropagateDeps tells if relevant resources should be propagated automatically. // Take 'Deployment' which referencing 'ConfigMap' and 'Secret' as an example, when 'propagateDeps' is 'true', // the referencing resources could be omitted(for saving config effort) from 'resourceSelectors' as they will be // propagated along with the Deployment. In addition to the propagating process, the referencing resources will be // migrated along with the Deployment in the fail-over scenario. // // Defaults to false. // +optional PropagateDeps bool `json:"propagateDeps,omitempty"`含义与注意事项:
- 当
propagateDeps为true时,被引用的资源(如 ConfigMap、Secret)可以省略在resourceSelectors中,它们会随 Deployment 一起传播,显著减少配置量; - 除传播过程外,在**故障转移(fail-over)**场景下,被引用资源也会随 Deployment 一起迁移,保证业务连续性;
- 默认值为
false。 - 从源码看,提案早期设想的
Association字段已被标记为Deprecated(已弃用),官方建议改用PropagateDeps(见 pkg/apis/policy/v1alpha1/propagation_types.go)。
ResourceBinding:新增 PropagateDeps 与 RequiredBy
在 pkg/apis/work/v1alpha2/binding_types.go 中,ResourceBindingSpec新增两个字段:
// ResourceBindingSpec represents the expectation of ResourceBinding. type ResourceBindingSpec struct { // PropagateDeps tells if relevant resources should be propagated automatically. // It is inherited from PropagationPolicy or ClusterPropagationPolicy. // default false. // +optional PropagateDeps bool `json:"propagateDeps,omitempty"` // RequiredBy represents the list of Bindings that depend on the referencing resource. // +optional RequiredBy []BindingSnapshot `json:"requiredBy,omitempty"` }PropagateDeps:继承自PropagationPolicy或ClusterPropagationPolicy,默认 false;RequiredBy:记录"依赖该资源的绑定"列表,即哪些独立绑定引用了当前依赖资源,是附属绑定与独立绑定之间的关联纽带。
BindingSnapshot定义在同文件 pkg/apis/work/v1alpha2/binding_types.go:
// BindingSnapshot is a snapshot of a ResourceBinding or ClusterResourceBinding. type BindingSnapshot struct { // Namespace represents the namespace of the Binding. // It is required for ResourceBinding. // If Namespace is not specified, means the referencing is ClusterResourceBinding. // +optional Namespace string `json:"namespace,omitempty"` // Name represents the name of the Binding. // +required Name string `json:"name"` // Clusters represents the scheduled result. // +optional Clusters []TargetCluster `json:"clusters,omitempty"` }字段说明:
Namespace:绑定的命名空间;对ResourceBinding必填;未指定时表示引用的是ClusterResourceBinding(集群级绑定);Name:绑定的名称,必填;Clusters:调度结果快照,即被引用绑定最终调度到的成员集群列表。
正是通过RequiredBy中的Clusters快照,附属绑定可以"知道"独立绑定的调度结果,从而把依赖资源分发到相同集群;当独立绑定因故障转移或重新调度而改变目标集群时,快照会同步更新,依赖资源随之迁移。
核心实现:Dependencies Distributor 控制器
Dependencies Distributor 是自动传播依赖资源的核心控制器,代码位于 pkg/dependenciesdistributor/dependencies_distributor.go,控制器注册名为dependencies-distributor(见同文件第 68 行),并在 cmd/controller-manager/app/controllermanager.go 中挂载到 controller-manager。
控制器职责与工作模式
控制器的整体设计逻辑(源码注释 + 代码结构):
- 当某个资源(如 Deployment)被 PropagationPolicy 匹配后,会创建对应的独立绑定(independent binding);
- 当 Dependencies Distributor 工作时,它为依赖资源(如 Secret)创建或更新对应的附属绑定(attached bindings)。
控制器同时实现了manager.Runnable与manager.LeaderElectionRunnable接口,意味着它以领导者选举模式运行,避免多副本同时分发产生竞争(见 pkg/dependenciesdistributor/dependencies_distributor.go)。
关键处理流程
Reconcile方法是主流程(见 pkg/dependenciesdistributor/dependencies_distributor.go),核心步骤:
- 获取独立绑定并校验:若绑定不存在则直接返回;若用户将
PropagateDeps从 true 改为 false,或绑定正在删除,则调用handleIndependentBindingDeletion清理所有附属绑定并移除 finalizer(代码注释明确指出这一场景:in case users set PropagateDeps field from "true" to "false"); - 获取资源模板:通过
FetchResourceTemplate从 informer 缓存获取独立绑定引用的工作负载对象; - 检查解释器钩子:调用
ResourceInterpreter.HookEnabled判断该 GVK 是否支持InterpreterOperationInterpretDependency操作,不支持则直接跳过; - 解析依赖:调用
ResourceInterpreter.GetDependencies获取依赖资源列表,成功/失败均上报 Kubernetes Event(EventReasonGetDependenciesSucceed/EventReasonGetDependenciesFailed); - 添加 finalizer:
addFinalizer为独立绑定添加util.BindingDependenciesDistributorFinalizer,确保删除独立绑定时有机会清理附属绑定; - 同步调度结果:
syncScheduleResultToAttachedBindings把独立绑定的调度结果同步到所有附属绑定。
同步调度结果:syncScheduleResultToAttachedBindings
该函数(见 pkg/dependenciesdistributor/dependencies_distributor.go)依次执行:
recordDependencies:把依赖列表序列化为 JSON,写入独立绑定的注解resourcebinding.karmada.io/dependencies(注解 key 定义在 pkg/util/constants.go)。若依赖未变化则跳过更新,减少写放大;removeOrphanAttachedBindings:找出不再被当前独立绑定引用的"孤儿"附属绑定,把当前独立绑定从它们的RequiredBy中移除;- 遍历每个依赖项,通过
RESTMapper转换为 GVR,动态注册 informer监听该类资源的新增/更新/删除事件,然后调用handleDependentResource创建或更新附属绑定。
handleDependentResource(见 pkg/dependenciesdistributor/dependencies_distributor.go)支持两种依赖定位方式:
- 按名称(Name):直接获取指定名称的资源模板并创建附属绑定;若资源模板尚不存在(
IsNotFound),静默跳过,等待资源创建后再处理——这正是"引用资源晚于 Deployment 创建也能被传播"的实现基础; - 按标签选择器(LabelSelector):通过
FetchResourceTemplatesByLabelSelector获取所有匹配的资源并逐一创建附属绑定。
附属绑定的创建与合并:createOrUpdateAttachedBinding
buildAttachedBinding(见 pkg/dependenciesdistributor/dependencies_distributor.go)构建附属绑定时:
- 名称由
names.GenerateBindingName(object.GetKind(), object.GetName())生成; - 为附属绑定添加形如
resourcebinding.karmada.io/depended-by-<独立绑定标识>的标签(key 前缀见第 78 行dependedByLabelKeyPrefix),用于后续按独立绑定关联检索附属绑定;label key 之所以要求唯一,正是因为同一个 Secret 可能被多个 Deployment 引用; - 设置
OwnerReferences指向依赖资源本身(控制器引用),当依赖资源被删除时由垃圾回收器联动清理; Spec.RequiredBy记录引用它的独立绑定快照;PreserveResourcesOnDeletion与ConflictResolution从独立绑定继承。
createOrUpdateAttachedBinding(见 pkg/dependenciesdistributor/dependencies_distributor.go)通过CreateOrUpdate幂等合并:调用mergeBindingSnapshot把新的 BindingSnapshot 合并进已有RequiredBy(相同 Namespace+Name 时更新 Clusters,否则追加),实现"一个依赖资源被多个工作负载共享"。
一个值得关注的细节:当多个独立绑定引用同一个依赖资源且策略字段不一致时,控制器会自动检测并消解冲突(见 pkg/dependenciesdistributor/policy_utils.go):
ConflictResolution:任一引用绑定为Overwrite则取Overwrite,否则取Abort;PreserveResourcesOnDeletion:任一引用绑定为 true 则取 true,否则 false;- 若检测到冲突,会通过
recordEventIfPolicyConflict上报DependencyPolicyConflict类型警告事件。
事件驱动的资源模板监听
Dependencies Distributor 不仅 watch ResourceBinding,还会通过 informer 监听被引用的依赖资源本身。reconcileResourceTemplate(见 pkg/dependenciesdistributor/dependencies_distributor.go)在依赖资源发生增删改时,遍历同命名空间下所有 ResourceBinding,通过matchesWithBindingDependencies匹配注解resourcebinding.karmada.io/dependencies中记录的依赖引用(支持按 Name 精确匹配或按 LabelSelector 匹配),匹配成功的绑定会推入genericEvent通道触发 Reconcile。
事件过滤器(见SetupWithManager中 pkg/dependenciesdistributor/dependencies_distributor.go)的细节:
- 创建事件:仅当
Spec.PropagateDeps == true且已产生调度结果(Spec.Clusters非空)才入队,未完成调度的绑定被丢弃; - 更新事件:要求
Generation发生变化,且新旧绑定任一开启PropagateDeps; - 删除事件:仅处理开启
PropagateDeps的绑定。
依赖解析:ResourceInterpreter 的 InterpretDependency 操作
自动传播的关键一环是"如何知道一个工作负载依赖哪些资源"。提案第 4 步引入的 ResourceInterpreter 支持InterpretDependency操作,返回[]DependentObjectReference。
DependentObjectReference定义在 pkg/apis/config/v1alpha1/interpretercontext_types.go:
type DependentObjectReference struct { // APIVersion represents the API version of the referent. APIVersion string `json:"apiVersion"` // Kind represents the Kind of the referent. Kind string `json:"kind"` // Namespace represents the namespace for the referent. Namespace string `json:"namespace,omitempty"` // Name represents the name of the referent. // Name and LabelSelector cannot be empty at the same time. Name string `json:"name,omitempty"` // LabelSelector represents a label query over a set of resources. // If name is not empty, labelSelector will be ignored. LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` }注意:Name与LabelSelector不能同时为空;Namespace为空表示引用的是集群级(非命名空间)资源,例如ClusterRole。
内置(默认)解释器
Karmada 内置解释器为常见工作负载提供了开箱即用的依赖解析,实现在 pkg/resourceinterpreter/default/native/dependencies.go,覆盖类型包括:
- Deployment(第 56 行
getDeploymentDependencies) - ReplicaSet(第 70 行)
- Job(第 84 行)
- CronJob(第 99 行)
- Pod(第 114 行)
- DaemonSet(第 124 行)
- 以及 StatefulSet 等
其实现模式高度统一:先把 unstructured 对象转换为类型化对象,再通过lifted.GetPodFromTemplate提取 Pod 模板,最终调用helper.GetDependenciesFromPodTemplate从 Pod 模板中解析出 ConfigMap、Secret、PVC、ServiceAccount 等依赖引用。也就是说,凡是"包含 Pod 模板"的工作负载,都能自动得到依赖清单。
自定义解释器
对于内置解释器未覆盖的自定义资源(CRD),Karmada 提供两种自定义依赖解析方式:
- 声明式 Lua 脚本:通过
ResourceInterpreterCustomization配置InterpretDependency的 Lua 脚本,由ConfigurableInterpreter.GetDependencies执行(见 pkg/resourceinterpreter/customized/declarative/configurable.go),底层由 Lua VM 调用脚本中的GetDependencies函数(见 pkg/resourceinterpreter/customized/declarative/luavm/lua.go); - Webhook 解释器:通过
CustomizedInterpreter.GetDependencies将请求转发给用户部署的 ResourceInterpreter Webhook(见 pkg/resourceinterpreter/customized/webhook/customized.go)。
内置解释器的默认分发入口在 pkg/resourceinterpreter/default/native/default.go 处,根据InterpretDependency操作分发到上述各类型解析函数。
实战示例:Deployment + ConfigMap 自动传播
下面完整复现提案中的示例。假设要创建一个名为myapp的 Deployment,它引用一个名为my-config的 ConfigMap 作为卷挂载。
第 1 步:创建 Deployment
apiVersion: apps/v1 kind: Deployment metadata: name: myapp labels: app: myapp spec: replicas: 1 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - image: nginx name: nginx volumeMounts: - name: configmap mountPath: "/configmap" volumes: - name: configmap configMap: name: my-config第 2 步:创建 PropagationPolicy(开启 propagateDeps)
创建 PropagationPolicy 将 Deployment 传播到指定集群,关键点是设置propagateDeps: true,且无需把my-config写进resourceSelectors:
apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: myapp-propagation spec: propagateDeps: true resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: myapp placement: clusterAffinity: clusterNames: - member1 - member2 replicaScheduling: replicaSchedulingType: Duplicated该配置使用Duplicated复制调度,将 Deployment 各副本复制到member1与member2两个集群。
第 3 步:创建被引用的 ConfigMap
apiVersion: v1 kind: ConfigMap metadata: name: my-config data: nginx.properties: | proxy-connect-timeout: "10s" proxy-read-timeout: "10s" client-max-body-size: "2m"第 4 步:观察自动生成的附属 ResourceBinding
当my-config被创建后,Dependencies Distributor 会为它自动创建附属 ResourceBinding,其spec大致如下:
spec: requiredBy: - clusters: - name: member1 replicas: 1 - name: member2 replicas: 1 name: myapp-deployment namespace: default resource: apiVersion: v1 kind: ConfigMap name: my-config namespace: default resourceVersion: "757297"可见:
requiredBy记录了引用者myapp-deployment(命名空间default)及其调度结果快照(member1、member2各 1 副本);resource指向实际依赖资源my-config;- 附属绑定会据此把 ConfigMap 分发到与 Deployment 相同的
member1、member2集群,完成自动传播。
测试保障
该特性的正确性由大量单元测试保障,测试文件 pkg/dependenciesdistributor/dependencies_distributor_test.go 覆盖了:
- 事件处理(
Test_OnUpdate)、资源模板协调(Test_reconcileResourceTemplate); - 依赖匹配(
Test_matchesWithBindingDependencies); - finalizer 添加与移除(
Test_addFinalizer、Test_removeFinalizer); - 独立绑定删除清理(
Test_handleIndependentBindingDeletion); - 孤儿附属绑定清理(
Test_removeOrphanAttachedBindings、Test_findOrphanAttachedBindings); - 附属绑定创建/更新与快照合并(
Test_createOrUpdateAttachedBinding、Test_mergeBindingSnapshot、Test_deleteBindingFromSnapshot、Test_buildAttachedBinding); - 策略冲突检测与消解(
Test_detectAndResolveConflictResolution、Test_detectAndResolvePreserveOnDeletion、Test_createOrUpdateAttachedBinding_emitsConflictEvent); - 调度结果同步(
Test_syncScheduleResultToAttachedBindings_doesNotWaitForInformerCacheSync)等。
此外,提案中的 Test Plan 还明确了对应的 E2E 用例方向:验证依赖资源是否被传播到所需集群。对应 E2E 用例位于 test/e2e/suites 目录,可用于在真实多集群环境中回归验证本特性。
使用建议与注意事项
- 开启方式:确认
karmada-controller-manager已启用PropagateDeps特性门控(当前仓库版本默认开启);创建 PropagationPolicy 时设置spec.propagateDeps: true。 - 不要重复声明:开启自动传播后,被引用的 ConfigMap/Secret 无需再写入
resourceSelectors,否则可能出现重复传播;官方也建议弃用早期的association字段。 - 解析范围限制:依赖解析基于原始资源模板,由 OverridePolicy 注入的依赖(例如在 override 中动态添加的 ConfigMap 引用)无法被识别。
- 资源共享:每个依赖资源拥有独立的附属绑定,同一个 Secret 可被多个 Deployment 共享;多个引用者的策略字段(
ConflictResolution、PreserveResourcesOnDeletion)不一致时,控制器按"任一优先"规则消解并告警。 - 生命周期联动:删除独立绑定时,其
RequiredBy快照会从附属绑定中移除;当最后一个引用者消失时,附属绑定上的策略字段被清空,后续由资源绑定控制器按常规流程回收。 - 自定义资源:如果你的 CRD 也需要自动传播依赖,可通过
ResourceInterpreterCustomization(Lua 脚本)或 ResourceInterpreter Webhook 实现InterpretDependency操作。
总结
Karmada 的依赖自动传播(PropagateDeps)把"工作负载 + 周边依赖资源"作为一个整体进行多集群编排:通过PropagationPolicy与ResourceBinding的 API 扩展、Dependencies Distributor 控制器、以及 ResourceInterpreter 的InterpretDependency操作三层协作,实现了依赖资源的自动识别、自动跟随调度与故障转移迁移。它既免去了手工维护依赖资源目标集群的负担,又通过"一资源一附属绑定 + RequiredBy 快照"的设计保持了资源共享的灵活性,是多集群应用编排中极具实用价值的内置能力。
【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考