Kibana:从 Discover 排障到 Space 权限治理
Discover 显示零条,先别把时间范围拉到一个月
Kibana 最容易制造误判的页面不是管理页,而是 Discover。搜索结果为空时,人很自然地扩大时间窗、把 Data View 改成通配符,甚至切到拥有更多权限的账号。这样确实可能“搜到东西”,也可能一次扫过大量历史分片,把别的业务索引和敏感字段带进浏览器。
空结果至少有四层来源:浏览器所在的 Kibana Space 不对,Data View 没覆盖目标索引,时间字段或时区过滤掉了文档,当前 Elasticsearch 角色无权读取目标索引。Kibana feature privilege 又会决定用户能不能进入 Discover、Dashboard 或 Dev Tools。页面可见性和数据权限是两条轴;只查其中一条,排障会在 UI 与集群之间来回猜。
Kibana 也不是一组静态网页。浏览器先访问 Kibana 服务,服务处理会话、Space、Saved Objects 和功能权限,再把搜索请求送到 Elasticsearch。Kibana 自己使用的服务身份负责内部索引和后台任务,最终用户访问业务数据仍受自身角色约束。把 Kibana 服务账号交给人使用,会把这两种责任混在一起。
它必须跟着 Elasticsearch 一起选型和升级
Kibana 属于 Elastic Stack。自建环境中,Elasticsearch 与 Kibana 应固定到同一发行版本;升级顺序是先 Elasticsearch、后 Kibana,并最终保持版本一致。官方明确不支持把多台 Kibana 做跨版本滚动升级:旧实例应先全部停止,再启动升级后的实例。Kibana 启动时还可能执行 Saved Objects 迁移,所以回滚依据不能只是一份旧镜像,必须包含 Kibana feature state 的集群快照。
发行包已经带有受支持的 Node.js。自行换系统 Node.js 不会让安装更“标准”,只会多出一条未经产品支持的运行时变量。插件同样要跟着 Kibana 版本更新;镜像升级而插件仍停在旧版,常见结果是启动阶段直接拒绝加载。
如果后端采用 OpenSearch,控制台应选择 OpenSearch Dashboards。两者都经常监听 5601,也都能显示类似的 Discover 和 Dashboard,但配置、插件、权限对象与 Saved Objects 不构成互换契约。
先在回环地址上证明服务链路
下面的容器只适合一次性本地实验。它关闭 Elasticsearch 安全能力,并把端口绑定到回环地址;不要把这段配置放到共享开发机、办公网服务器或云主机。STACK_VERSION 必须由执行者固定,不能在团队脚本里漂移到 latest。
: "${STACK_VERSION:?set an exact Elastic Stack version}"
docker network create kibana-lab
docker run -d --name elasticsearch-kibana-lab \
--network kibana-lab \
-p 127.0.0.1:19200:9200 \
-e discovery.type=single-node \
-e xpack.security.enabled=false \
-e ES_JAVA_OPTS="-Xms1g -Xmx1g" \
docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
docker run -d --name kibana-lab \
--network kibana-lab \
-p 127.0.0.1:15601:5601 \
-e ELASTICSEARCH_HOSTS=http://elasticsearch-kibana-lab:9200 \
docker.elastic.co/kibana/kibana:${STACK_VERSION}容器显示 Up 只能说明进程还在。先核对 Elasticsearch 的产品身份,再读 Kibana 日志中的就绪状态,最后才打开浏览器。
curl -fsS http://127.0.0.1:19200/
docker logs --tail 120 kibana-lab
curl -I http://127.0.0.1:15601/后端根响应应包含 Elasticsearch 版本,Kibana 日志不应出现版本不兼容、插件失败或反复迁移,浏览器入口应返回 HTTP 响应。内存不足时 Elasticsearch 可能先退出;这时保留两端日志,比连续重启更有价值。
kibana.yml 同时描述网络、服务身份和密钥责任
生产配置至少要回答:进程监听在哪里,用户看到哪个公共地址,Kibana 连接哪些 Elasticsearch 节点,以什么服务身份连接,信任哪条 CA 链,多个 Kibana 实例如何解密同一批会话和 Saved Objects。
server.host: "127.0.0.1"
server.port: 5601
server.publicBaseUrl: "https://kibana.example.test"
elasticsearch.hosts:
- "https://es-coordinator-a.example.test:9200"
- "https://es-coordinator-b.example.test:9200"
elasticsearch.serviceAccountToken: "${KIBANA_SERVICE_ACCOUNT_TOKEN}"
elasticsearch.ssl.certificateAuthorities:
- "/etc/kibana/certs/elastic-ca.pem"
elasticsearch.ssl.verificationMode: full
xpack.security.encryptionKey: "${KIBANA_SECURITY_KEY}"
xpack.encryptedSavedObjects.encryptionKey: "${KIBANA_SAVED_OBJECTS_KEY}"
xpack.reporting.encryptionKey: "${KIBANA_REPORTING_KEY}"server.host 是进程监听面,server.publicBaseUrl 是反向代理之后的外部身份。后者配错时,登录回调、Cookie 和分享链接会指向错误协议或内网地址。elasticsearch.hosts 则是服务到集群的内部路径,不是浏览器入口。
verificationMode: full 同时检查 CA 和主机名。证书报错应修正域名、SNI、证书 SAN 或 CA 挂载,不能把校验关掉。三个 encryption key 也不是随容器启动生成的临时值:多实例必须共享稳定的密钥,重建后仍要能解密既有会话、连接器和报表任务。密钥只通过 Secret 管理系统注入,配置仓库保留变量引用和轮换责任人。
用三条样本把 API、Data View 和 Discover 对齐
先在隔离实验索引写入不含真实业务信息的样本。时间在执行时生成,避免固定示例早已落出 Discover 默认窗口。
NOW="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
curl -fsS -X PUT 'http://127.0.0.1:19200/kibana-lab-logs' \
-H 'Content-Type: application/json' \
-d '{
"mappings": {"properties": {
"@timestamp": {"type": "date"},
"service": {"type": "keyword"},
"status": {"type": "keyword"},
"trace_id": {"type": "keyword"},
"message": {"type": "text"}
}}
}'
curl -fsS -X POST 'http://127.0.0.1:19200/_bulk?refresh=wait_for' \
-H 'Content-Type: application/x-ndjson' \
--data-binary "{\"index\":{\"_index\":\"kibana-lab-logs\"}}
{\"@timestamp\":\"${NOW}\",\"service\":\"checkout\",\"status\":\"ERROR\",\"trace_id\":\"trace-a\",\"message\":\"payment timeout\"}
{\"index\":{\"_index\":\"kibana-lab-logs\"}}
{\"@timestamp\":\"${NOW}\",\"service\":\"checkout\",\"status\":\"OK\",\"trace_id\":\"trace-b\",\"message\":\"request complete\"}
{\"index\":{\"_index\":\"kibana-lab-logs\"}}
{\"@timestamp\":\"${NOW}\",\"service\":\"catalog\",\"status\":\"OK\",\"trace_id\":\"trace-c\",\"message\":\"request complete\"}
"Bulk 响应中的 errors 应为 false。进入 Dev Tools,先做一条范围明确、字段受控的查询:
GET kibana-lab-logs/_search
{
"size": 10,
"timeout": "5s",
"track_total_hits": 1000,
"_source": ["@timestamp", "service", "status", "trace_id"],
"sort": [{"@timestamp": "asc"}],
"query": {
"bool": {"filter": [
{"term": {"service": "checkout"}},
{"range": {"@timestamp": {"gte": "now-5m", "lte": "now"}}}
]}
}
}这里应得到两条文档,而且 _source 不包含 message。size 只限制返回数量,不限制底层扫描;timeout 也不是容量治理。响应若出现 timed_out、分片失败或总命中关系只是下界,就不能把当前数字当作完整结论。
随后在当前 Space 创建仅覆盖 kibana-lab-logs 的 Data View,时间字段选择 @timestamp。Discover 使用相同时间窗和 service: checkout 条件,也应得到两条。Dev Tools 有结果、Discover 没结果时,采集链路已经不是首要怀疑对象;应核对当前 Space、Data View、时间字段、浏览器时区、KQL 过滤器与用户角色。
Space 隔离对象,角色约束功能和数据
Kibana Space 为 Dashboard、Visualization、Data View 等 Saved Objects 提供命名和访问边界。隐藏某项功能只改变当前 Space 的展示,不构成安全授权。真正的访问结果来自 Kibana privilege 与 Elasticsearch privilege 的并集。
| 控制面 | 管什么 | 常见误判 |
|---|---|---|
| Space 与功能可见性 | 当前空间展示哪些应用和对象 | 菜单隐藏就等于用户无权调用 API |
| Kibana feature privilege | Discover、Dashboard、Dev Tools 等功能的 read 或 all | 给了 Discover read 就自动能读业务索引 |
| Elasticsearch index privilege | 哪些索引可 read、是否可查看元数据 | 有索引 read 就能进入任何 Kibana Space |
| 文档与字段级安全 | 返回哪些文档和字段 | 受限聚合可以代表全局计数 |
生产排障角色通常只需要业务索引别名上的 read 与 view_index_metadata,再在指定 Space 授予 Discover、Dashboard 的读取能力。角色权限是累加的;另一个角色若对 All Spaces 授予了更高权限,不能靠当前 Space 的较低配置把它减回去。
权限验收要包含反例。用只读角色在 Dev Tools 对一个明确不存在的探针索引发删除请求:
DELETE kibana-permission-probe-do-not-create期望响应是 403,并指出缺少的 Elasticsearch action。若返回 404,说明请求已经通过授权,只是对象不存在;该账号仍拥有删除能力。隐藏 Dev Tools、弹二次确认或把用户教育为“不要点”都不能替代服务端拒绝。
401、403 和空页面不是同一种故障
401 更像会话或身份链失败:Cookie 过期、反向代理未传递认证信息、OIDC/SAML 回调地址错误,或者 Kibana 服务身份无法访问内部索引。浏览器 Network 与 Kibana 服务日志应放在一起看。
403 表示身份通常已被识别,但当前动作未获授权。此时错误里的 action 名比“页面打不开”更有用:读取业务索引、查询字段能力、保存对象、执行集群管理分别对应不同 privilege。直接换管理员账号虽然能让页面恢复,也会抹掉最关键的拒绝证据。
菜单消失则优先检查 Space feature visibility 和 Kibana feature privilege。页面能打开但只显示部分数据,应进一步核对文档级、字段级安全与 Data View 通配符。受限角色看到的聚合值只是它有权看到的集合,不应被当作全局业务指标。
Saved Objects 是可迁移资产,也是敏感数据
Kibana 可以在 Space 之间复制 Saved Objects,也可以导出为 NDJSON。Dashboard 的引用对象通常会被一起处理,这让迁移方便,也让文件包含更多内部信息:索引名、查询、字段、内部 URL 和对象关系都可能出现在其中。
导入兼容方向是同版本、同一 major 的更新 minor,或下一 major;不能把新版导出的对象反向灌回旧版。升级前应同时保留 NDJSON 级资产备份与包含 Kibana feature state 的 Elasticsearch snapshot。前者适合审查和选择性迁移,后者才承担系统状态恢复。
导入大小受 savedObjects.maxImportExportSize 和 savedObjects.maxImportPayloadBytes 约束。遇到超限时,先拆分对象归属和引用,不要把阈值无限抬高。跨 Space 复制还可能处理 ID 冲突与引用重写,导入后要逐个打开关键 Dashboard,确认 Data View、链接与权限仍指向预期对象。
截图、CSV、Console 历史都已经离开集群权限系统
Discover 隐藏一列,只改变当前表格。展开文档、导出 CSV 或再次调用 API,原字段仍可能出现。生产 Data View 应默认选择必要字段,高敏字段优先在采集或索引侧脱敏,再用字段级权限限制读取。
截图要检查页面顶栏、域名、用户名、过滤器、Trace ID 和展开行;CSV 要有字段白名单、最大时间窗、行数上限、受控落点和销毁责任。Console 导出的请求文本同样敏感,因为它可能保存真实索引名、个人数据和能够再次执行的破坏性方法。
为了把一次人工查询串回代理和 Elasticsearch 日志,可以为请求附加不含个人信息的 X-Opaque-Id。审计记录用户、动作、目标索引、响应状态和 opaque ID 即可,不应把完整结果集复制进 Kibana 或代理日志。
容量问题通常在 5601 后面
浏览器的一次搜索会伴随 Data View、字段能力、Saved Objects、自动补全和主查询。把时间窗从小时级拉到长周期,代价会落到 Elasticsearch 分片 fan-out、搜索线程池、聚合内存、网络传输和浏览器渲染。size: 0 只是不返回文档,并不等于没有扫描。
Kibana 侧观察 Node.js 堆、事件循环利用率、请求延迟和错误率;Elasticsearch 侧观察 search thread pool、rejected、task、慢日志、断路器、分片数和磁盘水位。增加 Kibana 副本只能缓解 Web 层压力,不能降低后端搜索成本。默认时间窗、索引别名、并发限制、异步搜索和服务端资源保护才是查询风暴的控制点。
多实例还要求相同版本、插件、配置与 encryption key。反向代理需要处理上传大小、空闲超时和长连接。浏览器超时后,后端 task 可能仍在运行,因此取消与资源释放也要通过 Elasticsearch task 和容量指标复核。
退出实验和升级失败都要有可验证终点
本地实验先删除明确命名的索引,再停止两个容器,最后删除专用网络:
curl -fsS -X DELETE 'http://127.0.0.1:19200/kibana-lab-logs'
docker rm -f kibana-lab elasticsearch-kibana-lab
docker network rm kibana-lab
unset STACK_VERSION NOW这些命令只针对本页创建的资源。若额外挂载了 volume,先核对 volume 名称和标签,再决定是否删除;不要在共享机器上执行全局 prune。
生产回滚不同。应恢复与 Elasticsearch 配套的 Kibana 版本、同版插件、上一版配置和原 encryption key 引用,并使用升级前 snapshot 恢复 Kibana feature state。已经泄露到 shell 历史、容器 inspect、日志或工单的 service token 与 OIDC secret 必须轮换,删除容器并不能撤回秘密。
Kibana 的验收终点也不该是“大家能打开页面”。团队应能从一个 Discover 结果反查当前 Space、Data View、时间字段、用户角色、索引权限和查询成本;危险 API 对只读身份稳定返回 403;Saved Objects、截图与 CSV 有明确去向;升级失败能够恢复对象和密钥。做到这些,Kibana 才是一套受控的搜索工作台,而不是暴露在 5601 上的管理员入口。
