Dashboard、Data View、Lens 配置和告警规则看起来分属不同功能,落到持久化层却共享同一套 Saved Object 协议。它用 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 系统索引;Space 只是通过 namespace 字段做逻辑隔离,不是把索引拆成 .kibana_<space> 系列。真正会变的是升级时的版本化索引别名,例如 .kibana_9.4.0_001 这种按版本滚动的新索引。

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
12
13
core.savedObjects.registerType({
name: 'dashboard',
hidden: false,
namespaceType: 'multiple-isolated', // 命名空间策略
mappings: { properties: { title: { type: 'text' }, ... } },
modelVersions: {
1: {
changes: [
{ type: 'mappings_addition', addedMappings: { title: { type: 'text' } } }
],
},
},
});

modelVersions 里的每个版本描述一组 schema / mapping 变化。升级 Kibana 时,core 会根据类型注册表把文档转换到目标模型版本;在传统 stack 升级里这是带停机的 v2 迁移,在 Serverless 里则走零停机(ZDT)路径,依赖 forwardCompatibility 让新旧版本短暂共存。migrations 是旧式写法,当前新类型应优先使用 modelVersions

已知的核心类型包括:index-pattern(历史 type 名;UI 现在叫 Data View)、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. 对每条文档按已声明的 modelVersions / 兼容迁移规则依次变换
5. 写入新索引
6. 原子切换别名 .kibana.kibana_8.x.0_001
7. 旧索引保留(.kibana_8.x-1.0_001),可手动删除

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

当前官方文档分别描述 classic stack 的 v2 迁移流程和 Serverless 使用的 zero-downtime 迁移算法。两者的触发条件与运维边界不同,不能把 Serverless 的 ZDT 行为直接套到所有自管理版本上。

实验:通过 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
modelVersions / 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 的访问能力。在传统 v2 升级里,迁移完成后旧别名 .kibana 切换到新索引,旧版本 Kibana 如果仍在运行会看到新格式的文档,通常会因为模型版本高于自身已知版本而拒绝处理,不会静默损坏数据。ZDT 路径则要求类型的 forwardCompatibility 能把新字段收敛回旧版本可理解的形状。

练习

在 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 导读:Kibana 的状态存在 Elasticsearch 里
01 架构:浏览器、Node.js server 与 New Platform
02 Saved Object:一切状态的统一模型
03 Data View:查询之前的字段抽象
04 Search Source 与查询翻译
05 Discover:交互式检索的执行模型
06 聚合式可视化:Visualize 与 bucket/metric
07 Lens:拖拽背后的自动聚合推断
08 TSVB、Timelion 与 Vega
09 Dashboard 与 Embeddable
10 Alerting:Rule、Connector 与 Action
11 Reporting:从 Dashboard 到 PDF/PNG
12 Task Manager:分布式任务调度
13 Spaces 与安全:RBAC、Feature Controls 与多租户
14 插件体系:setup/start 生命周期
15 性能模型:bundle、bootstrap 与异步搜索会话
16 Kibana vs Grafana:两种平台的设计取舍
17 演进:从纯前端到 Platform 再到 Serverless

参考资料