288 lines
8.7 KiB
Markdown
288 lines
8.7 KiB
Markdown
# NETCONF Transceiver Exporter
|
||
|
||
基于 Python 的 Prometheus Exporter,通过 NETCONF 周期性轮询 H3C 交换机,采集光模块(transceiver)及其物理通道指标,并通过 HTTP `/metrics` 暴露给 Prometheus。
|
||
|
||
本项目已按设计文档 `specs/transceiver-exporter-design-v9.md` 与 TDD 文档 `specs/transcrive-exporter-tdd-design-v2.md` 实现,并带有完整测试集。
|
||
|
||
下面以本仓库为路径 `/home/yuyr/dev/switch_lab/netconf_exporter` 为例,说明从零开始的操作步骤。
|
||
|
||
---
|
||
|
||
## 1. 创建虚拟环境 & 安装依赖
|
||
|
||
### 1.1 创建虚拟环境
|
||
|
||
```bash
|
||
cd /home/yuyr/dev/switch_lab/netconf_exporter
|
||
|
||
python3 -m venv .venv
|
||
source .venv/bin/activate
|
||
```
|
||
|
||
之后所有命令都建议在虚拟环境中执行(终端提示符前通常会有 `(.venv)`)。
|
||
|
||
### 1.2 安装依赖
|
||
|
||
```bash
|
||
cd /home/yuyr/dev/switch_lab/netconf_exporter
|
||
|
||
pip install --upgrade pip
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
`requirements.txt` 包含:
|
||
|
||
- 运行依赖:`fastapi`, `uvicorn`, `ncclient`, `cryptography`, `prometheus_client`, `PyYAML` 等;
|
||
- 开发/测试依赖:`pytest`, `pytest-asyncio`, `pytest-cov`, `httpx` 等。
|
||
|
||
---
|
||
|
||
## 2. 运行测试
|
||
|
||
项目所有源码都在 `src/exporter/` 下,测试在 `tests/` 目录中。
|
||
|
||
推荐使用如下命令运行所有测试:
|
||
|
||
```bash
|
||
cd /home/yuyr/dev/switch_lab/netconf_exporter
|
||
|
||
PYTHONPATH=src .venv/bin/pytest -q
|
||
```
|
||
|
||
- 会自动加载 `tests/conftest.py` 中定义的配置(包括从 `.env` 加载 H3C NETCONF 参数);
|
||
- 默认会运行:
|
||
- 单元/组件测试;
|
||
- 集成测试(包括 HTTP E2E);
|
||
- H3C NETCONF live 测试(如果 `.env` 中配置了可用的 H3C 连接参数)。
|
||
|
||
如果只想运行 H3C 相关测试,可以使用 mark 过滤,例如:
|
||
|
||
```bash
|
||
PYTHONPATH=src .venv/bin/pytest -q -m "h3c_live"
|
||
```
|
||
|
||
只跑 HTTP 端到端测试:
|
||
|
||
```bash
|
||
PYTHONPATH=src .venv/bin/pytest -q -m "http_e2e"
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 配置 H3C NETCONF 访问参数(.env)
|
||
|
||
为了方便本地联调 H3C 设备,本项目支持从 `.env` 文件中加载 H3C 连接参数。`tests/conftest.py` 会在 pytest 启动时自动读取 `.env`。
|
||
|
||
在项目根目录创建或编辑 `.env`:
|
||
|
||
```env
|
||
H3C_NETCONF_HOST=127.0.0.1
|
||
H3C_NETCONF_PORT=8830
|
||
H3C_NETCONF_USER=netconf_user
|
||
H3C_NETCONF_PASSWORD='NASPLab123!'
|
||
```
|
||
|
||
说明:
|
||
|
||
- 上述示例假设已经在本机将 127.0.0.1:8830 转发到真实 H3C 设备的 NETCONF 端口;
|
||
- `.env` 支持行尾注释(以 `#` 开头的行会被忽略),支持被单/双引号包裹的值;
|
||
- 若不想把密码写入 `.env`,可以在 shell 中 `export H3C_NETCONF_PASSWORD=...`,环境变量优先生效。
|
||
|
||
---
|
||
|
||
## 4. 编辑配置文件 config.yaml
|
||
|
||
Exporter 在启动时从 `config.yaml` 中加载全局配置和静态设备列表。典型最小配置示例如下(仅含全局配置,设备通过 HTTP API 注册):
|
||
|
||
```yaml
|
||
global:
|
||
http_listen: "0.0.0.0:19100" # HTTP 监听地址
|
||
scrape_interval_seconds: 5 # 采集周期(秒)
|
||
netconf_port: 830 # 默认 NETCONF 端口(可被 runtime 设备覆盖)
|
||
connect_timeout_seconds: 5 # SSH 连接超时
|
||
rpc_timeout_seconds: 30 # RPC 调用超时
|
||
max_workers: 5
|
||
|
||
api_token: "changeme" # HTTP API token,curl/客户端需携带
|
||
|
||
runtime_db_path: "./devices.db" # SQLite 路径(保存 runtime 设备列表)
|
||
|
||
# Fernet 密钥:32 字节 URL-safe Base64(长度 44)
|
||
# 生成方式:
|
||
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
||
password_secret: "在这里替换为你的FernetKey"
|
||
|
||
ssh_keepalive_seconds: 30
|
||
failure_threshold: 3 # 连续失败多少次开始退避
|
||
max_backoff_factor: 8 # 最大退避倍数
|
||
shutdown_timeout_seconds: 60 # 关停时等待 scraper 的超时时间
|
||
|
||
log_level: INFO
|
||
log_to_stdout: true
|
||
log_file: "" # 若非空,则写入指定文件
|
||
|
||
devices: [] # 静态设备先留空,通过 API 动态注册
|
||
```
|
||
|
||
注意:
|
||
|
||
- `global.password_secret` 必须是合法的 Fernet key,否则程序会在启动时抛异常;
|
||
- 若将 `shutdown_timeout_seconds` 配得太小,相比 `scrape_interval_seconds + rpc_timeout_seconds`,会在启动时打印一个 warning(来自配置校验),提示关停可能会在 RPC 尚未结束时终止 scraper。
|
||
|
||
---
|
||
|
||
## 5. 启动 Exporter HTTP Server
|
||
|
||
虚拟环境里,使用如下命令启动服务:
|
||
|
||
```bash
|
||
cd /home/yuyr/dev/switch_lab/netconf_exporter
|
||
|
||
PYTHONPATH=src .venv/bin/python -m exporter.main --config config.yaml
|
||
```
|
||
|
||
行为说明:
|
||
|
||
- 启动时:
|
||
- 加载 `config.yaml`;
|
||
- 初始化日志系统(包含 `device=...` 字段);
|
||
- 初始化 SQLite `devices.db`;
|
||
- 加载静态设备(若有);
|
||
- 启动 Scraper 线程,按全局配置定期轮询设备;
|
||
- 启动 Uvicorn HTTP server,默认监听 `http_listen`(例如 `0.0.0.0:19100`)。
|
||
- Ctrl+C 时:
|
||
- Uvicorn 会优雅关停 HTTP;
|
||
- Scraper 会收到 stop 信号并在 `shutdown_timeout_seconds` 内退出;
|
||
- 所有 NETCONF 连接和 SQLite 资源会被关闭;
|
||
- `KeyboardInterrupt` 会被捕获,退出不会打印 traceback。
|
||
|
||
启动成功后,可以在另一个终端通过:
|
||
|
||
```bash
|
||
curl -s http://127.0.0.1:19100/healthz
|
||
```
|
||
|
||
查看基本健康状态(`{"status":"ok", ...}`)。
|
||
|
||
---
|
||
|
||
## 6. 通过 curl 注册 H3C 设备(runtime device)
|
||
|
||
假设已经准备好 H3C 的 NETCONF 代理:
|
||
|
||
- H3C 可通过 127.0.0.1:8830 被访问;
|
||
- 用户名/密码为 `.env` 中配置的 `H3C_NETCONF_USER` / `H3C_NETCONF_PASSWORD`。
|
||
|
||
启动 exporter 后,在另一个终端中执行(注意 token 要与 config.yaml 中一致):
|
||
|
||
```bash
|
||
cd /home/yuyr/dev/switch_lab/netconf_exporter
|
||
|
||
curl -s -X POST \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-API-Token: changeme" \
|
||
-d '{
|
||
"name": "h3c-live-1",
|
||
"host": "127.0.0.1",
|
||
"port": 8830,
|
||
"username": "netconf_user",
|
||
"password": "NASPLab123!",
|
||
"enabled": true,
|
||
"supports_xpath": false
|
||
}' \
|
||
http://127.0.0.1:19100/api/v1/devices
|
||
```
|
||
|
||
预期返回(示例):
|
||
|
||
```json
|
||
{
|
||
"name": "h3c-live-1",
|
||
"host": "127.0.0.1",
|
||
"port": 8830,
|
||
"enabled": true,
|
||
"scrape_interval_seconds": null,
|
||
"supports_xpath": false,
|
||
"source": "runtime"
|
||
}
|
||
```
|
||
|
||
可以再通过:
|
||
|
||
```bash
|
||
curl -s -H "X-API-Token: changeme" http://127.0.0.1:19100/api/v1/devices
|
||
```
|
||
|
||
确认设备已注册(包含 `source: "runtime"`)。
|
||
|
||
---
|
||
|
||
## 7. 通过 curl 获取 Prometheus 指标
|
||
|
||
Scraper 线程会按 `global.scrape_interval_seconds` 周期性访问所有启用的设备,通过 NETCONF `<get>` 拉取 transceiver/channel 数据,并写入内存缓存。
|
||
|
||
等待一到两个采集周期(例如配置为 5 秒,则等待 10 秒左右)后,可用 curl 获取指标:
|
||
|
||
```bash
|
||
curl -s http://127.0.0.1:19100/metrics | head -n 40
|
||
```
|
||
|
||
或聚焦某些关键指标:
|
||
|
||
```bash
|
||
# 健康状态
|
||
curl -s http://127.0.0.1:19100/metrics | grep netconf_scrape
|
||
|
||
# Transceiver 级指标
|
||
curl -s http://127.0.0.1:19100/metrics | grep '^transceiver_'
|
||
|
||
# Channel 级指标
|
||
curl -s http://127.0.0.1:19100/metrics | grep '^transceiver_channel_'
|
||
```
|
||
|
||
典型输出示例(部分):
|
||
|
||
```text
|
||
netconf_scrape_success{device="h3c-live-1"} 1
|
||
netconf_scrape_duration_seconds{device="h3c-live-1"} 0.532
|
||
|
||
transceiver_channel_rx_power_dbm{device="h3c-live-1",port="1/0/1",channel="1/0/1:1",component_name="63.TwoHundredGigE1/0/1:1"} -3.53
|
||
transceiver_channel_tx_power_dbm{device="h3c-live-1",port="1/0/1",channel="1/0/1:1",component_name="63.TwoHundredGigE1/0/1:1"} 2.09
|
||
...
|
||
```
|
||
|
||
如果 `netconf_scrape_success=0` 且 `netconf_scrape_errors_total{error_type="TimeoutError"}` > 0,说明采集超时或失败,可通过 exporter 日志进一步排查(`exporter.scraper` logger 会输出具体异常堆栈)。
|
||
|
||
---
|
||
|
||
## 8. 删除 runtime 设备
|
||
|
||
若需删除通过 API 注册的 H3C 设备:
|
||
|
||
```bash
|
||
curl -s -X DELETE \
|
||
-H "X-API-Token: changeme" \
|
||
http://127.0.0.1:19100/api/v1/devices/h3c-live-1
|
||
```
|
||
|
||
再次列出设备即不再看到 `h3c-live-1`。静态设备(来自 config.yaml `devices:`)无法通过 API 删除。
|
||
|
||
---
|
||
|
||
## 9. 关停 Exporter
|
||
|
||
在运行 `exporter.main` 的终端中按 `Ctrl+C`:
|
||
|
||
- Uvicorn 会打印 `Shutting down` / `Application shutdown complete`;
|
||
- Scraper 线程会收到终止信号,尝试在 `shutdown_timeout_seconds` 内退出;
|
||
- 所有 NETCONF 会话会发送 `<close-session>` 并关闭;
|
||
- `KeyboardInterrupt` 会被捕获,退出不会打印 Python traceback。
|
||
|
||
Exporter 本身不持久化运行时状态,只有:
|
||
|
||
- `config.yaml`:静态全局配置 + 静态设备;
|
||
- `devices.db`:运行时注册的设备列表(已加密的密码)。
|
||
|
||
因此重启 Exporter 不会影响 H3C 设备,只会重新加载配置并恢复运行时设备列表。
|
||
|