Basics

核心概念

  1. Resources 定义了当用户创建自定义 API 实例时,KRO创建管理Kubernetes 对象
  2. 每个 Resource 都是有效Kubernetes YAML,可以使用 CEL 表达式实现动态值注入

Resource 结构

每个 Resource 必须包含两个核心字段

1
2
3
4
5
6
7
8
9
resources:
- id: deployment # 唯一标识符
template: # Kubernetes 资源模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
字段 说明
id RGD唯一标识资源,用于在 CEL 表达式引用
template 有效的 Kubernetes 资源清单

ID 命名规范(重要)

Resource ID 必须使用 lowerCamelCase 格式

1
2
3
4
✓ 有效:   deployment, webServer, postgresDatabase
✗ 无效: web-server (会被解析为减法运算)
WebServer (首字母应小写)
postgres_database (不推荐下划线)

原因:ID 会被用作 CEL 表达式中的标识符,连字符会被解释为减法运算符

CEL 表达式引用

可引用的三种数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
graph TD
subgraph Instance["实例 Instance"]
SchemaSpec["schema.spec<br/>用户配置"]
SchemaMetadata["schema.metadata<br/>实例元数据"]
end

subgraph Resources["Resources"]
R1["Resource 1<br/>database"]
R2["Resource 2<br/>deployment"]
end

R2 -->|"引用"| SchemaSpec
R2 -->|"引用"| SchemaMetadata
R2 -->|"引用"| R1

style SchemaSpec fill:#e1f5fe
style SchemaMetadata fill:#e1f5fe
style R1 fill:#fff3e0
style R2 fill:#c8e6c9

引用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
resources:
- id: database
template:
metadata:
# 引用实例 spec
name: ${schema.spec.name}-db
# 引用实例 metadata
namespace: ${schema.metadata.namespace}
labels: ${schema.metadata.labels}

- id: deployment
template:
metadata:
# 引用另一个 resource 的 metadata
annotations: ${database.metadata.annotations}
spec:
containers:
- env:
# 引用另一个 resource 的 status
- name: DATABASE_HOST
value: ${database.status.endpoint}
# 引用另一个 resource 的 spec
- name: DATABASE_VERSION
value: ${database.spec.version}

隐式依赖机制

关键点:当 Resource A 引用 Resource B 的字段时,自动创建依赖关系

1
2
3
4
5
graph LR
A["deployment"] -->|"依赖<br/>database.status.endpoint"| B["database"]

style A fill:#c8e6c9
style B fill:#fff3e0

KRO 会自动确定创建顺序

  1. database 必须先于 deployment 创建
  2. 因为 deployment 需要等待 database 的 endpoint 可用

验证机制(核心优势)

验证时机

1
RGD 创建时 → 验证所有模板和 CEL 表达式 → 失败则拒绝 RGD

验证内容

验证类型 说明 示例
模板语法 检查 ${} 配对 ${schema.spec.name} ✓
CEL 语法 验证表达式有效性 ${schema..name} ✗
类型检查 匹配字段类型期望 replicas: ${name} ✗ (字符串→整数)
字段存在性 确认引用字段存在 ${deployment.spec.podReplicas} ✗
Schema 验证 自定义字段存在性 ${schema.spec.nonExistent} ✗
静态字段 验证非 CEL 字段 replicas: “three” ✗

处理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
flowchart TB
subgraph Phase1["阶段1: 验证 RGD 创建"]
A1[解析资源模板] --> A2[提取 CEL 表达式]
A2 --> A3[验证 CEL 语法]
A3 --> A4[获取 K8s OpenAPI Schema]
A4 --> A5[类型检查]
A5 --> A6[构建依赖图]
A6 --> A7{验证通过?}
A7 -->|否| Reject[拒绝 RGD]
A7 -->|是| Accept[接受 RGD]
end

subgraph Phase2["阶段2: 实例创建"]
B1[按拓扑顺序处理] --> B2[计算 CEL 表达式]
B2 --> B3{字段可用?}
B3 -->|否| B4[重新排队等待]
B3 -->|是| B5[创建资源]
B5 --> B6[等待 readyWhen]
B6 --> B7[处理下一个资源]
end

Accept --> Phase2

类型安全示例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 整数字段
replicas: ${schema.spec.replicas} # ✓
replicas: ${schema.spec.replicas * 2} # ✓
replicas: ${schema.spec.name} # ✗ 类型错误

# 字符串字段
name: ${schema.spec.name} # ✓
name: ${schema.spec.name + "-deployment"} # ✓
name: ${schema.spec.replicas} # ✗ 类型错误

# 数组字段
containers: ${deployment1.spec.template.spec.containers} # ✓
containers: ${deployment1.spec.template.spec.containers[0]} # ✗ 单对象→数组

常见验证错误

错误类型 示例 错误信息
未知 API/Kind kind: PostgreSQL schema not found
未知字段 spec.unknownField no such member
类型不匹配 replicas: ${name} type mismatch: string but expected integer

关键优势

传统模板引擎 vs KRO

特性 传统模板引擎 KRO
验证时机 运行时 RGD 创建时
类型检查 完整类型检查
Schema 验证 基于 K8s OpenAPI
错误反馈 部署后才发现 即时反馈

核心价值:KRO 在 RGD 创建阶段就完成所有验证,确保用户在创建实例时不会遇到类型错误字段不存在的问题

Conditionals

核心概念

问题场景:并非所有资源都需要在每个实例中创建。例如

  1. 仅当用户请求时才启用监控
  2. 仅在特定环境才启用备份
  3. 仅当需要时才启用 TLS

解决方案:KRO 提供 includeWhen 字段,使资源变为可选

1
2
3
4
5
6
7
flowchart TD
A[实例创建] --> B{评估 includeWhen}
B -->|所有条件为 true| C[创建资源]
B -->|任一条件为 false| D[跳过资源]

style C fill:#c8e6c9
style D fill:#ffcdd2

基础示例

RGD 定义

1
2
3
4
5
6
7
8
9
resources:
- id: ingress
includeWhen:
- ${schema.spec.ingress.enabled} # 仅当启用时才创建
template:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${schema.spec.name}

实例示例

1
2
3
4
5
6
7
8
9
apiVersion: example.com/v1
kind: Application
metadata:
name: my-app
spec:
name: my-app
ingress:
enabled: true # ✓ Ingress 会被创建
# enabled: false # ✗ Ingress 会被跳过

工作原理

条件结果 行为
所有表达式为 true 资源被包含
任一表达式为 false 资源被跳过
条件从 true → false 资源被修剪
条件从 false → true 资源被创建

关键特性

  1. 每个表达式必须返回布尔值truefalse
  2. 条件在每次协调重新评估
  3. 集合资源includeWhen 应用于整个集合

可引用的内容

引用 schema.spec

1
2
3
4
5
6
7
8
9
# ✓ 有效 - 返回布尔值
includeWhen:
- ${schema.spec.ingress.enabled} # 布尔字段
- ${schema.spec.environment == "production"} # 比较表达式
- ${schema.spec.replicas > 3} # 数值比较

# ✗ 无效 - 必须返回布尔值
includeWhen:
- ${schema.spec.appName} # 返回字符串,非布尔值

引用上游资源

1
2
3
# ✓ 有效 - 引用上游资源
includeWhen:
- ${deployment.status.availableReplicas > 0}

注意:当 includeWhen 引用其他资源时,KRO 将其视为依赖关系,条件基于上游资源实际状态进行评估

依赖等待机制

1
2
3
4
5
6
7
8
flowchart TB
A[deployment 创建] --> B{等待 status 就绪}
B -->|availableReplicas 未就绪| C[重新排队等待]
B -->|availableReplicas > 0| D[评估 includeWhen]
D --> E[创建 serviceMonitor]

style C fill:#fff3e0
style E fill:#c8e6c9

示例:等待上游资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resources:
- id: deployment
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${schema.spec.name}

- id: serviceMonitor
includeWhen:
- ${deployment.status.availableReplicas > 0} # 等待副本就绪
template:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor

行为:

  1. 如果 deployment.status.availableReplicas 尚未填充,KRO 会等待重新评估
  2. 不会将未就绪的状态视为 false
  3. 如果 deployment 后来缩容到 0,KRO 会修剪 ServiceMonitor

引用上游资源的风险

问题:状态波动导致翻转

1
2
3
4
5
6
7
8
9
10
flowchart LR
A[availableReplicas > 0] -->|true| B[创建 monitor]
B --> C[副本波动]
C -->|availableReplicas = 0| D[删除 monitor]
D --> E[副本恢复]
E -->|availableReplicas > 0| F[重新创建 monitor]

style B fill:#ffcdd2
style D fill:#ffcdd2
style F fill:#ffcdd2

风险示例

1
2
3
4
5
6
7
8
9
# ⚠️ 危险 - status 字段波动会导致资源反复创建/删除
- id: monitor
includeWhen:
- ${deployment.status.availableReplicas > 0}

# ✓ 安全 - 用户控制的开关,协调期间稳定
- id: monitor
includeWhen:
- ${schema.spec.monitoring.enabled}

决策流程

1
2
3
引用上游资源前问自己:该字段在正常运行期间会来回变化吗?
├─ 是 → 使用 readyWhen 控制顺序,而非 includeWhen
└─ 否 → 可以使用 includeWhen(如 ConfigMap data 等创建后不变的字段)

readyWhen vs includeWhen

特性 includeWhen readyWhen(推断)
作用 控制资源是否存在 控制资源是否就绪
条件为 false 资源被删除/跳过 等待,不删除资源
适用场景 可选功能 等待依赖就绪
状态波动 可能导致翻转 安全,只是等待

依赖传播:跳过资源的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
graph TD
subgraph "条件为 true 的分支"
A1[Deployment ✓] --> B1[Service]
B1 --> C1[Ingress]
end

subgraph "条件为 false 的分支"
A2[Deployment ✗] -.-> B2[Service 跳过]
B2 -.-> C2[Ingress 跳过]
end

style A1 fill:#c8e6c9
style B1 fill:#c8e6c9
style C1 fill:#c8e6c9
style A2 fill:#ffcdd2
style B2 fill:#ffcdd2
style C2 fill:#ffcdd2
  1. 关键规则:当资源因 includeWhen 被跳过时,所有依赖它的资源也会被跳过
  2. 这确保资源图保持一致性,防止资源引用不存在的依赖

dep-cc76ba7133a117efdbb11893170620a0

多条件逻辑

逻辑 AND(默认)

1
2
3
4
5
6
7
8
resources:
- id: certificate
includeWhen:
- ${schema.spec.ingress.enabled} # 条件 1
- ${schema.spec.ingress.tls} # 条件 2
template:
apiVersion: cert-manager.io/v1
kind: Certificate
1
2
3
4
5
6
7
8
9
flowchart TB
A[评估条件] --> B{ingress.enabled?}
B -->|false| D[跳过 Certificate]
B -->|true| C{ingress.tls?}
C -->|false| D
C -->|true| E[创建 Certificate]

style E fill:#c8e6c9
style D fill:#ffcdd2

逻辑 OR单表达式内

1
2
includeWhen:
- ${schema.spec.env == "staging" || schema.spec.env == "production"}
1
2
3
4
5
6
7
8
flowchart TB
A{env == staging?} -->|true| B[创建资源]
A -->|false| C{env == production?}
C -->|true| B
C -->|false| D[跳过资源]

style B fill:#c8e6c9
style D fill:#ffcdd2

最佳实践总结

核心原则:includeWhen 应用于稳定的、用户控制的条件,而非波动的系统状态

场景 推荐方案 原因
用户可选功能 includeWhen: ${schema.spec.feature.enabled} 稳定、用户可控
环境判断 includeWhen: ${schema.spec.env == “prod”} 条件明确
等待资源就绪 readyWhen 避免资源翻转
状态监控 不使用 includeWhen status 字段易波动

Readiness

  1. readyWhenKRO 中用于控制资源何时才算真正可用的机制
  2. 解决了 Kubernetes 资源创建后需要时间才能达到可用状态的问题

核心问题

  1. Kubernetes 中资源创建可用之间存在时间差
    • Deployment 创建了,但 Pod 还在启动中(replicas = 0)
    • LoadBalancer Service 创建了,但还没有分配外部 IP
  2. 如果依赖资源直接使用这些还不存在的值,会失败获取无效数据

基本用法

1
2
3
4
5
resources:
- id: database
readyWhen:
- ${database.status.conditions.exists(c, c.type == "Ready" && c.status == "True")}
- ${database.status.?endpoint != ""}

含义:数据库资源必须同时满足两个条件才算 Ready:

  1. Ready condition 的 status 为 True
  2. endpoint 字段非空

工作原理

情况 行为
无 readyWhen 资源创建所有 CEL 表达式能解析后就 Ready
有 readyWhen 资源创建后等待,直到所有条件true
全部 true 标记为 Ready
任一 false 继续等待

依赖该资源的其他资源会等它 Ready才创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
flowchart TD
A[资源创建] --> B{有 readyWhen?}

B -->|否| C[CEL 表达式可解析?]
C -->|是| D[✅ 标记为 Ready]
C -->|否| E[⏳ 等待解析]
E --> C

B -->|是| F[执行 readyWhen 条件检查]

F --> G{所有条件都 true?}
G -->|是| D
G -->|否| H[⏳ 继续等待]
H --> F

D --> I[🚀 依赖资源可以创建]

style D fill:#90EE90
style E fill:#FFD700
style H fill:#FFD700
style I fill:#87CEEB

语法规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ✓ 正确 - 引用自身,返回布尔值
readyWhen:
- ${deployment.status.availableReplicas > 0}

# ✓ 正确 - 集合中使用 each
readyWhen:
- ${each.status.phase == 'Running'}

# ✗ 错误 - 不能引用其他资源
readyWhen:
- ${service.status.loadBalancer.ingress.size() > 0}

# ✗ 错误 - 不能引用 schema
readyWhen:
- ${schema.spec.replicas > 3}

# ✗ 错误 - 必须返回布尔值
readyWhen:
- ${deployment.status.availableReplicas}

设计哲学

核心原则:局部性单一职责

1
2
3
4
5
┌─────────────────────────────────────────────────────────────┐
│ readyWhen 设计哲学 │
├─────────────────────────────────────────────────────────────┤
│ "每个资源只负责判断自己的 readiness,不关心外部世界" │
└─────────────────────────────────────────────────────────────┘

只能引用自身 → 局部确定性

1
2
3
4
5
6
7
# ✓ 正确:只关心自己的状态
readyWhen:
- ${deployment.status.availableReplicas > 0}

# ✗ 错误:关心其他资源
readyWhen:
- ${service.status.loadBalancer.ingress.size() > 0}

设计理由

理由 说明
避免循环依赖 A 引用 B,B 引用 A → 死锁
职责分离 依赖关系模板引用处理,readyWhen 只管自己
可调试性 问题隔离在单个资源内
可测试性 单个资源的 readiness 可独立验证
1
2
3
4
5
6
7
8
9
10
          依赖关系 (模板引用)
┌─────────────────────────┐
│ │
┌────▼────┐ ┌────▼────┐
│ A │ │ B │
│ readyWhen │ │ readyWhen │
└────┬────┘ └────┬────┘
│ │
└─────────────────────────┘
只检查各自状态

不能引用 schema → 关注运行时状态

1
2
3
4
5
6
7
# ✗ 错误:schema 是配置,不是状态
readyWhen:
- ${schema.spec.replicas > 3}

# ✓ 正确:status 才是运行时状态
readyWhen:
- ${deployment.status.readyReplicas == deployment.spec.replicas}

设计理由 - Readiness 只关注实际状态

1
2
3
4
5
6
7
    输入                    处理                    输出
┌─────────┐ ┌─────────┐ ┌─────────┐
│ schema │ ────────► │Kubernetes│ ────────► │ status │
│(期望状态)│ │ 调谐 │ │(实际状态)│
└─────────┘ └─────────┘ └─────────┘

readiness 只关心这里
概念 特性 readyWhen 应该用?
schema.spec 用户输入的期望值 ❌ 这是配置,不是状态
status Kubernetes 控制器反馈的实际值 ✅ 这才是 readiness

核心思想:Readiness运行时概念,应该基于实际观察到的状态,而非配置的期望

必须返回布尔值 → 语义清晰

1
2
3
4
5
6
7
# ✗ 错误:返回数字,语义不明
readyWhen:
- ${deployment.status.availableReplicas}

# ✓ 正确:明确判断条件
readyWhen:
- ${deployment.status.availableReplicas > 0}

设计理由

1
2
3
4
5
6
7
8
9
   readyWhen 的本质
┌────────────────────────┐
"准备好了吗?"
答案只能是:
true (准备好了)
false (还没好)
└────────────────────────┘

除此之外的回答都是无效的
  1. 避免歧义:数字 5 是 ready 还是 not ready?
  2. 统一接口:所有 readyWhen 条件都是布尔表达式
  3. 组合友好:多个条件可以用 AND 逻辑自然组合

集合使用 each → 粒度控制

1
2
3
4
5
# ✓ 正确:每个 Pod 都要 Running
forEach:
- pod: ${schema.spec.pods}
readyWhen:
- ${each.status.phase == 'Running'}

设计理由

1
2
3
4
5
6
7
8
9
10
11
12
    集合 Readiness 的两种语义

┌────────────────┐ ┌────────────────┐
│ 整体级别 │ │ 元素级别 │
├────────────────┤ ├────────────────┤
│ "至少一个Ready" │ │ "全部Ready" │
│ │ │ │
│ □ □ □ ■ □ │ │ ■ ■ ■ ■ ■ │
│ Ready! │ │ Ready! │
└────────────────┘ └────────────────┘

KRO 选择后者:更安全、更可预测

each 让每个元素独立判断 readiness,集合整体 ready 当且仅当所有元素ready

设计理念总结

readyWhen 是一个局部的、基于状态的、布尔返回自描述 readiness 检查机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────────┐
│ readyWhen 设计原则 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 局部性 → 只关心自己,不依赖外部 │
│ 2. 实时性 → 基于 status,不基于 spec │
│ 3. 布尔性 → 答案明确,非真即假 │
│ 4. 组合性 → 依赖关系由模板层处理,readyWhen 不重复 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 架构分离 │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 模板引用 │ 决定 │ 创建顺序 │ │
│ │ (dependencies) ───► │ (ordering) │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ readyWhen │ 决定 │ 等待时长 │ │
│ │ (readiness) ───► │ (timing) │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ 两个机制各司其职,互不干扰 │
└─────────────────────────────────────────────────────────────────┘

可选操作符 ?

? 返回 null 而不是报错,适合处理可选字段

1
2
3
4
5
# 用于真正可选或结构未知的字段
- ${service.status.?loadBalancer.?ingress.size() > 0}

# 用于会延迟出现但最终会存在的字段
- ${database.status.endpoint != ""}

核心机制

1
${service.status.?loadBalancer.?ingress.size() > 0}

? 是 CEL安全导航操作符null-safe navigation),类似其他语言中的

1
2
3
- JavaScript: service?.status?.loadBalancer
- Rust: service.as_ref()?.status.as_ref()?.loadBalancer
- Swift: service?.status?.loadBalancer

行为对比

1
2
3
4
5
6
7
8
9
# 没有 ? - 传统访问
${service.status.loadBalancer.ingress.size()}

# 路径中任何一环为 null/undefined → ❌ 报错

# 有 ? - 安全访问
${service.status.?loadBalancer.?ingress.size()}

# 路径中任何一环为 null → ✅ 返回 null,继续执行

使用场景决策树

1
2
3
4
5
6
7
8
9
10
11
字段是否存在可预测?

├─ 可预测且最终一定存在
│ └─ 不用 ?
│ └─ ${database.status.endpoint != ""}
│ └─ KRO 会等待字段出现

└─ 可预测性未知或真正可选
└─ 用 ?
└─ ${service.status.?loadBalancer.?ingress.size() > 0}
└─ 字段不存在时返回 null,不阻塞

场景分类

场景 1:延迟出现但最终必有的字段 → 不用 ?

1
2
readyWhen:
- ${database.status.endpoint != ""}

为什么不用 ?

1
2
3
4
5
6
时间轴:
─────────────────────────────────────────────────────────→
t=0 t=5s t=10s t=15s
创建 endpoint=null endpoint="" endpoint="10.0.0.1"

此时条件变为 true
  1. endpoint 字段最终会存在
  2. KRO 知道在等待什么,会持续检查
  3. 一旦字段出现值有效,条件满足

场景 2:真正可选的字段 → 用 ?

1
2
readyWhen:
- ${service.status.?loadBalancer.?ingress.size() > 0}

为什么用 ?

1
2
3
4
Service 类型决定:
├─ ClusterIP → loadBalancer 永远不存在
├─ NodePort → loadBalancer 永远不存在
└─ LoadBalancer → loadBalancer 可能存在
  1. loadBalancer 字段可能永远不存在
  2. 不用 ? 会导致:ClusterIP 类型的 Service 永远不 ready
  3. ? 后:null.size()null → 条件为 false继续等待按业务逻辑处理

场景 3:结构未知的字段 → 用 ?

1
2
readyWhen:
- ${config.data.?endpoint != ""}

为什么用 ?

1
2
3
4
5
6
7
8
9
# ConfigMap 的 data 结构完全动态
apiVersion: v1
kind: ConfigMap
data:
# 可能包含 endpoint
endpoint: "http://api.example.com"

# 也可能不包含
# database_url: "postgres://..."
  1. data.endpoint 的存在与否取决于用户配置
  2. 无法预测字段是否存在
  3. ? 避免”字段不存在“的错误

执行流程对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────────┐
│ 无 ? 操作符 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ${service.status.loadBalancer.ingress.size() > 0} │
│ │
│ 1. service.status → ✅ 对象 │
│ 2. .loadBalancer → ❌ null │
│ 3. .ingress → 💥 报错:无法读取 null 的属性 │
│ 4. 整个条件 → 💥 异常,可能触发重试或失败 │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 有 ? 操作符 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ${service.status.?loadBalancer.?ingress.size() > 0} │
│ │
│ 1. service.status → ✅ 对象 │
│ 2. .?loadBalancer → ⚠️ null │
│ 3. .?ingress → ⚠️ null (短路求值) │
│ 4. .size() → ⚠️ null (null.size() = null) │
│ 5. null > 0 → ⚠️ false/null │
│ 6. 整个条件 → ✅ false,继续等待 │
│ │
└─────────────────────────────────────────────────────────────────┘

常见模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 模式 1:嵌套可选字段
readyWhen:
- ${resource.status.?nested.?deep.?field != ""}

# 模式 2:可选字段 + 默认值处理
readyWhen:
- ${resource.status.?optionalField != null && resource.status.optionalField.length > 0}

# 模式 3:集合可选访问
readyWhen:
- ${resource.status.?items.size() > 0}

# 模式 4:条件中使用 ? 避免 false positive
readyWhen:
# 如果 loadBalancer 不存在,整个表达式为 null/false
- ${service.status.?loadBalancer != null ? service.status.loadBalancer.ingress.size() > 0 : true}

决策指南

不用 ? - 确定存在 + 最终一定会出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                  字段是否确定存在?

┌───────────────┴───────────────┐
│ │
是 否
│ │
│ │
字段最终一定会出现? 必须使用 ?

┌─────┴─────┐
│ │
是 否
│ │
不用 ? 用 ?
│ │
KRO 等待 容忍不存在
场景 是否用 ? 原因
数据库连接字符串 创建后必然出现
LoadBalancer ingress ClusterIP 时不存在
Pod 的 status.phase Pod 必有 status
CRD 的可选 status 字段 取决于控制器实现
ConfigMap 的 data 键 用户自定义,不确定

总结

?容错机制:用于处理”字段可能不存在“的结构性不确定性,而非”字段尚未出现“的时序性延迟

依赖链示例

KRO 自动按依赖顺序处理:先创建 database → 等待其 Ready → 再创建 app(此时 endpoint 已有值)

1
2
3
database (readyWhen: endpoint != "")
↓ 等待 database Ready
app (使用 database.status.endpoint)

Collections

核心设计动机

  1. 传统 KRO 资源定义是 1:1 的 - 一个 resource 条目创建一个 K8s 对象
    • 如果需要 5 个 worker Pod,就得写 5 个 resource 定义
  2. 数量固定时没问题,但数量依赖运行时数据时就无能为力了
    • 比如可用区数量租户数worker 数等都是动态的

forEach1:1 变成了 1:N,一个 resource 定义变成一个”模板“,根据迭代数据动态创建 N 个资源

forEach 的本质

1
2
forEach:
- worker: ${schema.spec.workers} # 迭代变量: CEL 表达式(必须返回数组)

关键点

维度 说明
语法 数组,每个元素是一个单 entry map{变量名: CEL数组表达式}
变量名 template 中作为 CEL 变量可用,建议用语义化名称(region、dbSpec)而非 item、i
CEL 必须返回数组 不是数组的表达式会失败
数组顺序 = 资源顺序 确定性可预测索引命名

多迭代器 = 笛卡尔积

这是最容易出问题的地方,两个迭代器不是”分别创建“,而是全排列组合

1
2
regions: ["us-east", "us-west"]  ×  tiers: ["web", "api"]
= 4 个 Deployment:us-east-web, us-east-api, us-west-web, us-west-api
  1. 嵌套顺序:第一个迭代器是外层循环,后续迭代器依次嵌套顺序确定性
  2. 危险:维度爆炸
    • 3 regions × 5 tiers × 10 shards = 150 个资源
    • KRO 默认上限1000 个/集合10 个维度,都是可配置
  3. 空的任意迭代器 = 零资源(2 × 0 = 0),符合笛卡尔积数学语义

资源命名的硬性要求

每个集合内资源必须有全局唯一的 name,必须把所有迭代变量都编入 metadata.name

1
2
3
4
5
# 正确:包含 region + tier
name: ${schema.metadata.name + '-' + region + '-' + tier}

# 错误:省略了 tier → 多个资源会撞名覆盖
name: ${schema.metadata.name + '-' + region}
  1. 还要包含 schema.metadata.name 避免跨实例冲突
  2. 对于集群范围资源无 namespace),所有维度都必须放在 metadata.name

集合的数据源

迭代数组可以从多个地方来

来源 示例 特点
实例 Spec ${schema.spec.regions} 用户直接输入
资源 Spec ${database.spec.shards} 依赖上游资源的状态
资源 Status ${cluster.status.brokers} 运行时动态数据
数组字面量 ${[“a”, “b”, “c”]} 静态列表
CEL 函数 ${lists.range(3)} → [0,1,2] 生成索引序列
  1. 引用其他资源spec/status 时,自动创建依赖
  2. kro 会等待上游集合完全 ready 后才开始创建下游

集合间的引用与依赖

集合暴露为一个数组,其他资源可以用 CEL 函数操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单个资源引用集合
- id: summary
template:
data:
podNames: ${workerPods.map(p, p.metadata.name).join(', ')}
count: ${string(size(workerPods))}

# 集合引用集合
- id: backupJobs
forEach:
- db: ${databases} # 遍历另一个集合
template:
metadata:
name: ${schema.metadata.name + '-backup-' + db.metadata.name}

依赖传播机制

  1. 集合 B 引用集合 A → kro 等待 A 中所有资源ready 才开始创建 B
  2. 这是一种”全或无“的屏障语义

readyWhen 与集合

集合使用 each 关键字做逐项检查

1
2
3
4
forEach:
- worker: ${schema.spec.workers}
readyWhen:
- ${each.status.phase == 'Running'} # 每一项都必须满足
  1. 语义是 AND - 所有元素通过才算 ready,而空集合视为 ready没有元素需要等待
  2. 这与单个资源readyWhen 只能引用自身的设计哲学一致 - each集合的”自身”

includeWhen 与集合

  1. includeWhen 作用于整个集合,不能过滤单项,条件为 false整个集合跳过
  2. 过滤单项,用 filter()forEach 表达式中实现:
1
2
forEach:
- dbSpec: ${schema.spec.databases.filter(d, d.backupEnabled)} # 只迭代启用了备份的

集合生命周期

事件 行为
扩容(数组增加元素) 创建新资源
缩容(数组减少元素) 自动删除对应资源(通过 applyset 机制修剪)
空数组 零资源,不是错误,集合视为 ready
漂移 外部修改自动恢复期望状态

内部机制

  1. RGD 验证时(静态分析
    • 分析 forEach 表达式推导元素类型(如 []string → 每个迭代变量string),实现静态类型检查
  2. 运行时
    • 集合节点依赖图中是单个节点,但创建多个资源
    • 每个资源独立追踪,单个失败不影响其他
  3. 自动标签
    • kro.run/node-id - RGD 中的资源 ID
    • kro.run/collection-index — 集合内位置0-indexed
    • kro.run/collection-size — 集合总数
    • kro.run/instance-id — 实例 UID

Map 迭代的坑

Go 中 map 迭代顺序随机的,如果迭代 map keys/values,必须先转为排序数组

1
2
forEach:
- key: ${schema.spec.labels.map(k, k).sort()} # 排序确保确定性

建议:设计 Schema 时优先用数组而非 map - 更好的 patch 语义可扩展性确定性顺序

External References