我们平时办公,遇到一个项目要分几个小组,最简单的办法是每人建一个文件夹,各放各的。但在 Kibana 里,空间隔离如果也这么想,那就踩坑了。很多团队一开始用 Kibana,喜欢给每个组建一个空间,觉得空间就是文件夹。可真正多人协作时,发现数据权限乱七八糟,有的组能看到别人的索引,有的组不知道去哪找自己的面板。问题出在哪?因为空间隔离只是“应用门牌”,数据权限靠的是后端的索引权限控制。两者不搭好桥梁,光分文件夹根本不管用。
这篇文章就用大白话,带你看清楚 Kibana 空间和数据权限的真实关系,再从架构层面设计一套适合多团队协作的方案。我会全程用 Python 写示例代码,让你能直接跟着动手试。
一、问题来了:空间隔离为什么不是分文件夹
先打个比方。Kibana 的空间就像一个办公室里的隔间。每个隔间有自己的桌椅(仪表板、可视化、索引模式),你能看到隔间的门牌(空间名称)。但隔间里放的“文件”(实际数据)都存在一个大仓库里,仓库门锁(索引权限)才是真正决定你能拿什么文件的关键。如果你只给每个团队分一个隔间,却忘了配仓库门锁,他们依然能跑到别的隔间去翻别人桌上的文件。
在 Kibana 里,空间(Space)管理的是一堆“应用对象”,比如仪表板、图表、搜索、索引模式。它不管理数据。数据在哪?数据在 Elasticsearch 的索引里。索引权限由角色(Role)控制,角色又分配到用户(User)头上。所以你设想的多团队协作,其实要解决两件事:
- 每个团队在 Kibana 里只能看到自己的“隔间”(空间)。
- 每个团队只能访问自己该看的“仓库文件”(索引或数据视图)。
如果只做第一点,不做第二点,那就等于把隔间分好了,但仓库钥匙人人通用,数据直接裸奔。如果只做第二点,不做第一点,所有人都挤在一个共享空间里,界面乱七八糟,面板互相覆盖,协作依旧困难。
所以,正确的姿势是:空间负责“界面隔离”,角色负责“数据权限”,用户负责“身份绑定”,三者各司其职,才能平衡租户共享资源和数据安全之间的矛盾。
二、先搞懂Kibana空间和数据权限
2.1 空间到底管了什么
Kibana 空间(Space)是 Kibana 应用层的逻辑分区。它把仪表板、可视化、Canvas、索引模式等对象分隔开。比如你创建了“电商团队空间”“风控团队空间”,那么电商团队在电商空间里建的仪表板,风控团队在风控空间里看不到,反之亦然。
但注意,空间本身不包含数据。你在空间里创建索引模式时,索引模式只是一个“数据视图”,它指向 Elasticsearch 中某个索引或别名。如果某团队的空间里配置了指向所有索引的索引模式,那他们就能看到所有数据。所以,空间必须与数据权限配合,否则就是空壳。
2.2 数据权限的源头:索引权限
Elasticsearch 的权限体系基于角色。一个角色可以指向一组索引,并定义对这些索引的读、写、管理操作。没人会直接把用户绑定到索引,而是把角色分配给用户。比如:
- 角色
tenant_a_readonly只对app-a-logs-*有读权限。 - 角色
tenant_b_readonly只对app-b-logs-*有读权限。
然后在 Kibana 里,每个空间可以关联一组角色。用户登录后,Kibana 会判断他在当前空间有哪些权限。如果用户不具备该空间所需的最低权限,他甚至无法进入这个空间。所以,空间与角色的组合,才是完整的多租户隔离方案。
2.3 平衡点在哪
租户共享资源,指的是多个团队复用同一套 Elasticsearch 集群和 Kibana 实例。平衡点就是:共享底层设施,但不共享数据视图;共享应用入口,但不共享仪表板。换句话说,硬件和中间件是共享的,但逻辑资源和数据访问完全隔离。
你需要按“最小权限”原则去分配角色:团队成员只能读他们业务对应的索引,只能进入自己团队的空间,只能操作自己空间里的对象。管理员可以从架构层面,把“空间-角色-用户”映射关系做成一张配置表,用脚本自动同步,避免手工点错。
三、架构设计:让三个团队在一套Kibana里安全协作
3.1 整体原则
我们要设计一个通用方案,假设公司有 A、B、C 三个团队,各自管理不同的业务数据。具体需求:
- A 团队只能看
logs-a-*索引。 - B 团队只能看
logs-b-*索引。 - C 团队只能看
logs-c-*索引。 - 每个团队在 Kibana 里有独立空间,互不可见。
- 各团队可以自己保存仪表板,但不能看别人空间里的东西。
架构如下:
- Elasticsearch 中索引命名规范带团队标识,比如
logs-a-*。 - 每个团队定义一个角色,角色里限制索引范围,还定义了 Kibana 空间权限。
- 创建用户,绑定对应角色。
- 创建 Kibana 空间(可用 API 或 UI)。
- 配置空间与角色绑定,让用户只能进自己的空间。
这里用到一个核心概念:Kibana 功能权限。角色里可以声明 kibana_space 和 kibana_privileges。比如给 A 团队的角色设置 space_privileges 为 ["all"],表示在指定空间里拥有全部管理权。同时设置 base_privileges 为空,防止他们访问其他空间。
3.2 租户与角色的映射关系
我们可以在代码里用一个字典来映射团队、角色、索引、空间之间的关系,这样后续新增团队非常方便。比如:
# 定义三个团队的映射关系
teams = {
"team_a": {
"space_name": "space-a",
"index_patterns": ["logs-a-*"],
"role_name": "role-log-a",
"user_name": "user-a",
"password": "pass-a"
},
"team_b": {
"space_name": "space-b",
"index_patterns": ["logs-b-*"],
"role_name": "role-log-b",
"user_name": "user-b",
"password": "pass-b"
},
"team_c": {
"space_name": "space-c",
"index_patterns": ["logs-c-*"],
"role_name": "role-log-c",
"user_name": "user-c",
"password": "pass-c"
}
}
3.3 数据流:从登录到看数据
当用户 user-a 登录 Kibana 时,Elasticsearch 会验证他的身份。然后 Kibana 获取用户拥有的角色 role-log-a,这个角色指明了两件事:
- 能访问哪些索引(
logs-a-*)。 - 能进入哪些 Kibana 空间(
space-a)。
Kibana 会根据角色里的空间权限,把入口列表发给用户。此时 user-a 只能看到 space-a,点击进入后,空间里只有自己团队保存的仪表板和索引模式。他尝试搜索非授权索引时,后端会拒绝请求,即使他猜测索引名也没用。
这就是架构的关键:空间隔离只影响“看得到什么界面”,索引权限影响“拿得到什么数据”,两者是叠加校验的关系。
四、动手实操:用Python脚本实现租户隔离配置
本方案使用 Python 技术栈,调用 Elasticsearch 官方客户端库 elasticsearch 和 Kibana 相关 API。所有操作都以管理员身份执行。请确保已安装 elasticsearch 库(pip install elasticsearch)。下面提供完整脚本示例,包括创建角色、创建空间、创建用户、绑定权限四步。
4.1 准备工作
连接 Elasticsearch 和 Kibana 的 API。这里使用 HTTP 基本认证。需要知道 Elasticsearch 的地址(假设是 http://localhost:9200)和 Kibana 的地址(假设是 http://localhost:5601),管理员账号为 elastic,密码为 admin123。
4.2 创建角色并绑定索引权限
角色定义在 Elasticsearch 中,可以通过 search_security API 创建。我们需要为每个团队创建角色。角色里包含两部分:
- 集群权限(这里不需要特殊集群权限,给空数组)。
- 索引权限(指定索引模式,只给读权限)。
- Kibana 空间权限(必须使用 Elasticsearch 与 Kibana 结合的角色格式,用
applications字段声明)。
先以 team_a 为例,编写创建角色的函数。
from elasticsearch import Elasticsearch
from elasticsearch.client import SecurityClient
# 连接 ES 集群,使用管理员账号
es = Elasticsearch(
hosts=["http://localhost:9200"],
http_auth=("elastic", "admin123"),
verify_certs=False
)
# Security 客户端用于管理角色
security = SecurityClient(es)
def create_role(role_name, index_patterns, space_name):
"""
为团队创建角色。
role_name: 角色名称,比如 role-log-a
index_patterns: 该团队允许访问的索引列表,比如 ["logs-a-*"]
space_name: Kibana 空间名称,比如 space-a
"""
role_body = {
# 不需要集群权限,留空数组
"cluster": [],
# 索引权限:只读,禁止写操作
"indices": [
{
"names": index_patterns, # 允许访问的索引模式
"privileges": ["read", "view_index_metadata"], # 只读 + 查看元数据
"field_security": { # 如果有敏感字段,可以限定字段,这里全部可见
"grant": ["*"]
}
}
],
# 使用 Kibana 应用权限,指定哪些空间可用,以及对空间对象的权限级别
"applications": [
{
"application": "kibana-.kibana", # 固定的应用标识
"privileges": [
"space_all" # 拥有空间内所有对象的全部权限(包括读写)
],
"resources": [
f"space:{space_name}" # 限定这个空间
]
}
]
}
# 调用 API 创建或更新角色
response = security.put_role(name=role_name, body=role_body)
print(f"创建/更新角色 {role_name} 成功: {response}")
这里要重点解释 applications 的作用。space_all 是 Kibana 预定义权限,表示对空间内所有对象有全部访问权。space:{space_name} 是资源标识,指的是具体的空间。如果此处不写 space:space-a,那角色就没有任何空间的权限,用户也无法进入 Kibana。如果想要只读权限,可以用 space_read。
4.3 创建空间并分配用户
Kibana 空间可以通过调用 Kibana API 创建。Kibana 提供了内部接口。我们需要用 Python 的 requests 库发送 HTTP 请求。注意,必须在请求头中加上 kbn-xsrf: true,否则 Kibana 会拒绝。
import requests
import json
# Kibana 地址和认证信息
kibana_url = "http://localhost:5601"
kibana_auth = ("elastic", "admin123")
headers = {
"kbn-xsrf": "true",
"Content-Type": "application/json"
}
def create_space(space_name, display_name, description=""):
"""
创建 Kibana 空间。如果空间已存在,跳过。
space_name: 空间 ID,比如 space-a
display_name: 空间中显示的名称,比如 "A团队空间"
"""
# 构造空间信息
space_payload = {
"id": space_name,
"name": display_name,
"description": description,
"color": "#00BCB4", # 空间主题色,可随意设置
"initials": space_name[-1].upper(), # 空间缩写
"disabledFeatures": [] # 不禁用任何功能
}
# 调用 Kibana 创建空间 API
resp = requests.post(
f"{kibana_url}/api/spaces/space",
auth=kibana_auth,
headers=headers,
json=space_payload
)
if resp.status_code in (200, 201):
print(f"创建空间 {space_name} 成功")
elif resp.status_code == 409:
print(f"空间 {space_name} 已存在,跳过创建")
else:
print(f"创建空间 {space_name} 失败: {resp.text}")
接下来创建用户。用户保存在 Elasticsearch 中,可以用 SecurityClient 创建。用户需要设置密码,并关联刚才创建的角色。
def create_user(user_name, password, role_name):
"""
创建用户并关联角色。
user_name: 用户名,比如 user-a
password: 密码
role_name: 角色名,比如 role-log-a
"""
user_body = {
"password": password,
"roles": [role_name], # 将角色绑定给用户
"full_name": f"团队{user_name}用户",
"email": f"{user_name}@company.com"
}
response = security.put_user(username=user_name, body=user_body)
print(f"创建/更新用户 {user_name} 成功: {response}")
4.4 验证效果
配置完所有团队后,我们写一个主函数来执行流程,并验证用户是否能正确访问。验证时模拟普通用户登录,尝试读取自身索引和越权索引,看看是否被拒绝。
def run_setup():
"""为所有团队配置空间、角色、用户"""
for team_name, conf in teams.items():
# 先创建空间
create_space(
space_name=conf["space_name"],
display_name=f"{team_name.upper()} 空间",
description=f"{team_name} 团队专属空间"
)
# 然后创建角色
create_role(
role_name=conf["role_name"],
index_patterns=conf["index_patterns"],
space_name=conf["space_name"]
)
# 最后创建用户
create_user(
user_name=conf["user_name"],
password=conf["password"],
role_name=conf["role_name"]
)
def verify_access():
"""
用普通用户身份验证数据权限。
这里演示 team_a 的用户。
"""
# 用 user-a 连接 ES
es_user = Elasticsearch(
hosts=["http://localhost:9200"],
http_auth=("user-a", "pass-a"),
verify_certs=False
)
# 尝试读取自己的索引 logs-a-2024.01.01
try:
resp = es_user.search(index="logs-a-2024.01.01", body={"query": {"match_all": {}}})
print("用户 user-a 可以正常查询 logs-a-* 数据")
print("查询结果条数:", resp["hits"]["total"]["value"])
except Exception as e:
print("user-a 查询自己的索引被拒绝:", str(e))
# 尝试读取不应该看到的索引 logs-b-2024.01.01
try:
resp = es_user.search(index="logs-b-2024.01.01", body={"query": {"match_all": {}}})
print("越权访问成功?这不对!", resp["hits"]["total"]["value"])
except Exception as e:
# 期望得到权限错误,Elasticsearch 会抛出 AuthorizationException
if "security_exception" in str(e) or "unauthorized" in str(e).lower():
print("user-a 查询 logs-b-* 被拒绝,符合预期")
else:
print("出现其他错误:", str(e))
if __name__ == "__main__":
run_setup()
verify_access()
运行这段 Python 脚本,会依次完成空间、角色、用户的创建,最后验证 team_a 的用户能查自己的索引,但查不到别家索引。这种脚本可以放在 CI/CD 里,每次有新团队加入时,把 teams 字典多加一项,重新跑一遍即可。
五、应用场景与优缺点
5.1 适合哪些场景
这个架构非常适合以下情境:
- 多产品线共用一套 ELK 集群,但每个产品线数据独立,不希望运维每产品线搭一套 Kibana。
- 项目外包或合作伙伴需要在自己空间里查看数据,但不能看到内部其他业务的数据。
- 企业内部有多个部门(比如订单、用户、风控),它们共用日志平台但访问的索引完全不同。
在这些场景下,空间隔离加角色绑定的方案,能让不同团队像使用自己独立的 Kibana 一样,但底层资源是省钱的。
5.2 方案的优点
- 维护成本低:只需要维护几组角色和空间,不用部署多套 Kibana。
- 可扩展性强:新增团队时,照葫芦画瓢,改一下配置字典,脚本自动创建。
- 安全边界清晰:索引权限在后端强制生效,即使有人猜出其他空间 URL,也无法访问数据。
- 对象管理独立:各空间里的仪表板、图表互相隔离,团队可以随意修改自己的页面而不影响别人。
5.3 要小心的坑
- 空间不是权限边界。如果你在给角色分配空间权限时,误给了
global_all(全局权限),用户就能看到所有空间。一定要明确指定resources为特定空间。 - 索引模式(数据视图)可以跨空间。如果某个团队在空间里创建了一个指向通配符的索引模式,而角色限制了只能访问
logs-a-*,那数据查询依然会被后端拦截,但用户会看到索引模式因为无法访问而报错,影响体验。所以,最好在创建空间时,每个空间只放私有索引模式。 - 管理员账号必须谨慎。管理员角色具有超级权限,不要在团队用户上使用。
- 空间数量不要太多。Kibana 空间虽好,但创建几百个后,管理和切换会非常繁杂。如果团队太小,可以合并空间,用角色区分数据权限。
- 字段级安全容易忽略。如果某些日志包含手机号、身份证等敏感字段,你需要在角色的
field_security里做字段屏蔽。但注意,字段屏蔽并不适用于所有聚合操作,测试时要仔细验证。
六、注意事项大盘点
在实际落地时,还有几个细节需要留意。
第一,角色的索引权限要使用索引模式,而不是具体的索引名。因为日志索引通常按日期滚动,比如 logs-a-2024.01.01,如果写成固定的索引名,第二天就失效了。用通配符 logs-a-* 才能覆盖未来数据。
第二,view_index_metadata 权限尽量保留。没有这个权限,有些查询元数据的操作会失败,导致 Kibana 索引模式无法加载字段列表。
第三,Kibana 空间 API 在版本之间可能变化。不同 Kibana 版本的内部 API 可能有细微差别。建议先在你使用的版本上测试一次,然后再编写自动化脚本。我这里的示例基于 Kibana 7.x / 8.x 的通用接口。
第四,用户密码不要写在公开仓库里。建议用环境变量或密钥管理工具引入密码,避免泄漏。
第五,角色与用户绑定后,并不是立即生效的。如果用户已经在浏览器里登录了 Kibana,需要重新登录一次才能刷新权限。如果需要强制失效,可以使用 Security API 将用户的 auth 信息强制过期。
第六,如果团队需要提交滚动管理,比如某团队成员离职,应该从用户列表里禁用或删除他的登录名。这个操作同样可以通过 Security API 完成。
七、总结
说回最初的问题:Kibana 空间隔离不是简简单单分文件夹。空间管的是应用界面,数据权限管的是后端索引。真正要实现多团队共享一套 Kibana 又互不干扰,必须从架构上把“空间-角色-用户-索引”全部串联起来。通过定义统一的映射表,用脚本自动创建角色、空间、用户,既降低了运维成本,也保证了安全边界不会因为人工配置出错而破裂。
这里给出的 Python 方案,展示了从创建角色到赋权的完整流程,代码里保留了清晰注释,你完全可以照抄修改后用在生产环境。记住几个关键点:角色上的 applications 字段必须明确指向对应空间;索引权限要使用模式匹配;字段级安全要按需求配置。做到了这些,各团队就能在共享的硬件上安心协作,既不用怕数据越权,也不用怕界面混乱。
真正平衡租户共享和数据安全,本质上就是分层治理:底层的 Elasticsearch 负责锁数据,上层的 Kibana 空间负责分界面,而角色就是两者之间的钥匙。钥匙配对了,门自然就开对了。
评论
围绕“Kibana空间隔离并非简单分文件夹,租户共享资源时的数据权限与应用可访问性如何平衡?从架构层面设计多团队协作方案。”参与讨论