# 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 `` 拉取 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 会话会发送 `` 并关闭; - `KeyboardInterrupt` 会被捕获,退出不会打印 Python traceback。 Exporter 本身不持久化运行时状态,只有: - `config.yaml`:静态全局配置 + 静态设备; - `devices.db`:运行时注册的设备列表(已加密的密码)。 因此重启 Exporter 不会影响 H3C 设备,只会重新加载配置并恢复运行时设备列表。