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 创建虚拟环境
cd /home/yuyr/dev/switch_lab/netconf_exporter
python3 -m venv .venv
source .venv/bin/activate
之后所有命令都建议在虚拟环境中执行(终端提示符前通常会有 (.venv))。
1.2 安装依赖
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/ 目录中。
推荐使用如下命令运行所有测试:
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 过滤,例如:
PYTHONPATH=src .venv/bin/pytest -q -m "h3c_live"
只跑 HTTP 端到端测试:
PYTHONPATH=src .venv/bin/pytest -q -m "http_e2e"
3. 配置 H3C NETCONF 访问参数(.env)
为了方便本地联调 H3C 设备,本项目支持从 .env 文件中加载 H3C 连接参数。tests/conftest.py 会在 pytest 启动时自动读取 .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 注册):
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
虚拟环境里,使用如下命令启动服务:
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。
启动成功后,可以在另一个终端通过:
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 中一致):
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
预期返回(示例):
{
"name": "h3c-live-1",
"host": "127.0.0.1",
"port": 8830,
"enabled": true,
"scrape_interval_seconds": null,
"supports_xpath": false,
"source": "runtime"
}
可以再通过:
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 获取指标:
curl -s http://127.0.0.1:19100/metrics | head -n 40
或聚焦某些关键指标:
# 健康状态
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_'
典型输出示例(部分):
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 设备:
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 设备,只会重新加载配置并恢复运行时设备列表。