一、多租户场景下ThingsBoard资源隔离的常见问题
很多做物联网平台的开发者可能都遇到过这样的情况:用ThingsBoard搭了个多租户的平台,本来想着不同企业客户(也就是不同租户)的数据、设备、权限互相不干扰,结果上线没多久就出问题——比如A租户的设备突然连不上,一查发现是B租户的数据量太大,把平台的CPU、内存全占满了;还有更离谱的,A租户的管理员居然能看到B租户的设备列表,甚至能改B租户的设备配置。这些问题本质上都是ThingsBoard的资源隔离失效了。
ThingsBoard本身是支持多租户的,但它的原生隔离机制比较“基础”,默认只是在逻辑上给不同租户划了边界,没有做真正的物理资源隔离。比如所有租户的设备数据都存在同一个数据库实例里,所有租户的设备连接都共用同一个MQTT服务端进程,一旦某个租户的业务量爆发,就会抢占其他租户的资源,甚至突破逻辑边界拿到不该看的数据。
二、精细化管控的核心方案
要解决这个问题,不能只靠ThingsBoard原生的配置,得从架构层面做精细化的管控,核心思路是“逻辑隔离+物理隔离结合”,同时加上权限的严格校验。具体可以拆成四个部分来做。
2.1 网络层的隔离
首先要把不同租户的网络流量分开,避免互相干扰。最常用的方式是给每个租户分配独立的MQTT端口和HTTP端口,同时用网络策略限制不同端口之间的访问。
举个具体的例子,比如有两个租户,租户1的ID是tenant_001,租户2的ID是tenant_002,我们给租户1分配MQTT端口18831,HTTP端口8081;租户2分配MQTT端口18832,HTTP端口8082。然后配置防火墙规则,只允许租户1的设备访问18831和8081,租户2的设备访问18832和8082,同时禁止不同端口之间的跨租户访问。
这里需要用到ThingsBoard的自定义配置,修改ThingsBoard的application.yml文件,添加多端口的配置:
# 配置MQTT服务的多端口监听
server:
mqtt:
listeners:
- port: 18831
tenantId: tenant_001
- port: 18832
tenantId: tenant_002
# 配置HTTP服务的多端口监听
http:
listeners:
- port: 8081
tenantId: tenant_001
- port: 8082
tenantId: tenant_002
配置完成后,重启ThingsBoard服务,每个租户的设备就只能通过自己的端口连接平台,从网络层把流量分开了。
2.2 资源层的隔离
网络层隔离解决了流量的问题,但如果所有租户的资源还是共用同一个服务器的CPU、内存、磁盘,还是会出现资源抢占的问题。这时候就需要做物理级的资源隔离,最常用的是用Docker容器化部署,给每个租户分配独立的容器实例。
比如给租户1部署一个ThingsBoard容器,分配2核CPU、4G内存、100G磁盘;给租户2部署另一个ThingsBoard容器,同样分配2核CPU、4G内存、100G磁盘。每个容器的资源是独立的,就算租户1的业务量爆发,最多把自己容器的资源占满,不会影响租户2的容器。
这里给一个Docker Compose的示例,用来部署两个租户的ThingsBoard容器:
version: '3'
services:
# 租户1的ThingsBoard容器
thingsboard-tenant1:
image: thingsboard/tb-postgres:latest
ports:
- "18831:1883" # 把容器内部的1883端口映射到主机的18831端口
- "8081:8080" # 把容器内部的8080端口映射到主机的8081端口
deploy:
resources:
limits:
cpus: '2' # 限制CPU为2核
memory: 4G # 限制内存为4G
volumes:
- tb-tenant1-data:/data # 给租户1分配独立的磁盘存储
# 租户2的ThingsBoard容器
thingsboard-tenant2:
image: thingsboard/tb-postgres:latest
ports:
- "18832:1883" # 把容器内部的1883端口映射到主机的18832端口
- "8082:8080" # 把容器内部的8080端口映射到主机的8082端口
deploy:
resources:
limits:
cpus: '2' # 限制CPU为2核
memory: 4G # 限制内存为4G
volumes:
- tb-tenant2-data:/data # 给租户2分配独立的磁盘存储
# 定义独立的存储卷
volumes:
tb-tenant1-data:
tb-tenant2-data:
把这个文件保存为docker-compose.yml,然后执行docker-compose up -d命令,就能启动两个独立的租户容器,每个容器的资源都是独立的,不会互相抢占。
2.3 数据层的隔离
除了网络和资源,数据的隔离也很重要。ThingsBoard默认所有租户的数据都存在同一个PostgreSQL数据库里,虽然逻辑上有租户ID的区分,但如果数据库的权限配置不当,还是有可能出现数据泄露的问题。
最彻底的方式是给每个租户分配独立的数据库实例,每个租户的数据只存在自己的数据库里,就算某个数据库出问题,也不会影响其他租户的数据。
这里给一个修改ThingsBoard配置,连接独立数据库的示例:
# 租户1的ThingsBoard数据库配置
datasource:
type: postgresql
url: jdbc:postgresql://localhost:5432/tb_tenant1 # 连接租户1的独立数据库
username: tb_tenant1_user # 租户1数据库的专用用户名
password: tb_tenant1_pass # 租户1数据库的专用密码
driver-class-name: org.postgresql.Driver
# 租户2的ThingsBoard数据库配置(需要放在租户2的容器配置里)
datasource:
type: postgresql
url: jdbc:postgresql://localhost:5432/tb_tenant2 # 连接租户2的独立数据库
username: tb_tenant2_user # 租户2数据库的专用用户名
password: tb_tenant2_pass # 租户2数据库的专用密码
driver-class-name: org.postgresql.Driver
每个租户的ThingsBoard容器配置自己的数据库连接,这样所有租户的数据就完全分开了,从根源上避免了数据泄露的风险。
2.4 权限层的隔离
最后一道防线是权限的严格校验。ThingsBoard原生的权限机制可以做进一步的强化,比如限制每个租户的管理员只能访问自己租户的资源,不能跨租户操作。
具体可以通过自定义ThingsBoard的权限拦截器来实现,比如在用户访问某个资源的时候,先校验用户所属的租户ID和资源所属的租户ID是否一致,如果不一致就拒绝访问。
这里给一个简单的权限拦截器的Java代码示例(ThingsBoard是用Java开发的,所以可以自定义拦截器):
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TenantPermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 从请求中获取当前登录用户的租户ID(ThingsBoard原生会把用户信息存在请求属性里)
String userTenantId = (String) request.getAttribute("userTenantId");
// 2. 从请求路径中获取要访问的资源所属的租户ID(比如请求路径是/api/tenant/tenant_001/devices)
String resourceTenantId = request.getRequestURI().split("/")[3];
// 3. 校验两个租户ID是否一致
if (!userTenantId.equals(resourceTenantId)) {
// 不一致就返回403权限不足的错误
response.sendError(HttpServletResponse.SC_FORBIDDEN, "没有权限访问该资源");
return false;
}
// 一致就允许访问
return true;
}
}
把这个拦截器配置到ThingsBoard的Spring MVC配置里,就能实现严格的跨租户权限校验,就算某个租户的管理员拿到了其他租户的资源路径,也无法访问。
三、方案的应用场景、优缺点和注意事项
3.1 应用场景
这个精细化管控方案适合所有需要多租户的物联网平台,尤其是以下几种场景:
- 企业级的物联网SaaS平台,需要给不同的企业客户提供独立的服务,保证客户的数据和资源安全;
- 大规模的物联网项目,比如智慧城市、智慧园区,不同的子项目(比如智慧交通、智慧安防)需要独立的资源和数据;
- 对数据安全要求很高的行业,比如医疗、金融,需要严格的物理级数据隔离。
3.2 技术优缺点
优点主要有三个:
- 隔离效果好:从网络、资源、数据、权限四个层面做隔离,基本可以避免资源抢占和数据泄露的问题;
- 可扩展性强:用Docker容器化部署,每个租户的实例可以独立扩容,比如某个租户的业务量增长,只需要给它的容器增加CPU和内存就行;
- 维护方便:每个租户的实例是独立的,就算某个实例出问题,也不会影响其他实例,排查问题也更简单。
缺点也有两个:
- 成本高:每个租户都需要独立的服务器资源(CPU、内存、磁盘),如果租户数量很多,成本会比原生部署高很多;
- 部署复杂:需要配置网络、容器、数据库、权限拦截器等多个环节,对开发和运维人员的技术要求比较高。
3.3 注意事项
- 资源分配要合理:给每个租户分配的CPU、内存、磁盘要根据租户的实际业务量来定,不能给太少导致性能不够,也不能给太多浪费资源;
- 备份要到位:每个租户的数据库是独立的,所以要给每个数据库做单独的备份,避免数据丢失;
- 监控要完善:要监控每个租户的资源使用情况、网络流量、数据访问情况,一旦发现异常(比如某个租户的资源使用突然飙升),要及时处理;
- 兼容性要测试:自定义的配置和代码(比如权限拦截器)要和ThingsBoard的版本兼容,避免升级ThingsBoard后出现问题。
四、方案总结
多租户场景下ThingsBoard的资源隔离失效,本质上是原生的逻辑隔离无法满足复杂场景的需求,需要从网络、资源、数据、权限四个层面做精细化的管控。这个方案虽然成本高、部署复杂,但隔离效果好、可扩展性强,适合对安全和稳定性要求高的物联网平台。
在实际应用中,可以根据自己的业务情况灵活调整方案,比如如果租户数量很多、对成本敏感,可以只做网络隔离和权限隔离,资源和数据用共享的,同时限制每个租户的资源配额;如果对安全要求极高,就用全物理隔离的方案。
最后要注意的是,资源隔离不是一劳永逸的,需要定期监控和优化,根据业务的变化调整配置,保证平台的稳定运行。
评论
围绕“多租户场景下ThingsBoard资源隔离失效?物联网平台架构的精细化管控方案”参与讨论