上一篇确立了 Kibana 架构三层的必要性,Node.js server 的存在不是透明代理,而是承载凭据隔离、后台任务和持久化逻辑的应用层。这一篇进入状态层:Kibana 把所有用户配置统一表达为 Saved Object。Saved Object 容易被误解成"配置项的 JSON 存档"。更准确的说法是:它是一套带版本迁移框架、带引用图、带命名空间隔离的持久化协议,所有 Kibana 功能(Discover、Lens、Dashboard、Alerting)都在这套协议之上构建,而不是各自独立地往 ES 写数据。本文只抓一个问题:type、attributes、references 三段结构解决了什么,迁移框架和导入导出的本质是什么。

Saved Object 的结构

每个 Saved Object 在 .kibana 系列索引里是一条 ES 文档,其 _id 格式为 <type>:<uuid>_source 里携带固定的元数据字段加上一个以 type 命名的嵌套对象存放实际载荷:

1
2
3
4
5
6
7
8
9
10
11
Saved Object 文档结构(_source)
────────────────────────────────────────────────
type "dashboard" # 类型标识,对应注册的 SO 类型
id "a1b2c3d4-..." # UUID,不含 type 前缀
attributes { title, panels, ... } # 该类型的实际数据载荷
references [ { type, id, name } ] # 对其他 SO 的引用声明
migrationVersion { "dashboard": "8.0.0" } # 已应用的最高迁移版本
coreMigrationVersion "8.3.0" # core 框架自身的迁移版本
namespaces ["default"] # 所属 Space 列表
updated_at "2026-08-06T10:00:00Z"
created_at "2026-01-01T00:00:00Z"

type 字段决定这条文档用哪套 schema 解析 attributes,也决定 Saved Objects Service 在执行 CRUD 时用哪个迁移函数链。不同 type 的对象共存于同一个 .kibana 索引(自 8.0 起按命名空间路由到 .kibana_<space> 系列,但单个 Space 内仍混存)。

attributes 是对业务逻辑完全不透明的 payload,只有拥有该 type 的插件知道如何解读它。references 是从该对象到其他对象的有向边——Dashboard 引用多个 Lens 对象,Lens 引用 Data View,Data View 不引用其他 SO。这张引用图是导入导出的遍历依据。

type 注册与已知类型

每个 Kibana 插件在 setup 阶段通过 core.savedObjects.registerType(definition) 注册一个或多个 SO 类型。注册时声明:

1
2
3
4
5
6
7
8
9
10
11
core.savedObjects.registerType({
name: 'dashboard',
hidden: false,
namespaceType: 'multiple-isolated', // 命名空间策略
mappings: { properties: { title: { type: 'text' }, ... } },
migrations: {
'7.3.0': migrateTo730,
'7.11.0': migrateTo7110,
'8.0.0': migrateTo800,
},
});

migrations 对象里的每个函数接收旧版本的 attributes,返回新版本的 attributes。升级 Kibana 时,core 会扫描 .kibana 索引里所有文档的 migrationVersion,对版本落后的对象依次应用缺少的迁移函数,写回更新后的文档。这个过程在 Kibana server 启动阶段完成,完成前 server 不接受用户请求。

已知的核心类型包括:index-pattern(自 8.0 起实质上被 data-view 取代,index-pattern 作为别名保留)、visualizationlensdashboardsearch(Saved Search)、alertactiontagspaceconfig(每个 Space 的 Advanced Settings)、url(短链接)。

references 的作用:引用图

references 数组里每个元素是 { type, id, name }name 是在当前对象的 attributes 里引用这个外部对象的局部标识符(不是外部对象的 id)。

以 Dashboard 引用 Lens 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"type": "dashboard",
"id": "dash-001",
"attributes": {
"title": "概览看板",
"panels": [
{ "panelRefName": "panel_0", "type": "lens" }
]
},
"references": [
{ "type": "lens", "id": "lens-xyz", "name": "panel_0" }
]
}

attributes.panels[0].panelRefName"panel_0"referencesname"panel_0" 的条目指向真实的 lens 对象 id "lens-xyz"。这一间接层是为了让导入导出时 id 可以被重映射——同一个 Dashboard 导入到另一个 Kibana 实例时,lens 对象会分配新 id,references 里的 id 随之更新,而 attributes 里的 panelRefName 不变。

这种设计把 id 的稳定性要求从 attributes(业务载荷)里解耦出来,让 attributes 可以被直接 diff,而不用担心 id 引用在不同实例间漂移。

迁移框架:升级时发生了什么

Kibana 大版本升级的迁移过程分为以下阶段(自 7.11 起采用 WAIT_FOR_YELLOW_SOURCE → CLONE → MIGRATE → SWITCH 的四段流程,8.x 维持同一模型):

1
2
3
4
5
6
7
8
9
升级迁移流程(简化)
────────────────────────────────────────────────
1. Kibana 启动,检测 .kibana 当前版本
2. 在 ES 里创建新索引 .kibana_8.x.0_001
3. 从旧索引批量读取文档
4. 对每条文档按 migrations 函数链依次变换
5. 写入新索引
6. 原子切换别名 .kibana.kibana_8.x.0_001
7. 旧索引保留(.kibana_8.x-1.0_001),可手动删除

迁移期间旧索引只读,不影响正在运行的旧版 Kibana(如果做滚动升级)。新索引就绪后,别名切换是单次原子操作。

自 8.0 起引入了"zdt(zero downtime)migration"实验特性,允许在不停机的条件下完成迁移,但截至 8.13 仍为 opt-in 模式。

实验:通过 Saved Objects API 观察结构

在 Dev Tools 里用 Saved Objects API 创建一个简单对象,观察它在 ES 里的实际存储:

1
2
3
4
5
6
7
8
9
10
# 1. 创建一个 tag 类型的 Saved Object(tag 结构最简单)
POST kbn:/api/saved_objects/tag
{
"attributes": {
"name": "实验标签",
"description": "用于观察 SO 结构",
"color": "#0077CC"
}
}
# 返回示例:{ "id": "aabbccdd-...", "type": "tag", ... }
1
2
# 2. 读回这个对象
GET kbn:/api/saved_objects/tag/aabbccdd-...
1
2
# 3. 直接查 .kibana 索引看原始文档
GET .kibana/_doc/tag:aabbccdd-...

第 3 步的响应会展示 _idtype:uuid 格式,以及 _source.type_source.tag(type 名作为嵌套对象键)等字段。对比 API 返回和 ES 原始文档,可以看到 Saved Objects Service 做了哪些字段映射和命名空间注入。

接着导出一个 Dashboard 观察引用图:

1
2
3
4
5
POST kbn:/api/saved_objects/_export
{
"type": ["dashboard"],
"includeReferencesDeep": true
}

响应是 NDJSON(Newline-Delimited JSON),每行一个 Saved Object,最后一行是导出元数据。includeReferencesDeep: true 会让导出器沿着引用图递归收集所有被引用的对象(Lens、Data View 等),保证导入时依赖完整。

将实验结果映射回内部对象

POST /api/saved_objects/<type> 最终调用的是 server 端 SavedObjectsRepositorycreate() 方法。这个 repository 把入参包装成 ES index 请求写入 .kibana 索引,_idtype:uuid 格式生成,_source 里注入 typeupdated_atmigrationVersion 等元数据字段。

_export 端点的实现是 SavedObjectsExporter,它接受 type 列表或 object 列表作为起始节点,通过 SavedObjectsRepository.bulkGet() 批量拉取,再用 references 字段做图的 BFS(广度优先搜索),把所有可达节点收集后序列化为 NDJSON。

模式提炼

引用图替代外键约束。关系型数据库用外键保证引用完整性,Saved Object 在 ES 里没有约束能力,选择把引用声明在 references 数组里并交由应用层(SavedObjectsExporter/Importer)维护一致性。这让 ES 不需要做跨文档的事务操作,代价是删除被引用对象时不会自动级联,需要 Kibana 自己做孤儿清理。

迁移函数链可静态组合。每个版本号对应一个纯函数 (doc) => doc,升级时把函数链按版本排序后依次 reduce,中间结果可以被测试。这比"数据库迁移脚本按顺序执行"的模型更容易做单元测试和回归检查。

工程迁移表

Kibana 概念 CMS 对应 低代码平台对应 Terraform 对应
Saved Object type CMS content type (e.g. Contentful) 组件注册表 / widget registry resource type
attributes CMS 内容 payload 组件 props schema resource arguments
references CMS 关联字段 (link/relation) 组件依赖声明 resource depends_on
migrations CMS content migration script 平台升级 schema migration terraform state migration
namespaces/spaces CMS multi-site / locale 租户隔离 / workspace workspace / module
export/import (NDJSON) CMS 内容迁移包 低代码应用导出包 terraform plan + state

常见误解

不同 Kibana 功能的配置存在不同的"配置文件"里。所有用户配置(Dashboard、Alerting、Data View、Space 设置)都存为 Saved Object,进同一套 .kibana 索引,通过 type 字段区分。唯一例外是 kibana.yml,它存放的是 server 启动级配置(端口、ES 地址、TLS 证书),不是用户运行时配置。

Saved Object 的 id 是稳定的跨实例标识。id 只在单个 Kibana 实例内稳定。导入到另一个实例时,同一个对象会分配新 id(除非使用 overwrite: true 且手动对齐 id)。跨实例共享配置的正确方式是导出-导入流程,不是硬编码 id。

迁移会破坏旧版本 Kibana 的访问能力。迁移完成后旧别名 .kibana 切换到新索引,旧版本 Kibana 如果仍在运行会看到新格式的文档,通常会因为 migrationVersion 高于自身已知版本而拒绝处理,不会静默损坏数据。

练习

在 Dev Tools 里执行 GET .kibana/_mapping,观察 mapping 里有多少个嵌套对象(每个注册的 SO type 都有一个对应的 properties 块),理解为什么 .kibana 的 mapping 会随插件数量增长而变大。

创建一个 Dashboard,往里加两个不同的 Lens 可视化,然后用 POST kbn:/api/saved_objects/_export 导出,用文本工具数一数 NDJSON 里有多少行,对应哪些 type,理解引用图的深度。

在 Kibana 的 Stack Management → Saved Objects 页面,勾选一个 Dashboard 执行导出,再在另一个 Space 里执行导入,观察导入时 id 是否发生变化。

系列导航

主题 核心问题
00 导读:状态存在 ES 里 Kibana 是应用平台,不是 GUI
01 架构:三层与 New Platform 为什么需要 Node.js server
02(本篇) Saved Object:统一状态模型 type / attributes / references
03 Data View:查询前的字段抽象 时间字段、字段格式化、运行时字段

参考资料