External Secrets Operator Secret 生命周期完全指南:refreshPolicy、creationPolicy 与 deletionPolicy 实战解析

发布时间:2026/9/17 3:01:42
External Secrets Operator Secret 生命周期完全指南:refreshPolicy、creationPolicy 与 deletionPolicy 实战解析 External Secrets Operator Secret 生命周期完全指南refreshPolicy、creationPolicy 与 deletionPolicy 实战解析【免费下载链接】external-secretsExternal Secrets Operator reads information from a third-party service like AWS Secrets Manager and automatically injects the values as Kubernetes Secrets.项目地址: https://gitcode.com/GitHub_Trending/ex/external-secretsExternal Secrets OperatorESO负责把第三方服务如 AWS Secrets Manager、Vault、Azure Key Vault中的密钥同步为 Kubernetes Secret。它通过spec.refreshPolicy、spec.target.creationPolicy与spec.target.deletionPolicy三个字段提供了对 Secret 生命周期创建、刷新、合并、删除的细粒度控制。本文以官方指南 docs/guides/ownership-deletion-policy.md 为骨架结合仓库源码与完整示例配置深入讲解每个策略的语义、限制与组合行为帮助你为不同业务场景设计出安全、可预期的密钥同步方案。一、为什么需要生命周期策略Kubernetes Secret 本身没有从哪里来、何时更新、何时销毁的概念。ESO 作为控制器接管了这一职责但不同团队的需求差异很大有的希望 Secret 随 ExternalSecret 删除而删除跟随式生命周期有的希望 Secret 独立存活如备份、跨命名空间共享有的希望只创建一次、永不更新如引导凭据有的希望在 Provider 侧删除密钥后本地 Secret 保留、删除或仅移除相关字段。这些诉求分别落在三个策略维度上并且某些组合是被禁止的例如deletionPolicyDelete会删除已有 Secret因此不允许与creationPolicyMerge、CreateOrMerge、None组合。官方指南在开篇就明确列出了禁止组合deletionPolicyDeletecreationPolicyMergedeletionPolicyDeletecreationPolicyCreateOrMergedeletionPolicyDeletecreationPolicyNonedeletionPolicyMergecreationPolicyNone这些规则不仅在文档中声明也作为**准入校验Validating Webhook**在源码中强制实施。见 apis/externalsecrets/v1/externalsecret_validator.gofunc validatePolicies(es *ExternalSecret) error { if es.Spec.Target.DeletionPolicy DeletionPolicyDelete (es.Spec.Target.CreationPolicy CreatePolicyMerge || es.Spec.Target.CreationPolicy CreatePolicyNone || es.Spec.Target.CreationPolicy CreatePolicyCreateOrMerge) { errs errors.Join(errs, errors.New(deletionPolicyDelete must not be used when the controller doesnt own the secret. Please set creationPolicyOwner)) } if es.Spec.Target.DeletionPolicy DeletionPolicyMerge es.Spec.Target.CreationPolicy CreatePolicyNone { errs errors.Join(errs, errors.New(deletionPolicyMerge must not be used with creationPolicyNone. There is no Secret to merge with)) } return errs }也就是说如果控制器不拥有own这个 Secret它就没有资格删除它creationPolicyNone时根本没有 Secret 可供deletionPolicyMerge清理。提交不符合规则的清单时Webhook 会直接拒绝。二、刷新策略RefreshPolicy控制何时同步字段spec.refreshPolicy定义操作符如何刷新 Secret。在 API 类型定义中apis/externalsecrets/v1/externalsecret_types.go共三种取值RefreshPolicyCreatedOnce ExternalSecretRefreshPolicy CreatedOnce RefreshPolicyPeriodic ExternalSecretRefreshPolicy Periodic RefreshPolicyOnChange ExternalSecretRefreshPolicy OnChangePeriodic默认按固定间隔spec.refreshInterval周期性地从 Provider 拉取最新值并同步到 Secret。合法的时间单位与 Go 的time.ParseDuration一致ns、us或µs、ms、s、m、h例如1h0m0s、30m。兼容性注意出于向后兼容把refreshInterval设为0将表现为CreatedOnce的行为只同步一次之后不再刷新。控制器在 pkg/controllers/externalsecret/externalsecret_controller.go 中实现周期性判断func shouldRefreshPeriodic(es *esv1.ExternalSecret) bool { // if the refresh interval is 0, and we have synced previously, we should not refresh if es.Spec.RefreshInterval.Duration 0 es.Status.SyncedResourceVersion ! { return false } // if the ExternalSecret has been updated, we should refresh if es.Status.SyncedResourceVersion ! ctrlutil.GetResourceVersion(es.ObjectMeta) { return true } // if the last refresh time is zero, we should refresh if es.Status.RefreshTime.IsZero() { return true } now : time.Now() // if the last refresh time refresh interval is before now, we should refresh if !es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(now) { return false } // check sync windows before triggering a refresh return isPeriodicRefreshAllowedByWindows(es, now) }注意这里同时印证了文档中的两个关键点一是refreshInterval 0且已同步过则不再刷新等价CreatedOnce二是周期性刷新还受可选的syncWindows限制allow/deny两种窗口见 apis/externalsecrets/v1/externalsecret_types.go。ESO 是pull 型架构不监听 Provider所以只有Periodic才能感知 Provider 侧的值变化。OnChange仅在 ExternalSecret 对象自身发生变化spec 或 metadata 更新或设置force-sync注解时触发同步。从源码shouldRefresh的实现可以清楚看到externalsecret_controller.gocase esv1.RefreshPolicyOnChange: if es.Status.SyncedResourceVersion || es.Status.RefreshTime.IsZero() { return true } return es.Status.SyncedResourceVersion ! ctrlutil.GetResourceVersion(es.ObjectMeta)它通过比较上次同步时的资源版本与当前资源版本来判断 ExternalSecret 是否被改动过。适合对 Provider 值变化不敏感、只想在声明式更新时重新拉取的场景。CreatedOnce每个 ExternalSecret 对象只在其首次 reconcile 时同步一次。同步状态保存在 ExternalSecret 的statusSyncedResourceVersion与RefreshTime上因此删除并重建 ExternalSecret 会重置状态触发又一次一次性同步这次同步是否覆盖既有目标 Secret 的 operator 管理键取决于创建策略creationPolicyNone不会更新它spec.target.immutable: true会阻止数据重写。case esv1.RefreshPolicyCreatedOnce: if es.Status.SyncedResourceVersion || es.Status.RefreshTime.IsZero() { return true } return false关于跨对象状态的重要说明官方文档特别强调ESO 在 ExternalSecret 对象之间是无状态的。它不持久化同步声明sync claims也不会在 ExternalSecret 被重建时去认领或回收孤儿 Secret。因此creationPolicy包括Orphan不会保护已存在的目标 Secret 免于这次同步被重写。如果你希望一个已经填好数据的 Secret 在 ExternalSecret 重建后不被覆盖应设置spec.target.immutable: true可选地配合refreshPolicy: CreatedOnce。三、创建策略CreationPolicy控制如何创建与拥有字段spec.target.creationPolicy定义操作符如何创建 Secret。API 定义见 apis/externalsecrets/v1/externalsecret_types.goCreatePolicyOwner ExternalSecretCreationPolicy Owner CreatePolicyOrphan ExternalSecretCreationPolicy Orphan CreatePolicyMerge ExternalSecretCreationPolicy Merge CreatePolicyNone ExternalSecretCreationPolicy None CreatePolicyCreateOrMerge ExternalSecretCreationPolicy CreateOrMergeOwner默认操作符创建 Secret 并在其上设置ownerReference字段。这样如果初始的 ExternalSecret 不存在了该 Secret 会受 Kubernetes 垃圾回收机制 牵连被删除如果同名 Secret 已存在但不是控制器创建的会产生冲突——操作符只会报错而不会强行认领所有权。补充细节源码确认Owner 模式下控制器还会在 Secret 上打上管理标签。在 externalsecret_controller.go 的isSecretValid中Secret 有效性的判断依赖esv1.LabelManaged标签与数据哈希注解的一致性这正是该 Secret 是否由我管理的依据而 apis/externalsecrets/v1/externalsecret_types.go 中定义了LabelOwner reconcile.external-secrets.io/created-by指向创建它的 ExternalSecret。关于 ownerReference 找不到的情况如果 Secret 存在但ownerReference字段找不到控制器会把该 Secret 视为孤儿orphaned通过添加ownerReference字段并更新它来接管所有权。Orphan操作符根据 Provider 提供的信息创建/更新目标 Secret但不设置ownerReference因此 ExternalSecret 删除时不会触发 Secret 的垃圾回收Secret 得以保留。操作符仍然 watch 这个 Secret但对Orphan而言只会在刷新时机到来时重新同步见行为矩阵而不会对 Secret 的每次变更都做出反应。这一点在源码中也有直接印证isSecretValid的第一条判断就是// Secret is always valid with CreationPolicyOrphan即 Orphan 模式下缺失的 Secret 也被视为有效状态不会像 Owner 那样被立即重建。⚠️警告手动修改可能被意外回滚如果将spec.refreshPolicy设为Periodic或OnChange、同时spec.target.creationPolicy设为Orphan那么你对 Secret 的任何手动修改都会在下一次同步间隔或 ExternalSecret 下次更新时被 Provider 的值覆盖且手动修改永久丢失。请谨慎使用creationPolicyOrphan。Merge操作符不创建 Secret而是期望 Secret 已经存在然后把 Provider 的值合并进现有 Secret。注意控制器会接管一个字段的所有权即使该字段由其他实体拥有多个 ExternalSecret 可以共用同一个 Secret 使用creationPolicyMerge前提是字段互不冲突——否则会出现振荡状态oscillating state两个控制器互相覆盖对方写入的字段。CreateOrMerge综合了Merge与Orphan的优点Secret 不存在则创建已存在则合并 Provider 值合并时保留其他实体拥有的键如同Merge不设置ownerReference如同Orphan与Merge不同它会创建缺失的 Secret所以目标 Secret 被删除后会立即重建由 Secret watch 驱动只要 ExternalSecret 还存在与Owner不同ExternalSecret 被删除时 Secret 会保留。结合spec.target.immutable: true可以得到创建一次、冻结、删除后重建、ExternalSecret 删除后保留的经典模式。注意deletionPolicyDelete不允许与此策略组合因为控制器并不拥有该 Secret。None操作符既不创建也不更新 Secret基本是 no-op。适合未来 reserved 场景如与 injector 配合使用。四、不可变目标Immutable target设置spec.target.immutable: true会把生成的KindSecret标记为不可变Kubernetes 自身也会阻止对不可变 Secret 数据的编辑因此一旦 Secret 存在其数据永远不会被重写。需要强调的是它不影响缺失 Secret 的创建或重建——那由creationPolicy和refreshPolicy决定。一个不存在的 Secret 没有数据需要保护所以仍会被全新创建或重建。完整的交互行为见下文行为矩阵。在 API 类型中对应Immutable bool字段externalsecret_types.go并在状态条件中提供了ConditionReasonSecretImmutable SecretImmutableexternalsecret_types.go用于在目标 Secret 因不可变而无法更新时向用户报告原因。五、删除策略DeletionPolicy控制Provider 密钥被删后怎么办DeletionPolicy定义的是当某个 Secret 从 Provider如 Vault、AWS Parameter Store侧被删除后Kubernetes 侧应该发生什么。注意它仅在特定的 Provider 上受支持请查阅官方稳定性/支持表格确认你所用 Provider 的支持情况。删除策略只在 Provider 返回无数据NoSecretErr时触发。在控制器代码 pkg/controllers/externalsecret/externalsecret_controller_secret.go 中可以看到if errors.Is(err, esv1.NoSecretErr) externalSecret.Spec.Target.DeletionPolicy ! esv1.DeletionPolicyRetain { r.recorder.Eventf(externalSecret, v1.EventTypeNormal, esv1.ReasonMissingProviderSecret, eventMissingProviderSecret, i) continue }即Provider 密钥缺失时若删除策略不是Retain控制器发出正常事件并跳过该键而不是报错。Retain默认如果 Provider 侧所有密钥都被删除则保留Kubernetes Secret。此时因为 Provider 密钥不存在ExternalSecret 会进入SecretSyncedError状态。Delete如果 Provider 侧所有密钥都被删除则删除Kubernetes Secret。与 Retain 相反Provider 侧密钥被删且不可访问不视为错误ExternalSecret不会进入SecretSyncedError状态。这也适用于新创建的 ExternalSecret 映射到 Provider 中不存在的密钥的情形。重要约束deletionPolicyDelete只在creationPolicyOwner时生效操作符拒绝删除它不拥有的 Secret否则会报告SecretSyncedError这正是 Webhook 校验禁止其他组合的原因见第一节。Merge移除 Secret 中的键但不删除 Secret 本身。Provider 侧密钥被删且不可访问同样不视为错误ExternalSecret 不会进入SecretSyncedError状态。六、行为矩阵creationPolicy × refreshPolicy 完整对照下表继承自官方指南并完整保留说明creationPolicy与refreshPolicy如何组合驱动 Secret 操作。deletionPolicy是独立的另一个维度在表下方说明它只在 Provider 返回无数据时触发。creationPolicyrefreshPolicyCreate if missingReflect source changeOverwrite on ES re-createRecreate if Secret deletedRetain on ES deleteOwnerPeriodicYesYes (at interval)YesYes (immediate)No (GC via ownerRef)OwnerOnChangeYesOnly on ES changeYesYes (immediate)No (GC via ownerRef)OwnerCreatedOnceYesNoYesYes (immediate)No (GC via ownerRef)OrphanPeriodicYesYes (at interval)YesYes (at interval)YesOrphanOnChangeYesOnly on ES changeYesNo (until ES change)YesOrphanCreatedOnceYesNoYesNoYesMergePeriodicNo (waits)Yes (at interval)Yes (if target exists)No (never creates)YesMergeOnChangeNo (waits)Only on ES changeYes (if target exists)No (never creates)YesMergeCreatedOnceNo (waits)NoYes (if target exists)No (never creates)YesCreateOrMergePeriodicYesYes (at interval)YesYes (immediate)YesCreateOrMergeOnChangeYesOnly on ES changeYesYes (immediate)YesCreateOrMergeCreatedOnceYesNoYesYes (immediate)YesNoneanyNo (no-op)NoNoNoYes (nothing created)列含义解读Reflect source change反映源变更Provider 中的值发生变化后被拉取进 Secret。ESO 是 pull 型架构、不 watch Provider所以只有Periodic会在每个间隔轮询。OnChange只在 ExternalSecret 的 spec 或 metadata 变化、或设置force-sync注解时重新同步。CreatedOnce永不重新同步。Overwrite on ES re-createExternalSecret 重建时覆盖删除并重建 ExternalSecret 会重置其 status因此下一次同步会覆盖既有目标中由 ES 管理的键其他实体拥有的键被保留。这与refreshPolicy无关只有spec.target.immutable: true能阻止它。Recreate if Secret deletedSecret 被删后重建指 ExternalSecret 仍存在、但你手动删除了目标 Secret。Owner把缺失的 Secret 视为无效状态通过 Secret watch立即重建Orphan把缺失的 Secret 视为有效状态因此只在刷新被触发时重建如Periodic的下一个间隔在CreatedOnce或OnChange下它会保持删除状态。Merge从不创建。Retain on ES deleteExternalSecret 删除后保留目标 Secret 是否能在 ExternalSecret 删除后存活。只有Owner设置ownerReference因此只有Owner会被垃圾回收。横跨所有行的修饰项spec.target.immutable: true一旦目标 Secret 存在其数据永不重写因此Reflect source change和Overwrite on ES re-create变为 No。它不影响Create if missing或Recreate if Secret deleted——不存在的 Secret 没有数据可保护会被全新创建或重建。refreshInterval: 0Periodic行为等价于CreatedOnce。deletionPolicyRetain / Delete / Merge只在 Provider 返回无数据时起作用而这只能在下一次 re-sync 时被检测到。所以CreatedOnce下永远检测不到 Provider 侧删除OnChange下只在 ExternalSecret 变更时检测Periodic下在下一个间隔检测。七、完整配置示例以下是仓库中的完整示例 docs/snippets/full-external-secret.yaml其中已包含生命周期相关的全部字段与注释本处以生命周期部分为主省略模板与数据定义细节apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: hello-world spec: secretStoreRef: name: aws-store kind: SecretStore # 或 ClusterSecretStore # 刷新策略CreatedOnce / Periodic默认/ OnChange refreshPolicy: Periodic # 轮询间隔支持 ns/us/µs/ms/s/m/htime.ParseDuration 语法 # 设为 0 表示只拉取并创建一次等价 CreatedOnce refreshInterval: 1h0m0s # 可选限制周期性刷新的时间窗口仅 Periodic 生效 # kind: allow —— 仅当至少一个窗口激活时才允许同步 # kind: deny —— 任一窗口激活期间阻止同步 syncWindows: kind: allow windows: - schedule: 0 9 * * 1-5 # 工作日 09:00 UTC duration: 8h # 窗口开放至 17:00 UTC target: # Secret 名称默认取 ExternalSecret 的 .metadata.name不可变 name: application-config # 创建策略 # - Owner:默认创建 Secret 并设置 ownerReferencesExternalSecret 删除则 Secret 一并删除 # - Merge: 不创建 Secret将数据字段合并进已有 Secret要求 Secret 已存在 # - Orphan: 创建 Secret 但不设置 ownerReferences若 Secret 已存在则更新它 # - None: 不创建也不更新 Secret保留给未来 injector 场景 creationPolicy: Merge # 删除策略Provider 侧字段被删时 # - Retain:默认Provider 全部字段被删时保留 Secret # - Delete: Provider 全部字段被删时移除 Secret # - Merge: 从 Secret 中移除键但不删除 Secret 本身 deletionPolicy: Retain # 将生成的 Secret 标记为不可变数据一旦存在永不重写 immutable: false关键字段速查字段取值默认作用spec.refreshPolicyPeriodic/OnChange/CreatedOncePeriodic控制同步触发时机spec.refreshIntervalGo duration如1h0m0s无必填于 Periodic周期同步间隔0等价CreatedOncespec.target.creationPolicyOwner/Orphan/Merge/CreateOrMerge/NoneOwner控制创建方式与所有权是否设置 ownerReferencespec.target.deletionPolicyRetain/Delete/MergeRetain控制 Provider 侧删除后的本地行为仅部分 Provider 支持spec.target.immutabletrue/falsefalse标记目标 Secret 不可变阻止数据重写提醒deletionPolicy仅受部分 Provider 支持部署前请对照官方稳定性/支持表格确认。八、场景化推荐组合默认跟随式生命周期creationPolicyOwnerrefreshPolicyPeriodicdeletionPolicyRetain。Secret 与 ExternalSecret 同生共死Provider 密钥被删时进入SecretSyncedError报警适合绝大多数标准场景。只写一次、防篡改refreshPolicyCreatedOncespec.target.immutable: true。适合引导凭据、一次性生成的证书等创建后数据永久冻结。Secret 独立存活、不被回收creationPolicyOrphan。适合需要手动修改、备份或跨命名空间复用的 Secret但要注意 Periodic/OnChange 下手动修改会在下次同步被覆盖建议搭配immutable: true或谨慎选择CreatedOnce。Provider 删除即清理creationPolicyOwnerdeletionPolicyDelete。Provider 侧密钥删除后本地 Secret 自动删除且不报错适合密钥完全由 Provider 管理的场景。共享既有 Secret、只填字段creationPolicyMerge。多个 ExternalSecret 写入同一个 Secret 时务必规划好键名避免字段冲突引发振荡。创建/合并二合一creationPolicyCreateOrMergeimmutable: true。实现创建一次、冻结、被删重建、ExternalSecret 删除后保留兼具灵活性。结语ESO 的三个生命周期策略——refreshPolicy何时同步、creationPolicy如何创建与拥有、deletionPolicyProvider 删除后怎么办——共同构成了对 Kubernetes Secret 全生命周期的精细控制。理解它们之间的组合关系与约束尤其是 Webhook 强制校验的非法组合、Orphan对缺失 Secret 的宽容、immutable的跨行影响是安全使用 External Secrets Operator 的关键。本文中的实现细节均可在仓库源码中进一步验证API 类型定义见 apis/externalsecrets/v1/externalsecret_types.go策略合法性校验见 apis/externalsecrets/v1/externalsecret_validator.go刷新判定与 Secret 有效性逻辑见 pkg/controllers/externalsecret/externalsecret_controller.go 与 pkg/controllers/externalsecret/externalsecret_controller_secret.go完整示例见 docs/snippets/full-external-secret.yaml。【免费下载链接】external-secretsExternal Secrets Operator reads information from a third-party service like AWS Secrets Manager and automatically injects the values as Kubernetes Secrets.项目地址: https://gitcode.com/GitHub_Trending/ex/external-secrets创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考