modelRT/deploy/deploy.md

1082 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 项目服务部署指南
本项目依赖于 `PostgreSQL` 数据库和 `Redis Stack Server`(包含 `Redisearch` 等模块)部署文档将使用 `Docker` 容器化技术部署这两个依赖服务
## 前提条件
1. 已安装 `Docker`
2. 下载相关容器镜像
3. 确保主机的 `5432` 端口(`Postgres`)和 `6379` 端口(`Redis`)未被占用
### 1\. 部署 PostgreSQL 数据库
使用官方的 `postgres:13.16` 镜像,并设置默认的用户、密码和端口
#### 1.1 部署命令
运行以下命令启动 `PostgreSQL` 容器
```bash
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 状态检查
要确认容器是否正在运行,请执行
```bash
# 检查容器启动状态
docker ps -a grep postgres
# 检查容器启动日志信息
docker logs postgres
```
#### 1.4 初始化异步任务表
`PostgreSQL` 启动后执行以下建表语句,创建异步任务系统所需的两张表:
```sql
-- ==========================================
-- 表: 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` 容器
```bash
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 状态检查
要确认容器是否正在运行,请执行
```bash
# 检查容器启动状态
docker ps -a grep redis
# 检查容器启动日志信息
docker logs redis
```
#### 2.4 数据注入
测试数据注入
##### 2.4.1 Postgres数据注入
```SQL
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数据脚本
```shell
deploy/redis-test-data/measurments-recommend/measurement_injection.go
```
运行脚本向 Reids 导入数据
```shell
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 服务
```bash
go build -o model-rt main.go
```
#### 3.3 启动服务
使用编译好的二进制文件进行启动
```bash
./model-rt
```
#### 3.4 检测服务启动日志
在发现控制台输出如下信息`starting ModelRT server`
后即代表服务启动成功
### 4\. 部署基础依赖Kubernetes
Redis 和 RabbitMQ 部署在 Minikube 中YAML 文件位于 `deploy/k8s/`。RabbitMQ 启用双向 TLSmTLS客户端以 X.509 证书的 CN 字段作为用户名进行认证。
#### 4.1 部署 Redis
```bash
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
```bash
# 克隆 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 生成服务器证书
服务器证书需包含 SANSubject Alternative Name使其同时匹配集群内 DNS 和 Minikube IP。
创建 `server.cnf`
```text
[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
```
生成证书:
```bash
# 将 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`
```text
[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
```
生成证书:
```bash
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`
```text
[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
```
生成证书:
```bash
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 验证证书
```bash
# 验证服务器证书
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在证书文件所在目录执行
```bash
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 部署
```bash
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
```bash
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 就绪
```bash
kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s
```
##### 4.4.2 初始化异步任务表
PostgreSQL 就绪后执行 1.4 节的建表 SQL可通过以下方式进入容器执行
```bash
# 交互式 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 状态检查
```bash
kubectl get pods -l app=postgres
kubectl logs -l app=postgres --tail=30
```
##### 4.4.4 清理
```bash
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
```bash
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 就绪
```bash
kubectl wait --for=condition=ready pod -l app=mongodb --timeout=120s
```
##### 4.5.2 连接验证
```bash
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 状态检查
```bash
kubectl get pods -l app=mongodb
kubectl logs -l app=mongodb --tail=30
```
##### 4.5.4 清理
```bash
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\. 部署 ModelRTKubernetes
所有资源部署在 `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、无包管理器 |
**方式一:从源码构建并加载**
```bash
# 在项目根目录执行(默认运行用户 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
```bash
# 确认本地镜像存在
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 镜像冒烟测试
```bash
# 查看镜像大小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),进入证书文件所在目录执行:
```bash
sh deploy/k8s/modelrt-certs-secret.sh
```
该脚本等价于:
```bash
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 部署
```bash
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 状态检查
```bash
# 查看 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 APISSH 隧道本地端口 `8080` |
#### 5.7 清理
```bash
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
```bash
kubectl apply -f deploy/k8s/jaeger-deployment.yaml
kubectl apply -f deploy/k8s/jaeger-service.yaml
```
#### 6.2 部署 Loki
```bash
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
```bash
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
```bash
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 一键部署
```bash
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 状态检查
```bash
# 查看所有 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 清理
```bash
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 网络拓扑
``` text
Mac 本地端口 ──SSH隧道──▶ Ubuntu 宿主机 (192.168.1.101) ──▶ Minikube NodePort (192.168.49.2)
```
#### 7.2 建立隧道
```bash
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
```
如需后台静默运行(不占用终端):
```bash
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`;后台运行时查找并终止进程:
```bash
# 找到 ssh 隧道进程
ps aux | grep "ssh -fN"
# 终止(替换为实际 PID
kill <PID>
```
### 8\. 后续操作(停止与清理)
#### 8.1 停止容器
```bash
docker stop postgres redis
```
#### 8.2 删除容器(删除后数据将丢失)
```bash
docker rm postgres redis
```