多租户与隔离
EnerOS 的多租户模型对应电力运营主体边界:每个租户即一个独立的电网运营责任主体(如省调、地调、虚拟电厂运营商),其数据、Agent、设备、配额完全隔离。租户边界即电网运营责任边界。
电力系统天然是多主体协作场景:国调、分调、省调、地调、县调形成层级;发电集团、电网公司、售电公司、用户形成多边关系。每个主体的数据、控制权限、责任范围必须严格隔离。EnerOS 的多租户模型将这种现实中的边界映射到操作系统层面。
架构总览
┌─────────────────────────────────────────────────────┐
│ 租户 A(省调)│ 租户 B(地调)│ 租户 C(VPP) │
│ - 拓扑 A │ - 拓扑 B │ - 拓扑 C │
│ - Agent A │ - Agent B │ - Agent C │
│ - 时序 A │ - 时序 B │ - 时序 C │
│ - 配额 A │ - 配额 B │ - 配额 C │
└──────────────────┬──────────────────────────────────┘
│ 命名空间隔离
┌──────────────────┴──────────────────────────────────┐
│ eneros-tenant (多租户内核模块) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Namespace│ │ Quota │ │ ACL │ │
│ │ 命名空间 │ │ 配额管理 │ │ 访问控制 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ KMS │ │ Audit │ │ Cross │ │
│ │ 密钥管理 │ │ 审计 │ │ 跨租户 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────┬──────────────────────────────────┘
│ cgroup + network namespace
┌──────────────────┴──────────────────────────────────┐
│ 资源层:CPU / 内存 / 网络 / 存储 │
└─────────────────────────────────────────────────────┘
租户模型
use eneros_tenant::{Tenant, TenantId, TenantConfig, TenantTier};
let tenant = Tenant::new(TenantId::from("province_a"))
.name("A 省电力调度中心")
.tier(TenantTier::Enterprise)
.config(TenantConfig {
max_agents: 100,
max_devices: 100_000,
max_ts_throughput: 1_000_000, // samples/s
storage_quota_tb: 10,
api_rate: 1000, // req/s
realtime_cpu_cores: 2,
});
Tenant 字段说明
| 字段 | 类型 | 说明 |
|---|
| id | TenantId | 租户唯一标识(如 “province_a”) |
| name | String | 可读名称 |
| tier | TenantTier | 等级(Community / Pro / Enterprise / National) |
| config | TenantConfig | 资源配额 |
| created_at | DateTime | 创建时间 |
| status | TenantStatus | Active / Suspended / Terminated |
| kms_key_id | String | KMS 密钥 ID |
TenantTier 等级
| 等级 | 最大 Agent | 最大设备 | 适用场景 |
|---|
| Community | 10 | 1,000 | 实验室、教学 |
| Pro | 50 | 10,000 | 配电网、微电网 |
| Enterprise | 100 | 100,000 | 省调、地调 |
| National | 1000 | 1,000,000 | 国家级电网 |
命名空间隔离
所有内核资源(拓扑、设备、Agent、时序数据)都带租户命名空间前缀,确保物理隔离:
use eneros_tenant::TenantContext;
// 资源命名空间自动注入
let bus = network.add_bus(Bus::new(1, ...));
// 实际存储 key: "tenant:province_a:bus:1"
// 跨租户访问被拒绝
let other = network.get_bus(TenantId::from("province_b"), 1);
// → PermissionDenied
// 命名空间覆盖所有资源
// tenant:<id>:topology:<...>
// tenant:<id>:equipment:<...>
// tenant:<id>:timeseries:<tag>
// tenant:<id>:agent:<id>
// tenant:<id>:audit:<...>
命名空间层级
tenant:province_a:
├── topology:network:default
├── equipment:line:1
├── timeseries:bus_1.voltage
├── agent:dispatch_1
├── twin:instance:main
├── audit:2026-07
└── kms:key:primary
租户上下文
每个请求/Agent 执行都携带租户上下文,内核自动校验:
use eneros_tenant::TenantContext;
async fn handler(ctx: &TenantContext, req: Request) -> Response {
// ctx.tenant_id() 自动从 mTLS 证书提取
let tenant = ctx.tenant();
// 所有操作自动限定在租户命名空间
let network = tenant.network().await?;
let agents = tenant.agents().await?;
let timeseries = tenant.timeseries().await?;
// 拓扑操作自动隔离
let bus = network.get_bus(1).await?;
// 等价于 network.get_bus_in_namespace(ctx.tenant_id(), 1)
Response::ok()
}
// API 中间件自动注入租户上下文
async fn tenant_middleware(req: Request, next: Next) -> Response {
let cert = req.peer_certificate()?;
let tenant_id = extract_tenant_from_cert(cert)?;
let ctx = TenantContext::new(tenant_id);
next.run(ctx, req).await
}
TenantContext 方法
| 方法 | 返回 | 说明 |
|---|
| tenant_id() | &TenantId | 获取租户 ID |
| tenant() | Tenant | 获取租户对象 |
| has_permission(perm) | bool | 权限检查 |
| quota() | Quota | 获取配额 |
| usage() | Usage | 获取当前使用量 |
| kms() -> Kms | Kms | 密钥管理服务 |
配额管理
按租户维度强制配额,防止单租户耗尽集群资源:
use eneros_tenant::Quota;
let quota = Quota::for_tenant(&tenant_id)
.agents(100)
.devices(100_000)
.ts_throughput(1_000_000) // samples/s
.storage(10_tb)
.api_rate(1000) // req/s
.realtime_cpu_cores(2)
.memory_gb(16)
.network_mbps(100);
// 内核自动校验配额
tenant.spawn_agent(my_agent).await?;
// 超出配额 → QuotaExceeded
// 实时监控用量
let usage = tenant.usage().await?;
println!("Agent: {}/{}", usage.agents.used, usage.agents.quota);
println!("存储: {:.1}/{:.1} TB", usage.storage.used_tb, usage.storage.quota_tb);
println!("时序吞吐: {:.0}/{:.0} samples/s",
usage.ts_throughput.current, usage.ts_throughput.quota);
配额资源矩阵
| 资源 | 计费维度 | 默认配额 | 超出处理 |
|---|
| Agent 数量 | 实例数 | 100 | 拒绝创建 |
| 设备数 | 个 | 100k | 拒绝创建 |
| 时序写入 | samples/s | 1M | 限流 |
| 存储 | TB | 10 | 拒绝写入 |
| API 调用 | req/s | 1000 | 429 限流 |
| 实时 CPU | 核 | 2 | 拒绝创建 |
| 内存 | GB | 16 | OOM 保护 |
| 网络 | Mbps | 100 | 限速 |
| Twin forks | 个 | 8 | 拒绝 fork |
| 跨租户调用 | 次/天 | 10000 | 拒绝调用 |
配额告警
use eneros_tenant::QuotaAlert;
// 配置配额告警
tenant.set_alert(QuotaAlert {
resource: Resource::Storage,
threshold: 0.8, // 80%
action: AlertAction::Notify { emails: vec!["ops@a.com".into()] },
});
tenant.set_alert(QuotaAlert {
resource: Resource::TsThroughput,
threshold: 0.95, // 95%
action: AlertAction::AutoScale { factor: 1.5 },
});
资源共享与白名单
某些场景需要跨租户协作(如省地协调),通过显式白名单授权:
// A 省授权 B 省读取部分数据
tenant_a.grant(&tenant_b, Permission::read(&[
"bus:1:voltage",
"bus:2:voltage",
"branch:5:flow",
])).duration(Duration::from_hours(24)).await?;
// B 省在白名单内可访问
let v = tenant_b.cross_read(&tenant_a, "bus:1:voltage").await?;
// 撤销授权
tenant_a.revoke(&tenant_b, Permission::read(&["bus:1:voltage"])).await?;
// 查询授权
let grants = tenant_a.list_grants().await?;
for g in &grants {
println!("{} → {} : {:?} (剩余 {:?})",
g.grantor, g.grantee, g.permission, g.expires_in);
Permission 类型
| 类型 | 含义 | 典型场景 |
|---|
| read(tags) | 读取指定数据 | 省地数据共享 |
| write(tags) | 写入指定数据 | 协同控制 |
| control(devices) | 控制指定设备 | 委托控制 |
| query(topology) | 查询拓扑 | 协调规划 |
| invoke(tools) | 调用工具 | 共享服务 |
隔离强度
| 维度 | 隔离方式 | 强度 |
|---|
| 数据 | 命名空间前缀 + ACL | 强 |
| 计算 | cgroup v2 + CPU/内存限制 | 强 |
| 网络 | per-tenant network namespace | 强 |
| Agent | 独立进程 + IPC 通信 | 强 |
| 时序 | per-tenant LSM tree | 强 |
| 密钥 | per-tenant KMS 密钥 | 强 |
| 审计 | per-tenant WORM 日志 | 强 |
| 实时域 | per-tenant CPU 隔离 | 强 |
cgroup v2 配置示例
# /sys/fs/cgroup/eneros/tenant_province_a/
# cpu.max = 200000 100000 (2 核)
# memory.max = 17179869184 (16 GB)
# io.max = "8:16 rbps=104857600 wbps=104857600" (100 MB/s)
多租户运维
use eneros_tenant::Admin;
let admin = Admin::connect(&cluster);
// 列出所有租户
let tenants = admin.list_tenants().await?;
for t in &tenants {
println!("{}: {:?} - {:?}", t.id, t.tier, t.status);
}
// 创建新租户
let new_tenant = admin.create_tenant(TenantConfig {
id: TenantId::from("vpp_x"),
name: "X 虚拟电厂".into(),
tier: TenantTier::Pro,
..Default::default()
}).await?;
// 调整配额
admin.update_quota(&tenant_id, Quota::new().storage(20_tb)).await?;
// 资源使用统计
let usage = admin.usage(&tenant_id).await?;
println!("存储: {:.1}/{:.1} TB", usage.storage.used_tb, usage.storage.quota_tb);
println!("Agent: {}/{}", usage.agents.used, usage.agents.quota);
// 暂停/恢复租户
admin.suspend(&tenant_id).await?; // 暂停所有 Agent,但保留数据
admin.resume(&tenant_id).await?; // 恢复运行
// 终止租户
admin.terminate(&tenant_id).await?; // 删除所有数据,释放资源
租户生命周期
Created → Active ⇄ Suspended → Terminated
↓
Degraded(配额超限)
跨租户协作示例:省地协调
use eneros_tenant::CrossTenantChannel;
// 建立省地协调通道
let channel = CrossTenantChannel::new(
&tenant_province, // 省调
&tenant_local, // 地调
CoordinationProtocol::TieLineControl,
).await?;
// 省调发送协调命令
channel.send(CoordCmd::AdjustTieLine {
branch: 100,
target_mw: 200.0,
}).await?;
// 地调接收并执行
while let Ok(cmd) = channel.recv().await {
match cmd {
CoordCmd::AdjustTieLine { branch, target_mw } => {
let decision = DispatchDecision::new()
.set_generator(local_gen, target_mw);
ctx.execute(decision).await?;
}
}
}
性能指标
| 操作 | 延迟 | 备注 |
|---|
| 租户上下文注入 | < 5μs | mTLS 证书提取 |
| 命名空间前缀检查 | < 1μs | - |
| 配额校验 | < 10μs | 原子计数器 |
| 跨租户白名单校验 | < 20μs | 缓存命中 |
| 租户资源隔离开销 | < 1% | cgroup |
| 跨租户消息延迟 | < 100μs | 同集群 |
| KMS 密钥访问 | < 1ms | - |
| 单节点最大租户数 | 1000 | - |
| 集群最大租户数 | 100,000 | - |
测试环境:4 核 / 8GB / Ubuntu 22.04。
安全合规
多租户隔离满足以下合规要求:
| 标准 | 要求 | EnerOS 实现 |
|---|
| NERC CIP-005 | 电子安全边界 | per-tenant network namespace |
| IEC 62443-4-1 | 安全开发生命周期 | 内置安全模块 |
| GB/T 22239 | 等保 2.0 三级 | 强制访问控制 |
| ISO 27001 | 信息安全管理 | WORM 审计 + KMS |
| GDPR | 数据主体隔离 | per-tenant 数据加密 |
配置参数
eneros.toml 中多租户相关配置:
[tenant]
# 是否启用多租户
enabled = true
# 默认租户等级
default_tier = "pro"
# 单节点最大租户数
max_tenants_per_node = 1000
# 是否启用 cgroup 隔离
cgroup_isolation = true
# 是否启用 network namespace
network_isolation = true
# 默认存储配额(TB)
default_storage_quota_tb = 10
# 配额检查间隔(秒)
quota_check_interval_secs = 10
# 跨租户调用默认配额(次/天)
cross_tenant_quota_daily = 10000
# 是否启用 KMS
kms_enabled = true
| 参数 | 类型 | 默认值 | 说明 |
|---|
| enabled | bool | true | 启用多租户 |
| default_tier | enum | pro | 默认租户等级 |
| max_tenants_per_node | u32 | 1000 | 单节点最大租户数 |
| cgroup_isolation | bool | true | cgroup 隔离 |
| network_isolation | bool | true | 网络命名空间隔离 |
| default_storage_quota_tb | u32 | 10 | 默认存储配额 |
| quota_check_interval_secs | u32 | 10 | 配额检查间隔 |
| cross_tenant_quota_daily | u32 | 10000 | 跨租户调用配额 |
| kms_enabled | bool | true | KMS 启用 |
与其他能力的关系
| 关联能力 | 互动方式 |
|---|
| 零信任与安全增强 | 租户身份认证 |
| 运维自动化 | 租户运维 |
| 多智能体协作 | 租户内 Agent 编排 |
| 电网拓扑一等公民 | 拓扑按租户命名空间隔离 |
| 时序原生操作 | 时序数据按租户 LSM 隔离 |
| 安全守卫 | 命令按租户命名空间审计 |
| 数字孪生引擎 | 孪生 fork 按租户配额 |
| 实时双执行域 | 实时 CPU 按租户隔离 |
相关文档