42 KiB
项目服务部署指南
本项目依赖于 PostgreSQL 数据库和 Redis Stack Server(包含 Redisearch 等模块)部署文档将使用 Docker 容器化技术部署这两个依赖服务
前提条件
- 已安装
Docker - 下载相关容器镜像
- 确保主机的
5432端口(Postgres)和6379端口(Redis)未被占用
1. 部署 PostgreSQL 数据库
使用官方的 postgres:13.16 镜像,并设置默认的用户、密码和端口
1.1 部署命令
运行以下命令启动 PostgreSQL 容器
docker run --name postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=coslight \
-p 5432:5432 \
-d postgres:13.16
1.2 连接信息
| 参数 | 值 | 说明 |
|---|---|---|
| 容器名称 | postgres |
容器名 |
| 镜像版本 | postgres:13.16 |
镜像名 |
| 主机端口 | 5432 |
外部应用连接使用的端口 |
| 用户名 | postgres |
默认超级用户 |
| 密码 | coslight |
配置的密码 |
1.3 状态检查
要确认容器是否正在运行,请执行
# 检查容器启动状态
docker ps -a |grep postgres
# 检查容器启动日志信息
docker logs postgres
1.4 初始化异步任务表
PostgreSQL 启动后执行以下建表语句,创建异步任务系统所需的两张表:
-- ==========================================
-- 表: async_task
-- 说明: 存储异步任务的生命周期跟踪信息
-- ==========================================
CREATE TABLE IF NOT EXISTS async_task (
task_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_type VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL,
params JSONB,
created_at BIGINT NOT NULL,
finished_at BIGINT,
started_at BIGINT,
execution_time BIGINT,
progress INTEGER,
retry_count INTEGER DEFAULT 0,
max_retry_count INTEGER DEFAULT 3,
next_retry_time BIGINT,
retry_delay INTEGER DEFAULT 5000,
priority INTEGER DEFAULT 5,
queue_name VARCHAR(100) DEFAULT 'default',
worker_id VARCHAR(50),
failure_reason TEXT,
stack_trace TEXT,
created_by VARCHAR(100)
);
CREATE INDEX IF NOT EXISTS idx_async_task_task_type ON async_task(task_type);
CREATE INDEX IF NOT EXISTS idx_async_task_status ON async_task(status);
CREATE INDEX IF NOT EXISTS idx_async_task_created_at ON async_task(created_at);
CREATE INDEX IF NOT EXISTS idx_async_task_finished_at ON async_task(finished_at);
CREATE INDEX IF NOT EXISTS idx_async_task_started_at ON async_task(started_at);
CREATE INDEX IF NOT EXISTS idx_async_task_next_retry_time ON async_task(next_retry_time);
CREATE INDEX IF NOT EXISTS idx_async_task_priority ON async_task(priority);
CREATE INDEX IF NOT EXISTS idx_async_task_status_retry ON async_task(status, next_retry_time)
WHERE status = 'FAILED' AND next_retry_time IS NOT NULL;
-- ==========================================
-- 表: async_task_result
-- 说明: 存储异步任务的执行结果
-- ==========================================
CREATE TABLE IF NOT EXISTS async_task_result (
task_id UUID PRIMARY KEY,
result JSONB,
error_code INTEGER,
error_message TEXT,
error_detail JSONB,
execution_time BIGINT NOT NULL DEFAULT 0,
memory_usage BIGINT,
cpu_usage DOUBLE PRECISION,
retry_count INTEGER DEFAULT 0,
completed_at BIGINT NOT NULL
);
COMMENT ON TABLE async_task IS '异步任务生命周期跟踪表';
COMMENT ON TABLE async_task_result IS '异步任务执行结果表';
2. 部署 Redis Stack Server
我们将使用 redis/redis-stack-server:latest 镜像该镜像内置了 Redisearch 模块,用于 ModelRT 项目中补全功能
2.1 部署命令
运行以下命令启动 Redis Stack Server 容器
docker run --name redis -p 6379:6379 \
-d redis/redis-stack-server:latest
2.2 连接信息
| 参数 | 值 | 说明 |
|---|---|---|
| 容器名称 | redis |
容器名 |
| 镜像版本 | redis/redis-stack-server:latest |
镜像名 |
| 主机端口 | 6379 |
外部应用连接使用的端口 |
| 地址 | localhost:6379 |
|
| 密码 | 无 | 默认未设置密码 |
注意: 生产环境中建议使用
-e REDIS_PASSWORD=<your_secure_password>参数来设置Redis访问密码
2.3 状态检查
要确认容器是否正在运行,请执行
# 检查容器启动状态
docker ps -a |grep redis
# 检查容器启动日志信息
docker logs redis
2.4 数据注入
测试数据注入
2.4.1 Postgres数据注入
insert into public.grid(id,tagname,name,description,op,ts) VALUES (1, 'grid1', '网格1', '测试网格1', -1,CURRENT_TIMESTAMP);
insert into public.zone(id,grid_id,tagname,name,description,op,ts) VALUES (1, 1,'zone1', '区域1_1', '测试区域1_1', -1,CURRENT_TIMESTAMP);
insert into public.station(id,zone_id,tagname,name,description,is_local,op,ts) VALUES (1, 1,'station1', '站1_1_1', '测试站1_1_1', true, -1,CURRENT_TIMESTAMP),
(2, 1, 'station2', '站1_1_2', '测试站1_1_2', false, -1, CURRENT_TIMESTAMP);
INSERT INTO public.topologic(flag, uuid_from, uuid_to, context, description, op, ts)
VALUES
(1, '00000000-0000-0000-0000-000000000000', '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '{}', '', 1, CURRENT_TIMESTAMP),
(1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '10f155cf-bd27-4557-85b2-d126b6e2657f', '{}', '', 1, CURRENT_TIMESTAMP),
(1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', '{}', '', 1, CURRENT_TIMESTAMP),
(1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '70c190f2-8a75-42a9-b166-ec5f87e0aa6b', '{}', '', 1, CURRENT_TIMESTAMP),
(1, 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', '70c200f2-8a75-42a9-c166-bf5f87e0aa6b', '{}', '', 1, CURRENT_TIMESTAMP),
(1, 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', '968dd6e6-faec-4f78-b58a-d6e68426b09e', '{}', '', 1, CURRENT_TIMESTAMP),
(1, 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', '968dd6e6-faec-4f78-b58a-d6e68426b08e', '{}', '', 1, CURRENT_TIMESTAMP);
INSERT INTO public.bay (bay_uuid, name, tag, type, unom, fla, capacity, description, in_service, state, grid, zone, station, business, context, from_uuids, to_uuids, dev_protect, dev_fault_record, dev_status, dev_dyn_sense, dev_instruct, dev_etc, components, op, ts)
VALUES (
'18e71a24-694a-43fa-93a7-c4d02a27d1bc',
'', '', '',
-1, -1, -1,
'',
false,
-1,
'', '', '',
'{}',
'{}',
'[]',
'[]',
'[]',
'[]',
'[]',
'[]',
'[]',
'[]',
ARRAY['968dd6e6-faec-4f78-b58a-d6e68426b09e', '968dd6e6-faec-4f78-b58a-d6e68426b08e']::uuid[],
-1,
CURRENT_TIMESTAMP
);
INSERT INTO public.component (global_uuid, nspath, tag, name, model_name, description, grid, zone, station, station_id, type, in_service, state, status, connection, label, context, op, ts)
VALUES
(
'968dd6e6-faec-4f78-b58a-d6e68426b09e',
'ns1', 'tag1', 'component1', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'968dd6e6-faec-4f78-b58a-d6e68426b08e',
'ns2', 'tag2', 'component2', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'968dd6e6-faec-4f78-b58a-d6e88426b09e',
'ns3', 'tag3', 'component3', 'bus_1', '',
'grid1', 'zone1', 'station2', 2,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'70c190f2-8a60-42a9-b143-ec5f87e0aa6b',
'ns4', 'tag4', 'component4', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'10f155cf-bd27-4557-85b2-d126b6e2657f',
'ns5', 'tag5', 'component5', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d',
'ns6', 'tag6', 'component6', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'70c190f2-8a75-42a9-b166-ec5f87e0aa6b',
'ns7', 'tag7', 'component7', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'70c200f2-8a75-42a9-c166-bf5f87e0aa6b',
'ns8', 'tag8', 'component8', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
);
INSERT INTO public.measurement (id, tag, name, type, size, data_source, event_plan, bay_uuid, component_uuid, op, ts)
VALUES
(3, 'I11_C_rms', '45母甲侧互连电流C相1', -1, 200, '{"type": 1, "io_address": {"device": "ssu001", "channel": "TM1", "station": "001"}}', '{"cause": {"up": 55.0, "down": 45.0}, "action": {"command": "warning", "parameters": ["I段母线甲侧互连电流C相1"]}, "enable": true}', '18e71a24-694a-43fa-93a7-c4d02a27d1bc', '968dd6e6-faec-4f78-b58a-d6e68426b09e', -1, CURRENT_TIMESTAMP),
(4, 'I11_B_rms', '45母甲侧互连电流B相1', -1, 300, '{"type": 1, "io_address": {"device": "ssu001", "channel": "TM2", "station": "001"}}', '{"cause": {"upup": 65, "downdown": 35}, "action": {"command": "warning", "parameters": ["I段母线甲侧互连电流B相1"]}, "enable": true}', '18e71a24-694a-43fa-93a7-c4d02a27d1bc', '968dd6e6-faec-4f78-b58a-d6e68426b09e', -1, CURRENT_TIMESTAMP),
(5, 'I11_A_rms', '45母甲侧互连电流A相1', -1, 300, '{"type": 1, "io_address": {"device": "ssu001", "channel": "TM3", "station": "001"}}', '{"cause": {"up": 55, "down": 45, "upup": 65, "downdown": 35}, "action": {"command": "warning", "parameters": ["I段母线甲侧互连电流A相1"]}, "enable": true}', '18e71a24-694a-43fa-93a7-c4d02a27d1bc', '968dd6e6-faec-4f78-b58a-d6e68426b09e', -1, CURRENT_TIMESTAMP);
INSERT INTO public.project_manager (id, name, tag, meta_model, group_name, link_type, check_state, is_public, op, ts
) VALUES
(1, 'component', 'component', '', 'component', 0,
'{"checkState": [{"name": "global_uuid", "type": "UUID", "checked": 1, "isVisible": 1, "defaultValue": "", "lengthPrecision": -1}, {"name": "nspath", "type": "VARCHAR(32)", "checked": 1, "isVisible": 1, "defaultValue": "", "lengthPrecision": 32}, {"name": "tag", "type": "VARCHAR(32)", "checked": 1, "isVisible": 1, "defaultValue": "null", "lengthPrecision": 32}, {"name": "name", "type": "VARCHAR(64)", "checked": 1, "isVisible": 1, "defaultValue": "null", "lengthPrecision": 64}, {"name": "description", "type": "VARCHAR(512)", "checked": 1, "isVisible": 1, "defaultValue": "", "lengthPrecision": 512}, {"name": "station", "type": "VARCHAR(64)", "checked": 1, "isVisible": 1, "defaultValue": "null", "lengthPrecision": 64}, {"name": "zone", "type": "VARCHAR(64)", "checked": 1, "isVisible": 1, "defaultValue": "null", "lengthPrecision": 64}, {"name": "grid", "type": "VARCHAR(64)", "checked": 1, "isVisible": 1, "defaultValue": "null", "lengthPrecision": 64}, {"name": "type", "type": "INTEGER", "checked": 1, "isVisible": 0, "defaultValue": "0", "lengthPrecision": -1}, {"name": "in_service", "type": "SMALLINT", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "state", "type": "INTEGER", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "connection", "type": "JSONB", "checked": 1, "isVisible": 1, "defaultValue": "{}", "lengthPrecision": -1}, {"name": "label", "type": "JSONB", "checked": 1, "isVisible": 1, "defaultValue": "{}", "lengthPrecision": -1}, {"name": "context", "type": "JSONB", "checked": 1, "isVisible": 0, "defaultValue": "{}", "lengthPrecision": -1}, {"name": "op", "type": "INTEGER", "checked": 1, "isVisible": 0, "defaultValue": "-1", "lengthPrecision": -1}, {"name": "ts", "type": "TIMESTAMP", "checked": 1, "isVisible": 0, "defaultValue": "null", "lengthPrecision": -1}, {"name": "model_name", "type": "VARCHAR(64)", "checked": 1, "isVisible": 0, "defaultValue": "null", "lengthPrecision": 64}, {"name": "status", "type": "SMALLINT", "checked": 1, "isVisible": 0, "defaultValue": "null", "lengthPrecision": -1}]}', TRUE, -1, CURRENT_TIMESTAMP
),
(2, 'bus_bus_1_base_extend', 'bus_1', 'bus', 'base_extend', 0,
'{"checkState": [{"name": "bus_num", "type": "INTEGER", "checked": 1, "isVisible": 0, "defaultValue": "1", "lengthPrecision": -1}, {"name": "unom_kv", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "null", "lengthPrecision": -1}]}', FALSE, -1, CURRENT_TIMESTAMP
),
(3, 'bus_bus_1_model', 'bus_1', 'bus', 'model', 0,
'{"checkState": [{"name": "ui_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "100", "lengthPrecision": -1}, {"name": "ui_kv", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "35", "lengthPrecision": -1}, {"name": "ui_pa", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "stability_rated_current", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "1000", "lengthPrecision": -1}, {"name": "stability_dynamic_steady_current", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "40", "lengthPrecision": -1}, {"name": "load_adjustment_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "100", "lengthPrecision": -1}, {"name": "load_adjustment_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "100", "lengthPrecision": -1}, {"name": "bus_type", "type": "VARCHAR(10)", "checked": 1, "isVisible": 1, "defaultValue": "PQ母线", "lengthPrecision": 10}, {"name": "csc_s3_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_s3_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_i3_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_i3_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_z3s_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0.05", "lengthPrecision": -1}, {"name": "csc_z3s_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0.1", "lengthPrecision": -1}, {"name": "csc_s1_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_s1_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_i1_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_i1_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "csc_z1s_max", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0.05", "lengthPrecision": -1}, {"name": "csc_z1s_min", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0.1", "lengthPrecision": -1}, {"name": "csc_base_voltage", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "37", "lengthPrecision": -1}, {"name": "csc_base_capacity", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "100", "lengthPrecision": -1}]}', FALSE, -1, CURRENT_TIMESTAMP
),
(4, 'bus_bus_1_stable', 'bus_1', 'bus', 'stable', 0,
'{"checkState": [{"name": "uvpw_threshold_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "95", "lengthPrecision": -1}, {"name": "uvpw_runtime", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "10", "lengthPrecision": -1}, {"name": "uvw_threshold_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "90", "lengthPrecision": -1}, {"name": "uvw_runtime", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "10", "lengthPrecision": -1}, {"name": "ovpw_threshold_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "105", "lengthPrecision": -1}, {"name": "ovpw_runtime", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "60", "lengthPrecision": -1}, {"name": "ovw_threshold_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "110", "lengthPrecision": -1}, {"name": "ovw_runtime", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "10", "lengthPrecision": -1}, {"name": "umargin_pmax", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "umargin_qmax", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "0", "lengthPrecision": -1}, {"name": "umargin_ulim", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "90", "lengthPrecision": -1}, {"name": "umargin_plim_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "15", "lengthPrecision": -1}, {"name": "umargin_qlim_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "15", "lengthPrecision": -1}, {"name": "umargin_ulim_percent", "type": "DOUBLE PRECISION", "checked": 1, "isVisible": 1, "defaultValue": "15", "lengthPrecision": -1}]}', FALSE, -1, CURRENT_TIMESTAMP);
INSERT INTO public.bus_bus_1_stable (id, global_uuid, attribute_group, uvpw_threshold_percent, uvpw_runtime, uvw_threshold_percent, uvw_runtime, ovpw_threshold_percent, ovpw_runtime, ovw_threshold_percent, ovw_runtime,
umargin_pmax, umargin_qmax, umargin_ulim, umargin_plim_percent, umargin_qlim_percent, umargin_ulim_percent
) VALUES (
1,
'968dd6e6-faec-4f78-b58a-d6e68426b08e',
'stable',
95,
10,
90,
10,
105,
60,
110,
10,
0,
0,
90,
15,
15,
15
);
INSERT INTO public.bus_bus_1_model (id, global_uuid, attribute_group,
ui_percent, ui_kv, ui_pa, stability_rated_current, stability_dynamic_steady_current, load_adjustment_min, load_adjustment_max, bus_type, csc_s3_max, csc_s3_min, csc_i3_max, csc_i3_min, csc_z3s_max, csc_z3s_min, csc_s1_max, csc_s1_min, csc_i1_max, csc_i1_min, csc_z1s_max, csc_z1s_min, csc_base_voltage, csc_base_capacity
) VALUES (
1,
'968dd6e6-faec-4f78-b58a-d6e68426b08e',
'model',
100,
35,
0,
1000,
40,
100,
100,
'PQ母线',
0,
0,
0,
0,
0.05,
0.1,
0,
0,
0,
0,
0.05,
0.1,
37,
100
);
INSERT INTO public.bus_bus_1_base_extend (id, global_uuid, attribute_group,
bus_num, unom_kv
) VALUES (
1,
'968dd6e6-faec-4f78-b58a-d6e68426b08e',
'base_extend',
1,
NULL
);
2.4.2 Redis数据注入
Redis数据脚本
deploy/redis-test-data/measurments-recommend/measurement_injection.go
运行脚本向 Reids 导入数据
go run deploy/redis-test-data/measurments-recommend/measurement_injection.go
3. 启动 ModelRT 服务
3.1 配置服务配置文件
以下表格为配置文件参数说明表
| 类别 | 参数名 | 作用描述 | 示例值 |
|---|---|---|---|
| Postgres | host |
PostgreSQL 数据库服务器的 IP 地址或域名。 |
"192.168.1.101" |
port |
PostgreSQL 数据库服务器的端口号。 | 5432 |
|
database |
连接的数据库名称。 | "demo" |
|
user |
连接数据库所使用的用户名。 | "postgres" |
|
password |
连接数据库所使用的密码。 | "coslight" |
|
| Kafka | servers |
Kafka 集群的 Bootstrap Server 地址列表(通常是 host:port 形式,多个地址用逗号分隔)。 |
"localhost:9092" |
port |
Kafka 服务器的端口号。 | 9092 |
|
group_id |
消费者组 ID,用于标识和管理一组相关的消费者。 |
"modelRT" |
|
topic |
Kafka 消息的主题名称。 | "" |
|
auto_offset_reset |
消费者首次启动或 Offset 无效时,从哪个位置开始消费(如 earliest 或 latest)。 |
"earliest" |
|
enable_auto_commit |
是否自动提交 Offset。设为 false 通常用于手动控制 Offset 提交。 |
"false" |
|
read_message_time_duration |
读取消息时的超时或等待时间。 | ”0.5s" |
|
| Logger (Zap) | mode |
日志模式,通常为 development(开发)或 production(生产)。影响日志格式。 |
"development" |
level |
最低日志级别(如 debug, info, warn, error)。 |
"debug" |
|
filepath |
日志文件的输出路径和名称格式(%s 会被替换为日期等)。 |
"/Users/douxu/Workspace/coslight/modelRT/modelRT-%s.log" |
|
maxsize |
单个日志文件最大大小(单位:MB)。 |
1 |
|
maxbackups |
保留旧日志文件的最大个数。 | 5 |
|
maxage |
保留旧日志文件的最大天数。 | 30 |
|
compress |
是否压缩备份的日志文件。 | false |
|
| Ants Pool | parse_concurrent_quantity |
用于解析任务的协程池最大并发数量。 | 10 |
rtd_receive_concurrent_quantity |
用于实时数据接收任务的协程池最大并发数量。 | 10 |
|
| Locker Redis | addr |
分布式锁服务所使用的 Redis 地址。 |
"127.0.0.1:6379" |
password |
Locker Redis 的密码。 |
"" |
|
db |
Locker Redis 使用的数据库编号。 |
1 |
|
poolsize |
Locker Redis 连接池的最大连接数。 |
50 |
|
timeout |
Locker Redis 连接操作的超时时间(单位:毫秒)。 |
10 |
|
| Storage Redis | addr |
数据存储服务所使用的 Redis 地址(例如 Redisearch)。 |
"127.0.0.1:6379" |
password |
Storage Redis 的密码。 |
"" |
|
db |
Storage Redis 使用的数据库编号。 |
0 |
|
poolsize |
Storage Redis 连接池的最大连接数。 |
50 |
|
timeout |
Storage Redis 连接操作的超时时间(单位:毫秒)。 |
10 |
|
| Base Config | grid_id |
项目所操作的默认电网 ID。 |
1 |
zone_id |
项目所操作的默认区域 ID。 |
1 |
|
station_id |
项目所操作的默认变电站 ID。 |
1 |
|
| Service Config | service_name |
服务名称,用于日志、监控等标识。 | "modelRT" |
secret_key |
服务内部使用的秘钥,用于签名或认证。 | "modelrt_key" |
|
| DataRT API | host |
外部 DataRT 服务的主机地址。 |
"http://127.0.0.1" |
port |
DataRT 服务的端口号。 |
8888 |
|
polling_api |
轮询数据的 API 路径。 |
"datart/getPointData" |
|
polling_api_method |
调用该 API 使用的 HTTP 方法。 |
"GET" |
3.2 编译 ModelRT 服务
go build -o model-rt main.go
3.3 启动服务
使用编译好的二进制文件进行启动
./model-rt
3.4 检测服务启动日志
在发现控制台输出如下信息starting ModelRT server
后即代表服务启动成功
4. 部署基础依赖(Kubernetes)
Redis 和 RabbitMQ 部署在 Minikube 中,YAML 文件位于 deploy/k8s/。RabbitMQ 启用双向 TLS(mTLS),客户端以 X.509 证书的 CN 字段作为用户名进行认证。
4.1 部署 Redis
kubectl apply -f deploy/k8s/redis-deployment.yaml
kubectl apply -f deploy/k8s/redis-service.yaml
| 参数 | 值 | 说明 |
|---|---|---|
| 镜像 | redis/redis-stack-server:latest |
内置 Redisearch 模块 |
| NodePort | 30001 |
集群外访问端口 |
4.2 RabbitMQ TLS 证书生成
RabbitMQ 配置为仅允许 TLS 连接(listeners.tcp = none),所有客户端须持有由同一 CA 签发的证书。
4.2.1 生成根 CA
# 克隆 tls-gen 工具
git clone https://github.com/rabbitmq/tls-gen.git
cd tls-gen/basic
# 生成根 CA(结果在 result/ 目录)
make CN=rabbitmq-server
# ca_certificate.pem 和 ca_key.pem 生成于 result/
4.2.2 生成服务器证书
服务器证书需包含 SAN(Subject Alternative Name),使其同时匹配集群内 DNS 和 Minikube IP。
创建 server.cnf:
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = coslight
CN = rabbitmq-server
[v3_server]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = rabbitmq-server
DNS.2 = rabbitmq-service.default.svc.cluster.local
DNS.3 = localhost
IP.1 = 192.168.49.2
IP.2 = 127.0.0.1
生成证书:
# 将 ca_certificate.pem 和 ca_key.pem(即 cakey.pem)放在当前目录
openssl genrsa -out server_key.pem 2048
openssl req -new -key server_key.pem -out server_cert.csr -config server.cnf
openssl x509 -req -in server_cert.csr \
-CA ca_certificate.pem -CAkey cakey.pem -CAcreateserial \
-out server_certificate.pem -days 730 -sha256 \
-extfile server.cnf -extensions v3_server
rm server_cert.csr
4.2.3 生成 ModelRT 客户端证书
CN 必须与 RabbitMQ 中注册的用户名一致(modelrt-client)。
创建 modelrt.cnf:
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = coslight
CN = modelrt-client
[v3_client]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
生成证书:
openssl genrsa -out modelrt_client_key.pem 2048
openssl req -new -key modelrt_client_key.pem \
-out modelrt_client.csr -config modelrt.cnf
openssl x509 -req -in modelrt_client.csr \
-CA ca_certificate.pem -CAkey cakey.pem -CAcreateserial \
-out modelrt_client_cert.pem -days 365 \
-extensions v3_client -extfile modelrt.cnf
rm modelrt_client.csr
4.2.4 生成 EventRT 客户端证书
创建 eventrt.cnf(CN 改为 eventrt-client):
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = coslight
CN = eventrt-client
[v3_client]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
生成证书:
openssl genrsa -out eventrt_client_key.pem 2048
openssl req -new -key eventrt_client_key.pem \
-out eventrt_client.csr -config eventrt.cnf
openssl x509 -req -in eventrt_client.csr \
-CA ca_certificate.pem -CAkey cakey.pem -CAcreateserial \
-out eventrt_client_cert.pem -days 365 \
-extensions v3_client -extfile eventrt.cnf
rm eventrt_client.csr
4.2.5 验证证书
# 验证服务器证书
openssl verify -CAfile ca_certificate.pem server_certificate.pem
# 验证客户端证书
openssl verify -CAfile ca_certificate.pem modelrt_client_cert.pem
openssl verify -CAfile ca_certificate.pem eventrt_client_cert.pem
# 查看证书详情(确认 CN 和 SAN)
openssl x509 -in server_certificate.pem -noout -subject -ext subjectAltName
openssl x509 -in modelrt_client_cert.pem -noout -subject
openssl x509 -in eventrt_client_cert.pem -noout -subject
4.3 部署 RabbitMQ
4.3.1 创建证书 Secret
将服务器端三个证书文件打包为 K8s Secret(在证书文件所在目录执行):
kubectl create secret generic rabbitmq-certs \
--from-file=ca_certificate.pem=./ca_certificate.pem \
--from-file=server_certificate.pem=./server_certificate.pem \
--from-file=server_key.pem=./server_key.pem
4.3.2 部署
kubectl apply -f deploy/k8s/rabbitmq-secret.yaml
kubectl apply -f deploy/k8s/rabbitmq-config.yaml
kubectl apply -f deploy/k8s/rabbitmq-users-config.yaml
kubectl apply -f deploy/k8s/rabbitmq-deployment.yaml
kubectl apply -f deploy/k8s/rabbitmq-service.yaml
4.3.3 端口汇总
| 端口 | NodePort | 说明 |
|---|---|---|
5671 |
30671 |
AMQP over TLS(客户端连接) |
5672 |
30672 |
AMQP 明文(内部备用,生产禁用) |
15671 |
31671 |
Management UI over TLS |
15672 |
31672 |
Management UI 明文(内部备用) |
4.3.4 用户与权限说明
用户定义在 rabbitmq-users-config.yaml 的 definitions.json 中,通过 load_definitions 启动时自动加载:
| 用户 | 认证方式 | 权限 | 说明 |
|---|---|---|---|
coslight |
密码 | administrator | 管理员,密码在 rabbitmq-secret.yaml |
modelrt-client |
X.509 证书(CN) | configure/read/write | ModelRT 服务专用 |
eventrt-client |
X.509 证书(CN) | configure/read/write | EventRT 服务专用 |
web-client |
X.509 证书(CN) | read/write | Web 客户端 |
注意: 证书认证用户的
password_hash留空;RabbitMQ 通过ssl_cert_login_from = common_name将证书 CN 映射为用户名。
4.4 部署 PostgreSQL
kubectl apply -f deploy/k8s/pg-configmap.yaml
kubectl apply -f deploy/k8s/pg-pvc.yaml
kubectl apply -f deploy/k8s/pg-statefulset.yaml
kubectl apply -f deploy/k8s/pg-service.yaml
| 参数 | 值 | 说明 |
|---|---|---|
| 镜像 | postgres:13.16 |
PostgreSQL 13.16 |
| NodePort | 30432 |
集群外访问端口 |
| 数据库 | demo |
ConfigMap 中 POSTGRES_DB |
| 用户名 | postgres |
ConfigMap 中 POSTGRES_USER |
| 密码 | coslight |
ConfigMap postgres-config 中配置,生产环境迁移至 Secret |
| 存储 | 6Gi |
PVC postgres-data |
| CPU | 100m 请求 / 500m 上限 |
StatefulSet resources 字段 |
| 内存 | 256Mi 请求 / 512Mi 上限 |
StatefulSet resources 字段 |
注意: 密码当前以明文形式存储在
pg-configmap.yaml中,生产环境应将其迁移至 K8s Secret,并通过环境变量注入容器,避免将明文密码提交至版本库。
4.4.1 等待 Pod 就绪
kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s
4.4.2 初始化异步任务表
PostgreSQL 就绪后执行 1.4 节的建表 SQL,可通过以下方式进入容器执行:
# 交互式 psql
kubectl exec -it $(kubectl get pod -l app=postgres -o jsonpath='{.items[0].metadata.name}') \
-- psql -U postgres -d demo
# 或将 SQL 文件通过管道一次性执行
kubectl exec -i $(kubectl get pod -l app=postgres -o jsonpath='{.items[0].metadata.name}') \
-- psql -U postgres -d demo < /path/to/init.sql
4.4.3 状态检查
kubectl get pods -l app=postgres
kubectl logs -l app=postgres --tail=30
4.4.4 清理
kubectl delete -f deploy/k8s/pg-service.yaml \
-f deploy/k8s/pg-statefulset.yaml \
-f deploy/k8s/pg-pvc.yaml \
-f deploy/k8s/pg-configmap.yaml
4.5 部署 MongoDB
kubectl apply -f deploy/k8s/mongodb-secret.yaml
kubectl apply -f deploy/k8s/mongodb-pvc.yaml
kubectl apply -f deploy/k8s/mongodb-statefulset.yaml
kubectl apply -f deploy/k8s/mongodb-service.yaml
| 参数 | 值 | 说明 |
|---|---|---|
| 镜像 | mongo:7.0 |
MongoDB 7.0 |
| NodePort | 30017 |
集群外访问端口 |
| 用户名 | admin |
Root 管理员 |
| 密码 | coslight |
Secret mongodb-secret 中配置,生产环境请替换强密码 |
| 存储 | 2Gi |
PVC mongodb-data |
注意: 密码存储在
mongodb-secret.yaml的stringData中,生产环境应替换为强密码,并避免将明文密码提交至版本库。
4.5.1 等待 Pod 就绪
kubectl wait --for=condition=ready pod -l app=mongodb --timeout=120s
4.5.2 连接验证
kubectl exec -it $(kubectl get pod -l app=mongodb -o jsonpath='{.items[0].metadata.name}') \
-- mongosh -u admin -p coslight --authenticationDatabase admin
4.5.3 状态检查
kubectl get pods -l app=mongodb
kubectl logs -l app=mongodb --tail=30
4.5.4 清理
kubectl delete -f deploy/k8s/mongodb-service.yaml \
-f deploy/k8s/mongodb-statefulset.yaml \
-f deploy/k8s/mongodb-pvc.yaml \
-f deploy/k8s/mongodb-secret.yaml
5. 部署 ModelRT(Kubernetes)
所有资源部署在 default 命名空间,YAML 文件位于 deploy/k8s/。
5.1 构建并推送镜像
镜像采用三阶段构建,最终基于 scratch:
| 阶段 | 基础镜像 | 作用 |
|---|---|---|
| builder | golang:1.26-alpine |
编译 Go 二进制(CGO_ENABLED=0,-trimpath -ldflags="-s -w") |
| certs | alpine:3.21 |
提取 CA 证书、时区数据及非 root 用户定义(UID 默认 1000) |
| runtime | scratch |
仅含可执行文件与运行时依赖,无 shell、无包管理器 |
方式一:从源码构建并加载
# 在项目根目录执行(默认运行用户 UID=1000)
docker build -f deploy/dockerfile/modelrt.Dockerfile -t coslight/modelrt:latest .
# 自定义运行用户 UID
docker build -f deploy/dockerfile/modelrt.Dockerfile \
--build-arg USER_ID=2000 \
-t coslight/modelrt:latest .
# 加载到 Minikube(无需私有仓库)
minikube image load coslight/modelrt:latest
方式二:直接加载已有本地镜像
Ubuntu 宿主机上已存在构建好的镜像(如 modelrt:v1)时,无需重新构建,直接导入 Minikube:
# 确认本地镜像存在
docker images modelrt:v1
# 加载到 Minikube
minikube image load modelrt:v1
# 验证镜像已进入 Minikube 缓存
minikube image ls | grep modelrt
注意:
deploy/k8s/modelrt-deployment.yaml中的image字段需与加载的镜像名称一致,并将imagePullPolicy设为Never,防止 Minikube 尝试从远端拉取。
5.1.1 镜像冒烟测试
# 查看镜像大小(scratch 镜像预期 ≤ 25 MB)
docker images coslight/modelrt:latest
# 检查镜像元信息(确认 User、Cmd、架构)
docker inspect coslight/modelrt:latest
# 验证二进制可执行(无 config 时程序报错退出属预期行为,说明镜像构建正常)
docker run --rm coslight/modelrt:latest
# 挂载示例配置做完整启动验证(Ctrl+C 退出)
docker run --rm \
-v "$(pwd)/configs/config.example.yaml:/app/configs/config.yaml" \
-p 8080:8080 \
coslight/modelrt:latest
注意:
scratch镜像不含 shell,无法使用docker exec进入容器调试;如需排查问题,可临时将最终阶段改为alpine进行本地调试,确认后再切回scratch。
5.2 创建客户端证书 Secret
在 RabbitMQ TLS 证书生成完成后(见 4.2),进入证书文件所在目录执行:
sh deploy/k8s/modelrt-certs-secret.sh
该脚本等价于:
kubectl create secret generic modelrt-certs \
--from-file=ca_certificate.pem=./ca_certificate.pem \
--from-file=modelrt_client_cert.pem=./modelrt_client_cert.pem \
--from-file=modelrt_client_key.pem=./modelrt_client_key.pem
5.3 部署
kubectl apply -f deploy/k8s/modelrt-secret.yaml
kubectl apply -f deploy/k8s/modelrt-configmap.yaml
kubectl apply -f deploy/k8s/modelrt-deployment.yaml
kubectl apply -f deploy/k8s/modelrt-service.yaml
5.4 配置说明
| 配置项 | 方式 | 说明 |
|---|---|---|
postgres.password |
Secret modelrt-secret |
不写入 ConfigMap |
service.secret_key |
Secret modelrt-secret |
不写入 ConfigMap |
| RabbitMQ 客户端证书 | Secret modelrt-certs |
挂载至 /app/configs/certs/ |
config.yaml 其余配置 |
ConfigMap modelrt-config |
所有 host 已替换为 K8s service 名 |
K8S_NAMESPACE / K8S_NODE_NAME |
Downward API | 注入至日志全局字段 |
注意:
modelrt-configmap.yaml中postgres.password和service.secret_key留空,实际值由容器启动时的环境变量POSTGRES_PASSWORD/SERVICE_SECRET_KEY注入,应用需读取这两个环境变量覆盖 config 中的空值。若应用当前仅读取文件配置,可直接将值填入modelrt-secret.yaml并在 ConfigMap 中引用,或在 ConfigMap 中直接填写。
5.5 状态检查
# 查看 Pod 状态
kubectl get pods -l app=modelrt
# 查看启动日志
kubectl logs -l app=modelrt --tail=50
# 查看 Service
kubectl get svc modelrt-service
5.6 端口汇总
| NodePort | 说明 |
|---|---|
30080 |
ModelRT HTTP API,SSH 隧道本地端口 8080 |
5.7 清理
kubectl delete -f deploy/k8s/modelrt-service.yaml \
-f deploy/k8s/modelrt-deployment.yaml \
-f deploy/k8s/modelrt-configmap.yaml \
-f deploy/k8s/modelrt-secret.yaml
kubectl delete secret modelrt-certs
6. 部署可观测性栈(Kubernetes)
在 Kubernetes 集群中部署 Jaeger(链路追踪)+ Loki + Promtail + Grafana(日志可视化)。所有资源部署在 default 命名空间,YAML 文件位于 deploy/k8s/。
6.1 部署 Jaeger
kubectl apply -f deploy/k8s/jaeger-deployment.yaml
kubectl apply -f deploy/k8s/jaeger-service.yaml
6.2 部署 Loki
kubectl apply -f deploy/k8s/loki-configmap.yaml
kubectl apply -f deploy/k8s/loki-pvc.yaml
kubectl apply -f deploy/k8s/loki-deployment.yaml
kubectl apply -f deploy/k8s/loki-service.yaml
6.3 部署 Promtail
kubectl apply -f deploy/k8s/promtail-rbac.yaml
kubectl apply -f deploy/k8s/promtail-configmap.yaml
kubectl apply -f deploy/k8s/promtail-daemonset.yaml
6.4 部署 Grafana
kubectl apply -f deploy/k8s/grafana-configmap.yaml
kubectl apply -f deploy/k8s/grafana-deployment.yaml
kubectl apply -f deploy/k8s/grafana-service.yaml
6.5 一键部署
kubectl apply -f deploy/k8s/jaeger-deployment.yaml \
-f deploy/k8s/jaeger-service.yaml \
-f deploy/k8s/loki-configmap.yaml \
-f deploy/k8s/loki-pvc.yaml \
-f deploy/k8s/loki-deployment.yaml \
-f deploy/k8s/loki-service.yaml \
-f deploy/k8s/promtail-rbac.yaml \
-f deploy/k8s/promtail-configmap.yaml \
-f deploy/k8s/promtail-daemonset.yaml \
-f deploy/k8s/grafana-configmap.yaml \
-f deploy/k8s/grafana-deployment.yaml \
-f deploy/k8s/grafana-service.yaml
6.6 状态检查
# 查看所有 Pod 状态
kubectl get pods
# 查看所有 Service 及 NodePort
kubectl get svc
6.7 端口汇总
| 服务 | NodePort | 访问地址 | 说明 |
|---|---|---|---|
| Jaeger UI | 31686 |
http://<NodeIP>:31686 |
链路追踪查询界面 |
| Loki | 31100 |
http://<NodeIP>:31100 |
日志 HTTP API |
| Grafana | 31000 |
http://<NodeIP>:31000 |
可视化界面,账号 admin / coslight |
| OTLP gRPC | 31317 |
<NodeIP>:31317 |
ModelRT OTel 上报地址(gRPC) |
| OTLP HTTP | 31318 |
http://<NodeIP>:31318 |
ModelRT OTel 上报地址(HTTP) |
6.8 清理
kubectl delete -f deploy/k8s/
7. Mac 本地访问(SSH 隧道)
ModelRT / EventRT 在 Mac 本地运行时,依赖的 RabbitMQ、Redis、Jaeger、Loki、Grafana 均部署在 Ubuntu 宿主机(192.168.1.101)上的 Minikube(192.168.49.2)中。由于 Minikube 网络不直接对外暴露,需通过 SSH 本地端口转发建立访问隧道。
7.1 网络拓扑
Mac 本地端口 ──SSH隧道──▶ Ubuntu 宿主机 (192.168.1.101) ──▶ Minikube NodePort (192.168.49.2)
7.2 建立隧道
ssh -L 5432:192.168.49.2:30432 \
-L 27017:192.168.49.2:30017 \
-L 5671:192.168.49.2:30671 \
-L 15671:192.168.49.2:31671 \
-L 6379:192.168.49.2:30001 \
-L 4318:192.168.49.2:31318 \
-L 16686:192.168.49.2:31686 \
-L 3100:192.168.49.2:31100 \
-L 3000:192.168.49.2:31000 \
douxu@192.168.1.101
如需后台静默运行(不占用终端):
ssh -fN \
-L 5432:192.168.49.2:30432 \
-L 27017:192.168.49.2:30017 \
-L 5671:192.168.49.2:30671 \
-L 15671:192.168.49.2:31671 \
-L 6379:192.168.49.2:30001 \
-L 4318:192.168.49.2:31318 \
-L 16686:192.168.49.2:31686 \
-L 3100:192.168.49.2:31100 \
-L 3000:192.168.49.2:31000 \
douxu@192.168.1.101
7.3 端口映射说明
| Mac 本地端口 | Minikube NodePort | 服务 | 说明 |
|---|---|---|---|
5432 |
30432 |
PostgreSQL | 数据库连接 localhost:5432 |
27017 |
30017 |
MongoDB | 数据库连接 localhost:27017 |
5671 |
30671 |
RabbitMQ AMQP | ModelRT / EventRT 消息队列连接 |
15671 |
31671 |
RabbitMQ Management | RabbitMQ 管理界面 http://localhost:15671 |
6379 |
30001 |
Redis | 分布式锁 / 数据存储 |
4318 |
31318 |
OTLP HTTP | OTel Trace 上报(Jaeger Collector) |
16686 |
31686 |
Jaeger UI | 链路追踪查询 http://localhost:16686 |
3100 |
31100 |
Loki | 日志查询 API |
3000 |
31000 |
Grafana | 可视化界面 http://localhost:3000 |
注意: 隧道建立后,本地配置文件中所有服务地址均填
localhost:<本地端口>,无需修改即可在Mac上直接运行服务。
7.4 关闭隧道
前台运行时直接 Ctrl+C;后台运行时查找并终止进程:
# 找到 ssh 隧道进程
ps aux | grep "ssh -fN"
# 终止(替换为实际 PID)
kill <PID>
8. 后续操作(停止与清理)
8.1 停止容器
docker stop postgres redis
8.2 删除容器(删除后数据将丢失)
docker rm postgres redis