ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Metabase 多租户(Tenants)架构实战:一套共享仪表盘服务多个客户,并实现数据隔离

Metabase 多租户(Tenants)架构实战:一套共享仪表盘服务多个客户,并实现数据隔离 Metabase 多租户Tenants架构实战一套共享仪表盘服务多个客户并实现数据隔离【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase本指南基于 Metabase 开源仓库中的docs/embedding/tenants.md展开结合enterprise/backend/src/metabase_enterprise/tenants/下的源码实现系统讲解 Metabase 多租户Tenants功能的完整概念、启用方式、租户/租户组/租户用户的创建流程、共享集合与租户集合的权限模型、租户属性驱动的数据隔离以及基于 JWT 的租户自动预置方案。读完本文你将能够在自建 SaaS 应用中用一套 Metabase 仪表盘内容服务所有客户同时保证每个客户只能看到属于自己的数据。多租户的核心价值Tenants租户是 Metabase 中一个抽象概念代表共享某些属性、但彼此数据必须隔离的用户群体。典型场景是你在构建一个带嵌入式 Metabase 仪表盘的 SaaS 应用你的每个 SaaS 客户就是一个租户。把用户分组到租户中的最大好处是所有租户可以复用同一套内容与同一套权限模板你不需要为每个客户单独创建仪表盘、单独配置权限——每个租户始终只能看到自己的数据。使用租户可以做到通过**租户组Tenant groups和租户属性Tenant attributes**简化批量权限配置通过**共享集合Shared collections**复用仪表盘资产避免为每个客户重复创建通过SSOJWT自动预置租户与租户用户。从源码角度看多租户能力是 Metabase 的企业版EE特性。开源版OSS在 src/metabase/tenants/core.clj 中提供了一个 shim 命名空间其中create-tenant!直接抛出 Cannot create tenant in OSS. 异常而真正的实现位于 enterprise/backend/src/metabase_enterprise/tenants/core.clj由:feature :tenants门控。因此要使用本文介绍的全部功能需要运行企业版并具备相应许可。启用租户的总体步骤熟悉租户核心概念。启用多租户策略。在 Metabase 中创建租户与租户用户或通过 JWT 自动预置。创建共享集合。配置数据权限与集合权限。核心概念用户、组、集合的类型在 Metabase 中操作租户时会接触到多种用户类型、组类型和特殊的集合类型。先建立统一的术语体系。用户类型租户用户Tenant users最终用户。在 B2B SaaS 场景中就是你的客户的用户。租户用户通常通过中间应用与 Metabase 交互例如查看嵌入的仪表盘。内部用户Internal users不属于任何租户的普通 Metabase 用户例如 Metabase 管理员或负责开发将共享给租户的仪表盘的开发者。组类型租户用户与内部用户都可以被组织进 Metabase 用户组。All tenant users所有租户用户一个特殊组代表所有租户下的全部终端用户。这个组可用来为所有租户用户统一配置数据权限。如果需要在每个租户内部做更细粒度的权限控制还可以创建租户组。Tenant groups租户组为租户用户提供额外的权限层级。举例如果你在做招聘软件每个终端用户要么是招聘专员recruiter可访问所有开放职位的数据要么是招聘经理hiring manager只能访问特定职位的分析数据。那么可以在 Metabase 中创建 Recruiters 和 Hiring managers 两个租户组并分别配置权限。每个租户都能使用这些租户组——即每个客户都可以有各自的 Recruiters 和 Hiring managers 成员。All internal users所有内部用户代表所有不属于任何租户的人员的特殊组。这些人是直接在 Metabase 内工作的人。未启用多租户时这个组等价于 All users 组。Internal groups内部组供内部用户使用的额外组。例如你可以创建一个内部组 Analytics developers只允许他们创建日后要共享给租户的仪表盘而不授予对整个 Metabase 的管理员权限。租户用户与内部用户完全隔离租户用户不能被加入内部用户组内部用户也不能被加入租户组。集合类型集合Collections类似于文件夹可以容纳图表、仪表盘和模型同时是权限管理的组织单元如果某组人应该能访问某一组资产就应该把这些资产放进一个集合。共享集合Shared collections包含所有租户之间共享的仪表盘和图表。例如招聘应用的每个租户都应该能看到按日期统计的职位申请数。你可以创建一个 Metabase 问题 Count of applications by date 并保存到共享集合中。同时必须配置数据权限让每个租户只能看到自己的职位申请而不是所有人的申请。共享集合可以有多个也可以完全没有。例如可以有一个围绕招聘分析recruitment的共享集合再加一个围绕面试分析interviews的共享集合。共享集合还支持可选地同步到 GitHub。租户集合Tenant collections每个租户专属的集合创建租户时会自动创建。如果有特殊客户需要定制化分析可以把只给该客户的仪表盘和图表放进它的租户集合。租户集合也可以作为租户用户创建和保存新问题的空间——这些新问题可以在本租户用户之间共享但不能与其他租户的用户共享。源码层面这一自动创建逻辑在 enterprise/backend/src/metabase_enterprise/tenants/models.clj 的define-before-insert :model/Tenant钩子中实现插入租户记录前会先创建一个:type为tenant-specific-root-collection-type、namespace为tenant-specific的集合名称形如Tenant Collection: 租户名并将其 ID 写入租户的tenant_collection_id字段。内部集合Internal collections纯粹属于内部用户。这些是普通集合你和 Metabase 内部的其他用户可以把不希望终端用户看到的东西放进去开发中的仪表盘、内部指标甚至关于你的租户的分析数据。租户用户无法访问任何内部集合。个人集合Personal collection每个 Metabase 用户包括租户用户都有自己的个人集合——用来保存新问题和仪表盘的私有空间前提是用户拥有构建和保存新问题的权限。终端用户体验作为租户成员的终端用户不会知道自己是租户成员。在会向用户暴露 Metabase 集合的交互中例如全应用嵌入 full-app embedding、启用了保存功能的模块化嵌入 modular embedding或租户用户直接登录 Metabase租户用户看到的只是自己有权访问的租户集合与共享集合表现为普通集合没有任何多租户痕迹。启用多租户策略入口Admin settings People管理设置 人员无论你打算用 Metabase UI 手动管理租户还是通过 SSO 自动预置都需要先在 Metabase 中启用多租户策略。启用步骤进入Admin settings管理设置 People人员。点击人员列表上方的齿轮图标。选择Multi-tenant strategy多租户策略。切换为多租户策略后Metabase 会启用特殊的用户类型和集合类型你可以创建租户、租户组和集合并在 People 与 Permissions 标签页获得额外的管理设置。如果你已有既定的权限与集合配置想迁移到租户体系参见切换租户策略。启用后需要注意从多租户切回单租户是破坏性操作——所有租户用户和租户/共享集合都会被停用/删除。详见切换租户策略。在 Metabase 中创建租户入口Admin settings People Tenant创建租户步骤先启用多租户策略如果还没启用。进入Admin settings People。在左侧边栏选择Tenants租户点击New tenant新建租户。填写租户信息Tenant name租户名称显示给内部用户的租户显示名不会暴露给外部用户之后可以修改。Tenant slug租户标识租户的唯一标识符。可用于匹配 JWT 声明以及配置数据权限。详见特殊租户 slug 属性。Tenant attributes租户属性可定义会被每个租户用户继承的属性详见租户属性。关于 slug 的格式约束源码在 enterprise/backend/src/metabase_enterprise/tenants/models.clj 中定义为正则^[-_a-z0-9]{1,255}$即只能包含小写字母、数字、连字符和下划线最长 255 个字符。在 enterprise/backend/src/metabase_enterprise/tenants/api.clj 的create-tenant!中创建时还会校验租户名或 slug 是否已被占用返回 400 This tenant name or slug is already taken.并要求操作者为超级用户403 校验。同样在 enterprise/backend/src/metabase_enterprise/tenants/api.clj 中租户的 REST 路由挂载于/api/ee/tenant支持POST /api/ee/tenant创建租户参数为name、slug、可选attributesGET /api/ee/tenant列出租户可按statusall/active/deactivated过滤并分页GET /api/ee/tenant/:id查看单个租户PUT /api/ee/tenant/:id更新租户的名称、属性或is_active状态。其中更新接口内部使用事务t2/with-transaction当把is_active置为false时会调用deactivate-tenant-users!停用该租户全部用户并归档租户集合置回true时则重新激活用户并取消归档集合见 enterprise/backend/src/metabase_enterprise/tenants/db.clj。这与下文停用租户一节的行为完全一致。你也可以通过 JWT 预置租户 来避免在 Metabase 中手动创建租户。创建租户组入口Admin settings People Tenant groups租户组在所有租户间通用。例如你可以创建 Basic users 和 Premium users 两个租户组每个租户都能使用这两个组从而在租户内部区分基础权限与高级权限的用户。创建租户组步骤先启用多租户策略如果还没启用。进入Admin settings People。在左侧边栏选择Tenant groups租户组点击Create a group创建组。为组命名。向租户组添加成员的方法参见向组添加人员。创建租户用户入口Admin settings People Tenant users租户用户是租户内的终端用户。在 B2B SaaS 场景中就是你的客户的用户通常通过中间应用如嵌入式仪表盘与 Metabase 交互。添加租户用户步骤先创建租户。进入Admin settings People。在左侧边栏选择Tenant users租户用户点击New tenant user新建租户用户。填写用户信息包括所属租户Tenant和租户组Tenant groups。如果租户配置了租户属性这些属性会被该用户继承但你可以在 Attributes 中覆盖其值。你也可以通过 JWT 预置租户用户。从数据模型看租户与用户之间通过tenant_id关联租户用户记录上带有tenant_id字段enterprise/backend/src/metabase_enterprise/tenants/core.clj 中的user-tenant就是根据用户的tenant_id查询对应租户。用户还可以带有deactivated_with_tenant标记用于记录随租户停用而停用的状态见 enterprise/backend/src/metabase_enterprise/tenants/db.clj 的deactivate-tenant-users!/reactivate-tenant-users!。创建共享集合共享集合存放所有租户共享的仪表盘和图表。如果使用共享集合请务必配置好数据权限确保租户在共享集合中只能看到自己的数据。创建共享集合步骤先启用多租户策略如果还没启用。打开 Metabase 导航侧边栏点击左上角的三条横线注意这是普通 Metabase 界面不是 Admin settings。在侧边栏中应该能看到 External collections外部集合。如果看不到请确认已启用多租户策略。点击 External collections 旁边的创建共享集合。可以创建多个共享集合也支持嵌套的共享集合。共享集合还支持同步到 GitHub。将共享集合同步到 GitHub入口Admin settings Remote Sync可以为共享集合配置 Remote sync远程同步。这意味着你可以在一个 Metabase 中开发共享内容推送到 GitHub 仓库然后让生产环境的 Metabase 共享内容始终与该仓库保持同步。详细配置方式参见 Remote sync 文档。租户属性入口Admin settings People Tenants你可以创建租户级别的用户属性该租户的所有用户都会继承这些属性。这在配置基于属性的数据权限时非常有用例如行级安全 row-level security、用户模拟 impersonation 或数据库路由 database routing。在 Metabase UI 中手动创建租户属性进入Admin settings People。在左侧边栏选择Tenants租户。点击租户旁边的三个点。输入属性键key和值value。也可以通过 JWT 声明自动设置租户属性参见下文使用租户声明设置租户属性。添加租户属性后该租户的所有用户都会继承该属性但任何特定用户的值都可以被覆盖参见编辑用户属性。从实现上看租户属性保存在 Tenant 记录的attributes列JSON其 schema 见 enterprise/backend/src/metabase_enterprise/tenants/schema.clj属性值为字符串、数字或布尔值。同时 enterprise/backend/src/metabase_enterprise/tenants/models.clj 中的StrictAttributes校验规定属性键不能以开头前缀保留给系统属性否则 API 会返回 400。在登录环节enterprise/backend/src/metabase_enterprise/tenants/core.clj 的login-attributes会把租户的attributes与系统属性tenant.slug合并进租户用户的登录属性。而属性合并的优先级逻辑位于开源侧 src/metabase/tenants/core.clj 的combine函数租户属性:tenant与用户属性:user发生冲突时用户属性覆盖租户属性并保留原值记录shadow而系统属性:system优先级最高且不可被覆盖frozen。特殊租户 slug 属性每个租户用户都会获得一个系统定义的属性tenant.slug其值对应该租户的 slug。例如如果你创建了一个名为 Meowdern Solutions、slug 为meowdern_solutions的租户那么该租户的每个用户都会获得特殊属性tenant.slug : meowdern_solutions。如果通过 Metabase UI 创建租户可以在创建时选择 slug如果通过 JWT 预置租户租户 slug 就是 JWT 中tenant声明的值或你选择的其它租户分配属性。slug 之后不能修改。特殊属性tenant.slug可以像普通属性一样用于配置基于属性的权限例如行级安全、用户模拟或数据库路由。你的租户 slug 应当与租户在你的系统中实际的标识方式保持一致。例如如果你要用行级安全而你的表中租户是用 ID而非名称标识的那么租户 slug 也应该是 ID。假设数据长这样| Customer ID | Order number | Order date | Order total | | ----------- | ------------ | ---------- | ----------- | | 175924 | 3 | 2025-10-13 | 175.34 | | 680452 | 7 | 2025-10-13 | 34.56 |并且你想按Customer ID实施行级安全那么租户 slug 就应当形如175924以便与表中的 Customer ID 匹配。同理如果要用租户 slug 做用户模拟impersonation需要把租户 slug 映射到数据库角色如果要用做数据库路由database routing则需要把租户 slug 映射到数据库。使用 JWT 预置与分配租户使用租户声明登录用户你可以配置 JWT SSO并使用 JWT 登录租户用户。一旦启用多租户策略Metabase 会在 JWT 中查找tenant声明来判断用户是否是租户用户、属于哪个租户。tenant键的值应为租户的 slug。下面是一个用于登录租户用户的 JWT 声明示例{ email: mittensexample.com, first_name: Mister, last_name: Mittens, tenant: meowdern_solutions }如果用户已经被分配了租户例如通过 Metabase UI 分配那么 JWT 中必须包含租户声明才能登录该用户。自定义租户声明默认情况下Metabase 查找 JWT 中的tenant键。要改为其它键进入Admin Settings Authentication身份验证 JWT User attribute configuration用户属性配置。将Tenant assignment attribute租户分配属性键改为你偏好的标识符。源码层面该可配置项对应 enterprise/backend/src/metabase_enterprise/sso/providers/jwt.clj 中读取的jwt-attribute-tenant设置启用多租户后JWT 解析会额外提取租户分配属性与租户属性声明tenant.attributes。若tenant值不是字符串会返回 400 错误invalid-tenant。预置租户与用户你可以开启 JWT 用户预置让 Metabase 自动创建 JWT 中提到的用户与租户。开启 JWT 用户预置后Metabase 从 JWT 声明中读取租户标识符默认是tenant键可配置。如果租户不存在Metabase 自动创建它并将tenant键或你选择的分配属性的值作为租户 slug。新用户自动从 JWT 分配到对应租户。使用租户声明设置租户属性要从 JWT SSO 创建租户属性在 JWT 中包含tenant.attributes声明{ tenant: meowdern_solutions, tenant.attributes: { id: 13371337, name: Mammoth Solutions }, email: mittensexample.com, first_name: Mister, last_name: Mittens }如果该名称的租户属性不存在Metabase 会创建该属性并赋上 JWT 声明中的值但如果租户属性已存在Metabase不会更新其值。JWT 身份验证常见报错排查一些常见的认证错误消息及其含义Cannot add tenant claim to internal user不能向内部用户添加租户声明JWT 包含租户声明但该用户是内部用户。只有租户用户才能有租户。Tenant claim required for external user外部用户必须提供租户声明JWT 缺少租户声明但该用户是外部租户用户。Tenant ID mismatch with existing user租户 ID 与既有用户不匹配JWT 中的租户与用户已分配的租户不同。Tenant is not active租户未激活租户存在但已被停用。这些校验逻辑在 enterprise/backend/src/metabase_enterprise/tenants/core.clj 的tenant-is-active?中有对应实现仅当租户 ID 为空或该 ID 的租户存在且is_active为true时才返回真。相关集成测试可参见 enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj。租户的数据权限入口Admin settings Permissions数据权限控制人们能在图表和仪表盘上看到哪些数据以及能用这些数据做什么。要控制人们能看到哪些图表应改用集合权限。数据权限概览关于 Metabase 数据权限如何工作的完整说明参见数据权限。以下是关键要点但仍建议阅读完整文档View data查看数据控制每个用户组能在仪表盘和图表上看到哪些数据。例如如果所有租户的数据混在同一个数据库中可以结合Row and column security行与列安全或Impersonation用户模拟的 View data 权限让租户用户只能访问特定的行和列。如果每个租户的数据在各自独立的数据库中那么可以不依赖数据访问控制权限改用database routing数据库路由直接把查询路由到对应数据库。关于数据隔离方式的对比参见选择数据隔离方式。Create queries创建查询控制租户用户是否能在其可见数据上创建查询。如果希望租户用户能够进行下钻drill-through例如通过模块化嵌入 modular embedding 中的drills参数需要授予 Create queries 权限因为下钻本质上是发起一条新查询。Download results下载结果控制人们是否能下载查询结果。如果希望用户可以把数据下载为电子表格例如通过模块化嵌入中的with-downloads参数需要设置下载权限。Metabase 的数据权限可以在数据库或表级别指定并授予用户组。你需要使用特殊的All tenant users组以及租户组如有来分配数据权限。请记住Metabase 权限是叠加additive的如果一个人同时属于两个组他将获得更宽松的访问权限。特别地如果 All tenant users 对整个表拥有 Can view 权限而另一个租户组只有受限访问权限如行级安全那么该租户组的用户仍能看到表中的全部数据因为通过 All tenant users 组获得了权限。如果使用租户组建议撤销 All tenant users 的访问权限按组逐一配置访问。使用租户属性做数据权限行与列安全、用户模拟和数据库路由都需要用户属性。你可以自定义租户属性基于属性值配置数据权限。租户的集合权限集合权限控制人们能看到哪些实体仪表盘、问题、模型等。要配置这些实体中能看到哪些数据以及能用数据做什么参见数据权限。Metabase 的集合权限分多个级别**No无**访问、**View仅查看**访问和 **Curate策展**访问允许创建和保存新实体如仪表盘。更通用的集合权限说明参见集合权限。权限授予用户组。每个组可获得的权限取决于组的类型外部/租户或内部和集合的类型。租户用户的集合权限对内部集合租户用户**始终为 No无**访问。对共享集合租户用户**只能有 View仅查看或 No无**访问。这意味着租户用户最多只能查看既有实体不能创建新实体。不同的租户组可以对不同的共享集合拥有不同级别的访问。例如可以有一个所有用户都能查看的 Basic analytics 共享集合以及只有 Premium users 租户组能查看的 Advanced analytics 集合。参见配置共享集合权限。对租户集合租户用户**始终拥有 Curate策展**权限即租户用户总是可以在自己的租户集合中保存新问题。如果不希望租户用户创建和保存自己的图表需要禁用租户用户的 Create queries数据权限如果是嵌入场景还要配置嵌入式 UI 组件禁用保存功能。对个人集合租户用户**始终拥有 Curate策展**权限。内部用户的集合权限Metabase 管理员对所有共享集合和所有租户集合拥有Curate访问权限。其他内部用户和非管理员组默认无访问权限但可以被授予对共享或租户集合的View或Curate访问权限详见配置共享集合权限。内部用户对内部集合的权限配置参见集合权限通用文档。配置共享集合权限入口Admin settings Permissions要配置租户组和内部组对共享集合的访问进入Admin settings Permissions Shared collections共享集合。你可以为每个共享集合及其子集合配置内部用户和外部租户用户的访问权限。通用说明参见集合权限文档。特殊的Root shared collection根共享集合控制谁有权访问所有共享集合。例如如果你希望确保内部用户无权访问任何租户共享集合可以撤销 Root shared collection 的权限。配置权限时请记住Metabase 中所有权限都是叠加的如果某人属于两个组他将获得最宽松的访问权限。特别是如果 All tenant users 对某个共享集合有 View 权限而另一个租户组在权限设置中被明确设为 No该租户组的用户仍会获得 View 权限因为他们通过 All tenant users 组获得。如果使用租户组建议撤销 All tenant users 的权限按组逐一配置。租户的订阅权限入口Admin settings Permissions默认情况下所有租户用户创建时没有订阅权限订阅与提醒。如果你希望用户能创建订阅无论是在全应用嵌入、模块化嵌入中还是直接登录 Metabase需要将Subscription and alerts订阅与提醒权限改为 Yes。停用租户入口Admin settings People Tenants停用一个租户会同时停用该租户的所有用户。停用租户步骤进入Admin settings People。在左侧边栏选择Tenants租户。点击租户旁边的三个点。选择Deactivate tenant停用租户。所有租户用户都会被停用无法再登录。租户用户不会被永久删除Metabase 不会删除用户只会停用因此即使租户用户已被停用你也无法用相同的邮箱创建新用户。这与前面提到的源码行为完全一致停用操作会调用deactivate-tenant-users!把is_active置为false并打上deactivated_with_tenant标记同时归档租户根集合重新激活时则反向恢复见 enterprise/backend/src/metabase_enterprise/tenants/db.clj 与 enterprise/backend/src/metabase_enterprise/tenants/api.clj 的update-tenant!。切换租户策略从单租户切换到多租户启用多租户策略时Metabase 中现有的所有用户都会被当作内部用户。如果你不希望其中任何用户变成租户用户可以直接按全新实例的方式继续租户搭建创建租户、创建集合、配置权限等。但如果想把某些既有用户分配为租户用户需要用 API 调用将它们标记为租户用户PUT /api/user/:id {tenant_id: 1}如果使用 JWT 做 SSO在 JWT 中加入tenant声明。配置租户组、数据权限和集合权限因为你无法再用既有的内部组来配置租户权限。从多租户切换到单租户如果禁用多租户策略所有租户用户都会被停用所有租户与共享集合都会被删除但如果你之后重新启用多租户策略用户和集合都会恢复。所以如果你只想停用租户功能而保留活跃用户需要额外准备用普通用户组和普通集合复刻现有的租户配置替代租户组和共享集合。复习数据权限、集合权限和用户组的文档。务必用测试用户彻底验证你的配置——开发实例可能会派上用场。如果使用了租户组移除所有租户用户的租户组成员身份。用 API 把租户用户改为内部用户PUT /api/user/:id {tenant_id: null}如果不做这一步你的所有用户都会被停用。最后在所有内容验证无误后再禁用该功能。限制与注意事项租户集合和个人集合无法禁用。如果不希望租户用户创建和保存自己的图表可以禁用 Create queries数据权限嵌入场景下再配置嵌入式 UI 组件禁用保存功能。租户用户不能更换租户。一旦外部用户被分配到某个租户就不能切换到另一个租户。如果禁用多租户策略被停用的租户用户不会出现在 Deactivated users已停用用户 列表中但 Metabase 仍会跟踪他们并且不允许用相同邮箱创建新用户。没有租户专属的用户组。租户组在所有租户之间共享。如果某个组只想适用于部分租户可以创建该租户组但不要加入不适用租户的任何成员。延伸阅读嵌入总览JWT 身份验证权限总览数据隔离方式【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进