Kibana 3 曾让浏览器直接连接 Elasticsearch,Kibana 4 以后却在中间加入了 Node.js server。这个变化不只是部署形态调整:凭据隔离、后台任务、报表生成和插件生命周期都需要一个应用服务层。沿着一次浏览器请求向下追踪,才能看清三层架构各自承担的职责。

三层结构

请求从浏览器出发,经过 Node.js server,最终到达 Elasticsearch。每一层的职责边界如下:

1
2
3
4
5
6
7
8
Browser                  Node.js Server              Elasticsearch
─────────────────── ────────────────────────── ───────────────────────
React UI (plugins) ──► HTTP routes / auth proxy ──► .kibana system indices
KQL / Lens editor Saved Objects service business indices
Dashboard renderer Task Manager _cluster / _nodes / _cat
Dev Tools terminal Reporting engine
Alerting executor
Plugin setup/start lifecycle

浏览器端是一批 React 应用,以插件 bundle 的形式加载。每个 Kibana 功能区(Discover、Lens、Dashboard、Alerting)都是独立插件,在浏览器里注册到 core 提供的客户端框架。

本文按 8.13 的历史锚点展开;截至 2026-08-25,Elastic 文档里的 current 线已经是 9.x,最新公开 GA 版本为 9.4.4。

Node.js server 拦截所有浏览器到 ES 的请求,在这里验证凭据、注入 Kibana 自己的认证头,然后以 Kibana 服务账号的身份向 ES 发出实际请求。ES 看到的始终是 Kibana server 的凭据,浏览器端的用户凭据不直接到达 ES。

Elasticsearch 同时扮演两个角色:业务数据的存储和查询后端,以及 Kibana 自身状态的持久化存储(.kibana* 系列索引)。

为什么 Kibana 必须有自己的 server

纯浏览器直连 ES 的架构(Kibana 3 的方式)在以下场景下失效:

凭据安全问题。浏览器直连要求把 ES 的地址和认证信息暴露给前端。任何打开页面的用户都能拿到这些凭据直接访问 ES。加一层 server 后,凭据留在 server 端,浏览器只持有对 Kibana HTTP 端点的会话 token。

后台任务调度。告警规则要定时执行查询——用户不开着浏览器这个任务也得跑。Kibana 的 Task Manager 是一个跑在 server 端的调度器,把任务定义存在 .kibana_task_manager 索引,server 启动后持续轮询待执行任务。这类长周期后台逻辑在浏览器里没有稳定的执行环境。

报表生成。Reporting 功能用 headless Chromium 渲染 Kibana 页面截图,或把 Discover 结果导出为 CSV。headless 浏览器和文件系统 IO 都只能在 server 端完成。

机器学习与数据摄入辅助。自 7.x 起 Kibana 内置了多个数据向导(Fleet、Integrations),这些功能需要代理 ES 的 Index Template API 调用,并在 server 端做配置校验。

platform 插件模型

Kibana 7.x 完成了从 Legacy Platform 到 platform 的迁移,插件生命周期从简单的"注册一堆全局对象"变成了显式的 setup/start/stop 三段式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Plugin Lifecycle (server side)
──────────────────────────────────────────────────────
Kibana core bootstrap

├─ plugin.setup(core, plugins)
│ 注册路由、注册 Saved Object 类型、声明依赖
│ 此时其他插件还未 start,不能调用跨插件 API

├─ plugin.start(core, plugins)
│ 所有插件已初始化完毕,可以调用跨插件服务
│ Task Manager 在此阶段注册任务类型
│ Alerting 在此阶段注册规则类型

└─ plugin.stop()
清理定时器、关闭连接

同样的三段式在浏览器端也有对应,browser-side 插件的 setup 阶段注册导航菜单项和路由,start 阶段初始化与 server 的连接。

平台的核心约束是:插件间依赖必须在 setup 阶段显式声明,不允许全局单例引用。这一设计让 Kibana 的依赖关系可以在启动时做图遍历检查,循环依赖会直接报错;开发态的 bundle 变化可以更快反馈,但生产部署仍以完整实例重启为准,不存在通用的“单插件热更新”。

请求链路追踪

一次从 Discover 发出的查询,在三层里的路径如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 浏览器 Discover 插件
构造 KQL → 转换为 ES Query DSL
POST /internal/search/es (Kibana internal API)

2. Node.js server
验证用户会话 (cookie / API key)
调用 data.search 服务 (Search Service)
Search Service 注入 kibana.version
向 ES 发 POST /<target_indices>/_search

3. Elasticsearch
执行查询,返回 hits + aggregations

4. Node.js server
把 ES 响应按 Kibana 的 response schema 封装
返回给浏览器

5. 浏览器
Discover 插件渲染 hits 表格和时间轴

浏览器看到的是 Kibana 的 internal API(路径通常是 /internal//api/),从不直接调 ES。这一层隔离让 Kibana 可以在 server 端做限流、做结果缓存、做字段格式化,而浏览器插件不需要知道 ES 的实际地址。

实验:通过 Status API 观察 server 信息

Kibana 暴露了一个公开的 status 端点,可以不登录查看 server 侧基本信息:

1
curl -s http://localhost:5601/api/status | python3 -m json.tool | head -60

响应里的关键字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "kibana",
"uuid": "...",
"version": {
"number": "8.13.0",
"build_hash": "...",
"build_date": "..."
},
"status": {
"overall": { "level": "available" },
"core": {
"elasticsearch": { "level": "available", "summary": "..." },
"savedObjects": { "level": "available", "summary": "..." }
}
},
"metrics": {
"process": { "memory": { "heap": { "used_in_bytes": ... } } },
"os": { "load": [...] }
}
}

status.core.elasticsearch 反映 Kibana server 与 ES 的连通状态,status.core.savedObjects 反映 .kibana 系列索引的迁移状态。当前的 /api/status 响应外层还包含 metricsnamestatusuuidversion;其中 status.core.savedObjects 仍然是判断迁移是否完成的关键字段。

用 ES 的 _nodes/stats 也可以观察到 Kibana 对 ES 的 HTTP 连接:

1
GET _nodes/stats/http

在返回的 nodes.<node_id>.http.total_opened 里,持续打开的 HTTP 连接中有一部分来自 Kibana server 的连接池(自 8.x 起默认使用长连接池,减少 TLS 握手开销)。

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

Status API 的 status.core.savedObjects 对应 server 端的 SavedObjectsService——这个服务在 setup 阶段接收各插件注册的类型定义,在 start 阶段完成 .kibana 索引的 mapping 迁移(如有)后才标记为 available。

status.core.elasticsearch 对应 ElasticsearchService,管理 Kibana server 到 ES 的 HTTP 客户端。当前 API 名称是 core.elasticsearch.client.asInternalUsercontext.core.elasticsearch.client.asCurrentUser;前者用于系统级操作(如 .kibana*),后者在路由处理器里代表当前请求用户。

模式提炼

三层架构里值得提炼的两个设计决策:

凭据作用域分离。server 用 asInternalUser 操作系统索引,用 asCurrentUser 代理用户请求。两个客户端的凭据完全独立,保证了 Kibana 系统索引的操作权限不会因为一个低权限用户的请求而受影响。

插件生命周期显式化。setup/start/stop 强制插件在正确的时间点注册服务和路由,不允许"在某个函数里偷偷初始化全局对象"。代价是插件开发者要理解两阶段启动的约束,收益是整个系统的依赖关系可静态分析。

工程迁移表

Kibana 概念 Grafana 对应 BI 工具对应 Next.js / Nuxt 模式
Node.js server 凭据代理 Grafana backend plugin data proxy BI server-side JDBC/ODBC driver API routes with server-side fetch
platform setup/start Grafana plugin activate() 无显式生命周期 nuxt.config module setup hooks
Task Manager Grafana internal scheduler (alerting) BI scheduled reports engine Vercel Cron Jobs / BullMQ
Reporting (headless Chrome) Grafana image renderer plugin BI PDF export service Puppeteer in serverless function
Plugin bundle splitting Grafana lazy panel loading 无直接对应 Next.js dynamic imports

常见误解

Kibana server 只是 ES 的反向代理。实际上 server 端有 Task Manager、Reporting、Alerting executor、Saved Objects migration 等完全不是代理的逻辑。纯粹的代理不需要自己的数据存储,而 Kibana 依赖 .kibana_task_manager 索引来协调多实例下的任务分配。

多个 Kibana 实例会造成配置冲突。多实例模式下(水平扩展),每个实例都连同一个 ES 集群,.kibana 索引是共享存储。Task Manager 用 optimistic locking(版本号 + seq_no)来协调任务认领,保证同一任务只被一个实例执行。

升级 Kibana 时 ES 的业务数据会受影响。Kibana 的迁移只写 .kibana* 系列系统索引,不触碰业务索引。.kibana_8.x.0_001 这类带版本号的索引是每次大版本升级后新建的,旧索引保留一段时间后由迁移脚本归档或删除。

浏览器插件可以直接调 ES API。自 platform 起 ES 的地址和凭据对浏览器端插件完全不可见,插件只能通过 data.searchhttp.fetch 这类 core 提供的 client 间接访问 ES。

练习

在 Dev Tools 里执行 GET _cat/indices/.kibana*?v,观察有多少个 .kibana 系列索引,它们的文档数量分别是多少。对比 Kibana 大版本号和索引名中的版本号。

在 Kibana 的 Stack Management → Task Manager Health 页面(路径 /app/management/insightsAndAlerting/taskManager)观察当前实例的任务调度状态,重点看 driftload 和容量相关字段,理解多实例下的任务竞争模型。

curl -s http://localhost:5601/api/status 在 Kibana 启动后的不同阶段多次调用,观察 status.core.savedObjectsinitializing 变为 available 的过程,理解迁移锁的存在。

系列导航

篇目 主题
00 导读:Kibana 的状态存在 Elasticsearch 里
01 架构:浏览器、Node.js server 与 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

参考资料