EnerOS v0.12.0
发布日期:2024年12月01日 版本代号:Tenant Git Tag:v0.12.0 支持状态:稳定(Stable) Crate 总数:26(新增 4 个) 测试用例数:3580+(新增 480)
版本概述
EnerOS v0.12.0「Tenant」是 EnerOS 面向「多主体共享」场景的关键版本,本版本的核心目标是引入**多租户隔离(Multi-Tenant Isolation)**能力,使单套 EnerOS 集群能够安全承载多个独立电力主体(如省级电网公司、售电公司、虚拟电厂运营商、大型工业用户)的工作负载,且各租户之间在命名空间、资源配额、权限模型与数据访问四个层面实现完全隔离。
在电力市场改革与新型电力系统建设的背景下,越来越多的主体需要共享同一套调度与运维基础设施——例如省级电力交易中心可能同时为 30+ 售电公司提供申报服务,虚拟电厂运营商需要聚合数百家分布式资源。传统方案是为每个主体部署独立集群,存在成本高、资源利用率低、跨主体协同困难等问题。v0.12.0 通过内核级多租户支持,使单集群即可安全承载数十至上百个租户,资源利用率提升 4-6 倍。
本版本引入了 eneros-tenant(租户管理)、eneros-tenant-quota(资源配额)、eneros-tenant-rbac(租户级 RBAC)、eneros-tenant-isolation(数据隔离)四个新 crate。设计哲学是「电力责任边界即租户边界」——租户的划分与电力系统的安全责任区、调度管辖范围、资产所有权严格对齐,确保跨租户操作不会绕过电力安全规程。
关键数据
| 指标 | v0.11.0(单租户) | v0.12.0(32 租户) | 说明 |
|---|---|---|---|
| 单集群租户数 | 1 | 1-256 | 硬隔离 |
| 租户间延迟干扰 | N/A | < 2% | CPU 隔离 |
| 资源利用率 | 22% | 78% | 共享提升 |
| 租户创建耗时 | N/A | 120ms | 热路径 |
| 配额校验延迟 | N/A | 8μs | 内核级 |
新特性
1. 多租户隔离架构
引入 eneros-tenant crate,提供完整的租户生命周期管理。每个租户拥有独立的命名空间(Namespace)、资源配额、权限策略与数据视图,租户之间默认完全隔离,仅可通过显式授权进行受控协作。
租户模型
use eneros_tenant::{Tenant, TenantId, TenantConfig};
// 创建一个省级电网租户
let tenant = Tenant::new(TenantId::from("grid-province-a"))
.config(TenantConfig {
display_name: "A省电力公司".into(),
namespace_prefix: "gpa".into(),
max_agents: 256,
max_topology_nodes: 50000,
max_timeseries_throughput: 1_000_000, // 点/秒
storage_quota: 512 * 1024 * 1024 * 1024, // 512GB
isolation_level: IsolationLevel::Strict,
})?;
// 启用租户
tenant.activate().await?;
隔离层级
| 层级 | 隔离对象 | 实现机制 | 强度 |
|---|---|---|---|
| L1 命名空间 | 拓扑/Agent/任务 | 前缀路由 | 逻辑隔离 |
| L2 资源 | CPU/内存/存储 | cgroup + 配额 | 硬隔离 |
| L3 权限 | API/操作 | RBAC + ABAC | 强制访问 |
| L4 数据 | 时序/快照/日志 | 行级加密 + 视图 | 加密隔离 |
2. 命名空间隔离
每个租户拥有独立的命名空间,所有资源(拓扑节点、Agent、任务、时序数据)均归属特定命名空间,跨命名空间访问需显式授权。命名空间通过前缀路由实现,避免全局查找带来的性能开销。
命名空间操作
use eneros_tenant::Namespace;
// 在租户命名空间下创建拓扑
let bus = topology.create_bus(
Namespace::of("grid-province-a"),
BusId::from(1),
BusConfig { voltage_level: 110, base_kv: 110.0 },
)?;
// 跨命名空间访问需要授权
let cross = topology.access(
Namespace::of("grid-province-a"),
BusId::from(1),
AccessScope::Read,
).authorize(&admin_token)?;
// 查询自动限定在当前租户命名空间
let buses = topology.list_buses()
.in_namespace(Namespace::of("grid-province-a"))
.execute()?;
命名空间路由表
| 命名空间前缀 | 租户 | 资源数量 | 路由延迟 |
|---|---|---|---|
| gpa | grid-province-a | 48230 | 1.2μs |
| vpp | virtual-pp-1 | 1240 | 0.8μs |
| ind | industrial-user-7 | 560 | 0.6μs |
3. 资源配额管理
引入 eneros-tenant-quota crate,对每个租户的 CPU、内存、存储、Agent 数量、时序写入吞吐等资源进行精细化配额管理。配额校验在内核热路径完成,延迟低于 10μs。
配额配置
use eneros_tenant_quota::{Quota, QuotaConfig, Resource};
let quota = Quota::for_tenant(TenantId::from("vpp-1"))
.limit(Resource::Cpu, QuotaConfig::cores(8))
.limit(Resource::Memory, QuotaConfig::bytes(16 * 1024 * 1024 * 1024))
.limit(Resource::Agents, QuotaConfig::count(64))
.limit(Resource::TimeseriesWrite, QuotaConfig::per_second(100_000))
.limit(Resource::Storage, QuotaConfig::bytes(128 * 1024 * 1024 * 1024))
.burst_allowance(0.2) // 允许 20% 突发
.enforce_mode(EnforceMode::Hard);
配额校验
// 在 Agent 创建时校验配额
match quota.check_and_reserve(Resource::Agents, 1) {
Ok(reservation) => {
let agent = Agent::new(...).spawn()?;
reservation.commit();
}
Err(QuotaError::Exceeded { current: 64, limit: 64 }) => {
return Err("租户 Agent 数量已达上限");
}
}
资源配额默认值
| 资源 | 标准租户 | 大型租户 | 小型租户 |
|---|---|---|---|
| CPU 核数 | 8 | 32 | 2 |
| 内存 | 16GB | 64GB | 4GB |
| Agent 数量 | 64 | 512 | 8 |
| 时序写入 | 10万点/秒 | 100万点/秒 | 1万点/秒 |
| 存储空间 | 128GB | 2TB | 16GB |
4. 租户级 RBAC
引入 eneros-tenant-rbac crate,为每个租户维护独立的角色与权限模型。权限校验同时考虑租户边界与角色边界,防止跨租户越权。
角色定义
use eneros_tenant_rbac::{Role, Permission, Scope};
let dispatcher_role = Role::for_tenant(TenantId::from("grid-province-a"))
.name("dispatcher")
.permission(Permission::new("topology:read"))
.permission(Permission::new("dispatch:command"))
.permission(Permission::new("agent:manage"))
.scope(Scope::TenantOnly); // 仅限本租户
let observer_role = Role::for_tenant(TenantId::from("grid-province-a"))
.name("observer")
.permission(Permission::new("topology:read"))
.permission(Permission::new("timeseries:read"))
.scope(Scope::TenantOnly);
权限校验
// 校验用户在特定租户下的权限
let ctx = TenantContext::new(user_id, TenantId::from("grid-province-a"));
if ctx.has_permission("dispatch:command")? {
ctx.syscall(DispatchCommand { ... }).await?;
} else {
return Err(PermissionDenied);
}
// 跨租户操作需要显式跨租授权
ctx.cross_tenant(TenantId::from("vpp-1"))
.require_permission("topology:read")?;
5. 数据隔离
引入 eneros-tenant-isolation crate,对时序数据、拓扑快照、审计日志进行行级隔离。每个租户的数据在物理上可共享存储,在逻辑上完全隔离,且支持按租户独立加密。
时序数据隔离
use eneros_tenant_isolation::TenantTimeseries;
// 写入时自动标记租户
let ts = TenantTimeseries::for_tenant(TenantId::from("grid-province-a"));
ts.write(Point::new("bus.1.voltage", 1.024).at(now())).await?;
// 查询时自动过滤
let result = ts.query()
.metric("bus.1.voltage")
.range(now() - Duration::minutes(30), now())
.execute()?; // 仅返回本租户数据
数据加密策略
| 数据类型 | 隔离方式 | 加密 | 密钥管理 |
|---|---|---|---|
| 时序数据 | 租户前缀 + 行级 | AES-256-GCM | 每租户独立密钥 |
| 拓扑快照 | 命名空间 | AES-256-GCM | 每租户独立密钥 |
| 审计日志 | 租户前缀 | AES-256-GCM | 每租户独立密钥 |
| Agent 状态 | 命名空间 | 可选加密 | 租户自管理 |
改进
- API 网关:所有 REST/GraphQL API 自动注入租户上下文,无需应用层处理
- 审计日志:所有操作自动记录租户标识,支持按租户检索
- 可观测性:新增
eneros-tenant-metrics子模块,按租户维度暴露指标 - 配额告警:支持配置配额使用率阈值,达到阈值自动告警
Bug 修复
- 修复
eneros-agent在租户切换时上下文残留的问题(#1208) - 修复
eneros-timeseries跨租户查询在索引未命中时泄露数据的问题(#1215) - 修复
eneros-topology命名空间前缀冲突时未报错的问题(#1221) - 修复
eneros-sched配额校验在任务快速创建时偶发逃逸的问题(#1227)
破坏性变更
Agent::new签名变更:新增tenant: TenantId参数,原签名迁移至Agent::new_standaloneTopology::create_bus:首参数改为Namespace,原BusId参数顺序调整Timeseries::query:默认限定当前租户,跨租户查询需使用query_cross_tenant
性能提升
- 租户创建耗时从 800ms 降至 120ms(提升 6.7 倍)
- 配额校验延迟从 45μs 降至 8μs(提升 5.6 倍)
- 命名空间路由延迟稳定在 1.2μs 以内
- 单集群可承载租户数从 8 提升至 256
贡献者
本版本由 21 位贡献者共同完成,提交 358 次。特别感谢:
- @tenant-core:租户模型与生命周期管理
- @quota-master:内核级配额校验引擎
- @rbac-arch:租户级 RBAC 权限模型
- @crypto-isolation:行级数据隔离与加密方案
升级指南
从 v0.11.0 升级
1. 更新依赖
# Cargo.toml
[dependencies]
eneros-tenant = { version = "0.12" }
eneros-tenant-quota = { version = "0.12" }
eneros-tenant-rbac = { version = "0.12" }
eneros-tenant-isolation = { version = "0.12", features = ["encryption"] }
2. 初始化默认租户
升级后需创建默认租户并迁移现有资源:
use eneros_tenant::migrate;
// 自动将 v0.11.0 的所有资源迁移到 "default" 租户
migrate::from_single_tenant(TenantId::from("default")).await?;
3. 适配新 API
// v0.11.0 旧写法
let agent = Agent::new("my-agent").spawn()?;
let bus = topology.create_bus(1, BusConfig { ... })?;
// v0.12.0 新写法
let agent = Agent::new("my-agent", TenantId::from("default")).spawn()?;
let bus = topology.create_bus(
Namespace::of("default"),
BusId::from(1),
BusConfig { ... },
)?;
4. 配置配额
为默认租户配置合理配额,避免单租户耗尽集群资源:
# eneros.toml
[tenant.default]
cpu_cores = 32
memory = "64GB"
max_agents = 512
timeseries_write_per_second = 1000000
storage = "2TB"
5. 数据加密(可选)
如需启用租户级数据加密,需配置密钥管理服务:
[tenant.encryption]
enabled = true
algorithm = "aes-256-gcm"
kms_endpoint = "https://kms.internal:8200"
key_rotation_days = 90