KubeVela vNext `from*` 指令族(fromParameter / fromSource / fromDependency)解析:渲染期值替换与准入期 Schema 校验设计

原创2026-09-26 10:06:491,880 阅读
文章标签:云原生DevOps运维微服务

KubeVela vNext from* 指令族(fromParameter / fromSource / fromDependency)解析:渲染期值替换与准入期 Schema 校验设计

本文基于仓库 design/vela-core/keps/2.21-from-resolution/README.md 展开。它属于 KubeVela vNext(2.0)路线图中的设计提案(KEP),阐述 fromParameter、fromSource、fromDependency 三个内联值替换指令共享的解析模型、结构化检测规则、准入期 Schema 校验与简写语法,并给出与 KEP-2.16、KEP-2.17、KEP-2.19 等关联提案的对照,以及当前仓库中已落地的 CEL 属性表达式机制(pkg/definition/propexpr/expr.go)作为佐证。读完本文,你可以完整理解 vNext 中"组件属性如何在渲染前被声明式地注入"这一设计,以及为何这类引用可以在 kubectl apply 阶段就被静态校验。


0. 阅读前提:这是一份早期概念草案

按 design/vela-core/keps/README.md 的说明,vNext 是 KubeVela 下一阶段架构演进的代号,最终将汇聚为 KubeVela 2.0 发布,而本文所依据的 KEP-2.21 在仓库中明确标注为 Status: Drafting(Not ready for consumption),其文档头部也带有如下警示:

⚠️ Early concept draft. 这是一个早期阶段的探索,不完整且可能不准确,方向尚未定型,不应作为实现依据或已承诺行为的描述。预期会有大幅变更。

因此,本文的定位是理解这份设计意图,而不是把它当作已落地功能的操作手册。事实上,仓库中已经有一个与之相关但走了不同路线的落地机制:fromSource 在 KEP-2.16 的当前版本中已被 $( ) CEL 属性表达式取代(详见 第 7 节)。这一点在阅读本 KEP 时必须先建立预期。

该 KEP 在整个 vNext 提案族中的位置:

本 KEP 聚焦的是三个指令共享的横向机制——解析模型、准入校验、简写语法;每个指令各自"从哪里解析、作者如何编写"的细节,由各自的子 KEP 负责。


1. from* 家族总览:三个指令、三种数据来源

fromParameter、fromSource、fromDependency 是一族内联的值替换指令(inline value substitution directives),全部出现在组件(乃至 trait、policy 等)的 properties 中,作用是在 Definition 渲染器拿到 properties 之前,把其中的引用节点替换为具体值。

指令 解析自 何时解析 在何处做 Schema 校验
fromParameter Application.spec.parameters 渲染期(即时) 准入期(模板化应用校验 key + 类型)
fromSource SourceDefinition CueX 模板 渲染期(惰性、带缓存) 准入期(通过 schema: 块校验 path + 类型)
fromDependency Component.status.exports 上游组件健康之后 准入期(通过 exports: 块校验 path + 类型)

三者共同的核心价值:把组件之间的数据传递从"写 workflow step + 手工传值"的编排负担,降级为声明式的属性引用,并且因为指令是声明式的,其"引用的 Schema"可以在 kubectl apply 时就被静态校验——而不是等到渲染、运行期才暴露接线错误。


2. 解析模型:两阶段、都发生在组件渲染之前

按本 KEP 的设计,解析发生在两个阶段,且都先于组件渲染:

Phase 1 —— Source 解析(串行)

spec.sources 条目按声明顺序依次解析。对每一条 source,其 properties 中出现的 fromSource 引用,会基于"已解析完成的更早 source 的输出"来解析。解析出的 source 输出会被缓存,供后续 source 和组件 properties 使用。这使得 **source 链式引用(source chaining)**成为可能——完整语义见 KEP-2.16。

Phase 2 —— 组件属性解析(单次递归遍历)

Application controller 在把合并后的 properties map 交给 Definition 渲染器之前,对每个组件执行一次递归遍历:

  1. 遍历 properties 树中的每一个节点(对象、数组、标量叶子);
  2. 当遇到一个唯一键为 fromParameter、fromSource 或 fromDependency 的节点时,用解析出的值替换整个节点;
  3. 把完全解析后的 properties 交给 CUE Definition 渲染器。

各指令在 Phase 2 的解析时机不同:

  • fromDependency 节点只有在引用的组件健康、且已把 exports 写入 Component.status.exports 之后才可解析。在此之前,消费组件不会被渲染——controller 会延迟它,并在上游 export 可用时重新入队(re-queue)。
  • fromSource 节点使用 Phase 1 已缓存的 source 输出解析。
  • fromParameter 节点直接从 spec.parameters 即时解析。

因此,Phase 2 的递归遍历对 fromParameter 和 fromSource 而言是**确定性、单趟(single-pass)的;对 fromDependency 而言则是拓扑排序(topologically ordered)**的——因为依赖图决定了求值顺序。

与 Hub 流水线的呼应:本 KEP 依赖的 KEP-2.3 hub-controller 在其 Hub 协调流水线(Hub Reconcile Pipeline)中也明确了 fromDependency 的环节:Resolve dependsOn Component.status.exports → inject into Component spec,随后才 Dispatcher.Dispatch(Component CR) → spoke,并在健康后读取 Component.status.exports 传递给依赖方。也就是说,解析动作发生在 Hub 的 application-controller,而不是 Spoke 的 component-controller。


3. 检测规则:结构性检测,而非字符串匹配

from* 指令的检测是结构性的(structurally),不是字符串匹配。一个节点被识别为替换指令,当且仅当它是一个 map、且恰好只包含一个键,该键是 fromParameter / fromSource / fromDependency 之一。

# 被识别为 fromParameter 指令
region:
  fromParameter: region

# 不被识别 —— 与 fromParameter 并存的还有第二个键
region:
  fromParameter: region
  default: us-east-1    # 错误 —— 应改用 map 形式

关键规则:

  • 任何带有额外键的 map 都被当作普通 map,而不是替换指令。因此"想要同时给指令提供默认值"时,必须使用下文第 5 节的 map 形式(fromDependency: {component: ..., path: ..., default: ...}),而不是在指令节点旁边平铺 default 键。
  • from* 指令在 properties 中任意深度都有效,包括嵌套对象和数组条目。
  • from* 指令只能作为值出现,不能作为 map 的键。

这与 KEP-2.16 中对表达式"结构性地在任意深度查找"的处理方式一脉相承——"expressions are found structurally, at any depth within properties, including nested objects and array entries"。


4. 准入期 Schema 契约校验:接线错误在 apply 时暴露

from* 指令被刻意设计为声明式,这意味着每条替换引用的 Schema 都可以在 kubectl apply 时被校验——先于协调、先于渲染、先于任何运行期值存在。

指令 Schema 来源 准入期校验内容
fromParameter spec.parameters(自由形式)或 ApplicationDefinition.parameter{} schema key 存在性(自由形式)或 key 类型匹配(模板化应用)
fromSource SourceDefinition.schema: 块 source 名称存在于 spec.sources;path 指向声明的输出字段;输出字段类型与消费组件在该属性路径上的 parameter{} schema 兼容
fromDependency ComponentDefinition.exports{} schema 依赖组件存在于 Application 中;path 指向声明的 exports 字段;exports 字段类型与消费组件在该属性路径上的 parameter{} schema 兼容

核心洞察:即使 fromSource / fromDependency 的值在准入期尚不可知,它们的类型却可以从发布方 Definition 的 output{} / exports{} schema 中获知。Hub 的准入 webhook 同时解析消费组件的 parameter{} schema 与提供方 Definition 的输出 schema,并对两者做类型兼容性校验——把破损的接线在 apply 时拦下,而不是留到运行期。

4.1 一个会在准入期被拦下的例子

# aws-rds ComponentDefinition 声明:
#   parameter: { port: *5432 | int }
# postgres-ha ComponentDefinition 声明:
#   exports: { endpoint: string, port: int }

components:
  - name: db
    type: postgres-ha

  - name: app
    type: aws-rds
    properties:
      port:
        fromDependency:
          component: db
          path: endpoint   # ← 类型不匹配:endpoint 是 string,port 期望 int
                           #   在准入期被拦截,而不是运行期

准入 webhook 会拒绝该 Application,错误信息形如:

fromDependency type mismatch: db.exports.endpoint is string, aws-rds.parameter.port expects int

这使 from* 家族成为一个 Schema 安全的接线系统(schema-safe wiring system)——生产者与消费者之间的契约被静态强制,而值本身保持动态。

从源码视角看类型静态化:KEP-2.16 的当前版本把同一思想落到了 CEL 表达式上——source 的 schema:(一个 cue.Value)被翻译成 apiservercel.DeclType,表达式在一个"每个绑定一个类型"的环境中编译,由 CEL 的 OutputType() 报告结果类型,全程不求值,因此类型是"表达式与声明 schema 的属性",不要求运行期已有数据(见 KEP-2.16 的 "Type checking" 一节)。这正是本 KEP 所设想"类型可知、值不可知"校验原则的同构实现。


5. 简写语法:string 形式与 map 形式

三个指令都支持简写字符串形式和 map 形式。简单引用优先使用简写;需要 default 时必须使用 map 形式。

指令 简写 Map 形式(带 default)
fromParameter fromParameter: my-param fromParameter: {name: my-param, default: "x"}
fromSource fromSource: source-name.field fromSource: {name: source-name, path: field, default: "x"}
fromDependency(同拓扑) fromDependency: component.field fromDependency: {component: comp, path: field, default: "x"}
fromDependency(跨拓扑) fromDependency: component/group.field fromDependency: {component: comp, group: grp, path: field, default: "x"}

解析器按值的类型判断形式——string 即简写,map 即显式形式。简写刻意不支持 default:需要默认值意味着该字段是可选的,值得用更显式的 map 形式来表达。

关于跨拓扑简写的补充:/ 用于分隔 component+group 与 path,. 用于分隔 path 段;group: 字段只在跨拓扑引用中有效(见 KEP-2.19)。


6. fromParameter:应用级常量的内联替换

fromParameter 从 Application.spec.parameters 解析——这是一个自由形式的值集合,代表跨组件、trait、policy、operation 共享的应用级常量。

spec:
  parameters:
    region: us-west-2
    tier: production

  components:
    - name: my-db
      type: aws-rds
      properties:
        region:
          fromParameter: region
        tier:
          fromParameter: tier

使用规则与边界:

  • fromParameter 在 properties 的任意深度有效(嵌套对象、数组条目均可)。
  • 在 OperationDefinition 模板中无效——Operation 直接使用 context.appParams(而不是 fromParameter)。对非模板化应用,Operation 作者应使用 CUE 默认值(如 context.appParams.region | "us-east-1"),或在自己 parameter{} schema 中声明依赖并由调用方通过 fromParameter 或显式值接线(见 KEP-2.9)。
  • 模板化应用(templated Applications):即引用 ApplicationDefinition 的 Application,其 fromParameter 引用在准入期按 Definition 的 parameter{} schema 校验(key 类型匹配)。
  • 非模板化应用:spec.parameters 是自由形式 map,准入期只做 key 存在性检查;若引用的 key 缺失,按 KEP-2.9 的设计,controller 会在协调期以清晰的错误信息(指出缺失的 key)提示。

6.1 与 ApplicationDefinition 的关系

spec.parameters 与 ApplicationDefinition 是配套概念:模板化应用的 spec.parameters 在准入期按 Definition 的 parameter{} schema 校验,controller 用这些参数渲染出完整 Application spec——组件属性、trait 属性、policy 属性都是 CUE 渲染出来的,parameter.* 引用自然解析。而非模板化应用则通过 fromParameter 这一显式指令在 properties 内部做替换(KEP-2.9 中给出了 region: {fromParameter: region}、multiAz: {fromParameter: enableMultiAz} 等完整示例)。


7. fromSource:SourceDefinition 与已落地的 $( ) CEL 表达式机制对照

fromSource 从声明于 Application.spec.sources 的 SourceDefinition 实例解析,支持通过 Config CRD 做惰性、缓存化解析。其完整的编写模型见 KEP-2.16。

spec:
  sources:
    - name: cluster-info
      definition: cluster-config-reader
      properties:
        cacheDuration: "1h"

  components:
    - name: api
      type: webservice
      properties:
        region:
          fromSource: cluster-info.region

7.1 重要对照:当前仓库中 fromSource 已被 CEL 表达式取代

这是阅读本 KEP 时最需要留意的一点。虽然 KEP-2.21 仍以 fromSource 指令的形式展开设计,但 KEP-2.16 的当前版本(Status: Ready for Review)已经明确:source 消费的唯一机制是 $( ) 包裹的 CEL 表达式,"the fromSource directive it replaced is gone, along with its FromSource and SourceSelector API types"。例如:

image:    '$(source.catalog.image + ":1.25.0")'   # 字符串拼接
replicas: '$(source.tenant.maxReplicas / 2)'      # 整数运算
port:     '$(source.catalog.httpPort)'            # 保持 int 类型
value:    '$(has(source.registry.mirror) ? source.registry.mirror : "none")'

仓库源码 pkg/definition/propexpr/expr.go 的包注释也确认了这段演进史:属性表达式"supersedes an earlier fromSource: directive, which could name a value but not compute with one"——表达式是 fromSource 的严格超集(整个 struct/list 可替换、default: 变成 *x | y、路径同样做 schema 校验),因此指令被移除而非并存,同时也移除了那套已经漂移过一次的重复强制路径。

选择 CEL 而非自造语言的核心理由(来自 KEP-2.16):CEL 已是 Kubernetes 自身的表达式语言(CRD 校验规则、准入策略、鉴权),其 checker 能不求值地给出静态结果类型;而 k8s.io/apiserver/pkg/cel 本就是传递依赖。本 KEP 文档开篇也强调其设计意图"deliberately declarative"、"schema of every substitution reference can be validated at apply time",这与 CEL 类型检查的目标完全一致。

7.2 启用开关与分隔符问题(enablement gates)

KEP-2.16 还处理了一个与本 KEP 高度相关的现实问题:$( ) 是 Kubernetes 自身"依赖环境变量"的语法($(VAR_NAME)),因此表达式特性采用两级开关、三种状态:

状态 EnableCelExpressions RequireCelExpressionOptIn 读取范围
Off(默认) false - 什么都不读
Opt-in true true(默认) 仅带注解 app.oam.dev/cel-expressions: "true" 的 Application
On true false 每个 Application

判断入口收敛在 pkg/sources 的 ExpressionsEnabledFor 这一处,保证准入与渲染对"某个 Application 是否在范围内"不会产生分歧。$$( 用于转义字面 $(——这正是 KEP-2.21 中"检测必须是结构性的、不能破坏存量 $(VAR)"这一顾虑的落地形态。


8. fromDependency:组件 exports 契约驱动的隐式依赖

fromDependency 从兄弟组件的 Component.status.exports 解析,并创建一个隐式顺序依赖——消费组件在生产组件健康之前不会被渲染。完整编写模型见 KEP-2.17,跨拓扑解析见 KEP-2.19。

components:
  - name: database
    type: postgres

  - name: api
    type: webservice
    properties:
      dbHost:
        fromDependency: database.endpoint

8.1 底层机制:exports 契约(KEP-2.17)

exports 声明在 ComponentDefinition 模板中,定义了该组件向同 Application 内其他组件开放的数据契约,是 fromDependency 唯一可读取的表面——消费者无法接触内部字段、status 内部或 properties 本身:

template: {
  output: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    // ...
  }

  // exports 声明公开契约;值是在组件达到 healthy 状态后解析的 CUE 表达式
  exports: {
    endpoint:    context.status.loadBalancer.ingress[0].hostname
    port:        context.parameters.port
    serviceName: context.name
  }

  parameter: {
    port:  *80 | int
    image: string
  }
}
  • exports 的值是 CUE 表达式,可访问 context.status、context.parameters、context.name、context.outputs;在组件健康后于 Spoke 侧求值,并写入 Component.status.exports.*。
  • Application controller 在渲染期从所有 fromDependency 引用构建依赖图;环在准入期被拒绝(带明确标识环的错误),因此依赖图靠强制而天然无环,不需要运行期环检测。
  • 安全边界:exports 只能由 ComponentDefinition 声明(应用清单不能任意声明或覆盖 exports);fromDependency 不能越过声明的 exports 契约读取 properties、原始 status 或未显式导出的 Secret;Secret 值应在 exports 中声明 sensitive: true,由 component-controller 从日志中脱敏并限制其在 status 中的出现。

8.2 跨拓扑 fromDependency(KEP-2.19)

当生产者在集群 A、消费者在集群 C 时,同拓扑的 Spoke 本地解析不再可行,需要 Hub 中介解析。其语法在简写基础上增加 /<group> 段:

      vpcId:
        fromDependency: infra/infra-a.vpcId    # <component>/<group>.<path>

group 指向一个命名拓扑组(named topology group)——一个 config.oam.dev/v1beta1 的 Config 对象(模板为 dispatcher 注册的 cluster-gateway-topology),把逻辑名绑定到 cluster selector。Hub 的解析步骤为:查命名 Config → 调活跃 Dispatcher 的 ResolveTopologyGroup(groupName) 拿到集群列表 → 找到投递到该集群的 Component CR → 读 Component.status.exports.<path> → 在派发前把具体值替换进消费者 properties。跨拓扑引用同样在准入期校验:group 必须引用同 namespace 或 vela-system 中 config.oam.dev/type: topology-group 的 Config、path 必须命中生产方 exports{} 声明的字段、类型必须与消费方 parameter{} 兼容。存量内联 selector 的用户可用 vela migrate topology 命令迁移到命名组(支持 --dry-run)。


9. 非目标(Non-Goals)

为避免范围蔓延,本 KEP 明确划定了三件事不做:

  • 任意运行期属性变更——这些是渲染期(render-time)替换,不是协调期(reconcile-time)替换;
  • 跨 Application 替换——fromDependency 仅限 Application 内部;
  • from* 作为 map 键——只作为值出现。

这三点与 KEP-2.16 / KEP-2.17 的边界一致:KEP-2.17 同样把"跨应用依赖、任意 status 字段访问、运行期依赖编排"列为非目标,并明确 Phase 1 只做渲染期 exports。


10. 跨 KEP 关联索引

本文档是 vNext 提案族中"横向机制"的一环,与以下提案的对应关系:


11. 从设计到现实:如何在当前仓库中继续深入

如果你希望追踪这份设计在当前仓库中的真实形态,推荐按以下路径阅读:

  1. 属性表达式实现:pkg/definition/propexpr/expr.go —— 包注释直接交代了 fromSource 被表达式取代的演进史,Parse / Whole / SoleExpr 实现了"整个值才保留类型、嵌入文本只能产出 string"等关键语义。
  2. SourceDefinition 完整设计:design/vela-core/keps/2.16-source-definition/README.md —— 含 schema:/storage:/template: 分块求值时机表、两层缓存(进程内 LRU + 持久 Config)、onStaleFailure 策略、内置 8 类 SourceDefinition(configmap-local、git-file、http-get、vela-config、vela-app、vela-component、vela-addon、vela-env)、以及 vela status --sources / vela config list 等运维命令。
  3. 组件 exports:design/vela-core/keps/2.17-component-exports/README.md,跨拓扑:design/vela-core/keps/2.19-cross-topology-deps/README.md。
  4. Hub 流水线:design/vela-core/keps/2.3-hub-controller/README.md —— 其中的 Hub Reconcile Pipeline 给出了 fromDependency 注入与 exports 读取在协调流程中的准确位置。
  5. vNext 全景:design/vela-core/keps/README.md —— 其中 "Open Questions" 一节也记录了本 KEP 相关的悬而未决问题(如 from* 是否扩展到 trait/policy properties、Config 分发与 Dispatcher 的关系等),是判断该设计成熟度的关键参考。

小结

KEP-2.21 为 KubeVela vNext 定义了 from* 指令族的共享骨架:渲染期两阶段解析(串行 source 解析 + 单趟递归属性遍历)、结构性检测(唯一键判定,拒绝字符串匹配)、准入期 Schema 契约校验(类型已知即静态可查)、以及简写/ map 双形式语法。三个指令各自落在 spec.parameters、SourceDefinition 与 Component.status.exports 三条数据通道上,覆盖了"应用级常量、外部数据、组件间数据"三类典型注入需求,并通过非目标列表和跨 KEP 引用保持了与其他提案的边界。需要再次强调:这是一份仍在演进中的早期草案,其 fromSource 分支在现实仓库中已被 $( ) CEL 表达式取代,因此在参考时务必以 KEP-2.16 与 pkg/definition/propexpr/expr.go 所代表的"当前实现事实"为准,而把本 KEP 视为理解设计动机与解析模型的入口。

登录后查看全文
kubevela