在网络设备数量逐渐增多以后,依靠人工登录交换机、路由器、防火墙执行 display current-configuration 或 show running-config 再手工保存配置,基本不具备可持续性。
尤其是在多厂商网络环境中,真正需要解决的不只是"能不能定时执行命令",还包括:
- 不同设备使用不同登录提示符和命令;
- 配置发生变化后是否能够追溯;
- 是否能够通过 Git 查看历史版本;
- 新增设备后能否自动纳管;
- 华为、H3C、Cisco 等设备模型如何适配;
- 某台设备抓取失败时,如何快速判断问题出在哪里。
因此,我最近尝试使用 Oxidized 搭建网络设备配置自动备份平台。
Oxidized 本身并不复杂,但真正从"容器运行起来"走到"设备配置稳定备份下来",中间还是遇到了不少问题。
本文记录一次实际部署和排错过程,重点不是简单复制官方配置,而是把几个容易踩坑的地方说明白。
一、Oxidized 是什么
Oxidized 是一个面向网络设备的配置备份工具,可以理解成:
自动登录网络设备 → 执行配置查询命令 → 获取配置 → 保存版本。
典型架构如下:
text
+----------------+
| router.db |
| 设备资产清单 |
+-------+--------+
|
v
+-------------+ +------+-------+
| Huawei VRP |----->| |
+-------------+ | |
| Oxidized |------> Git Repository
+-------------+ | | 配置历史版本
| Cisco IOS |----->| |
+-------------+ +------+-------+
|
+-------------+ |
| H3C / Other |-------------+
+-------------+
|
v
Oxidized Web
Web 查询界面
我这次主要希望实现:
- 网络设备自动配置备份;
- 支持华为等多厂商设备;
- 使用 Git 保存历史版本;
- 提供 Web 页面查看备份状态;
- 整体通过 Docker 部署,方便后续迁移和维护。
二、Docker 部署
这里采用 Docker Compose。
目录可以规划为:
text
oxidized/
├── docker-compose.yml
├── config/
│ ├── config
│ ├── router.db
│ └── model/
│ └── oxidized.git/
核心思想是把 Oxidized 的配置目录持久化出来。
例如:
yaml
services:
oxidized:
image: oxidized/oxidized:latest
container_name: oxidized
restart: unless-stopped
ports:
- "8888:8888" # Web UI / REST API 端口
volumes:
- ./config:/home/oxidized/.config/oxidized
# 若想将备份的 Git 仓库存到宿主机,取消下面一行的注释
- ./output:/home/oxidized/.config/oxidized/output
environment:
- CONFIG_RELOAD_INTERVAL=600 # 每 600 秒自动重载配置
启动:
bash
docker compose up -d
查看日志:
bash
docker logs -f oxidized
这里有一个经验:
不要以"容器正常运行"作为 Oxidized 部署成功的判断标准。
Oxidized 容器没有退出,只能证明程序启动了。
真正应该关注的是:
text
设备是否被读取
↓
Model 是否正确加载
↓
SSH 是否成功
↓
Prompt 是否识别
↓
配置命令是否成功执行
↓
配置是否写入 Git
后面绝大多数问题,实际上都发生在这条链路上。
三、配置 Oxidized 主配置文件
Oxidized 的主配置通常位于:
text
/root/.config/oxidized/config
一个基础配置大致可以写成:
yaml
---
username: xunjian
password: xunjian
model: vrp
interval: 3600
debug: false
threads: 10
timeout: 20
retries: 3
extensions:
oxidized-web:
load: true
listen: 0.0.0.0
port: 8888
source:
default: csv
csv:
file: "/home/oxidized/.config/oxidized/router.db"
delimiter: !ruby/regexp /:/
map:
name: 0
model: 1
gpg: false
output:
default: git
git:
user: Oxidized
email: oxidized@example.com
repo: "/home/oxidized/.config/oxidized/oxidized.git"
实际使用中最关键的是理解几个组件:
text
source
↓
读取有哪些设备
model
↓
告诉 Oxidized 这是什么设备、执行什么命令
input
↓
通过 SSH / Telnet 等方式连接
output
↓
配置最终保存到哪里
很多问题其实都可以根据这四层快速定位。
四、第一个坑:Huawei 为什么报 ModelNotFound
接下来开始接入华为设备。
最开始容易想当然地在 router.db 中写:
text
192.168.1.10:huawei
然后可能会遇到:
text
ModelNotFound
这是一个很典型的问题。
因为:
router.db里面填写的 model,并不一定等于厂商名称。
对于很多华为交换机、路由器,Oxidized 实际使用的是:
text
vrp
所以应该写成:
text
192.168.1.10:vrp
而不是简单写:
text
192.168.1.10:huawei
这里必须理解 Oxidized 的设计:
text
Huawei
↓
厂商
VRP
↓
网络操作系统 / Oxidized Model
类似:
text
Cisco
↓
ios / iosxe / nxos ...
Huawei
↓
vrp
所以看到:
text
ModelNotFound
优先检查的不是用户名和密码,而是:
text
router.db 中第二列的 model 名
五、第二个坑:自定义 vrp_old.rb 为什么放进去却加载不到
因为部分老设备提示符或命令行为和新版 VRP 不完全一致,我后来尝试建立自定义 Model,例如:
text
vrp_old.rb
但是出现了:
text
文件明明存在
Oxidized 却仍然提示 ModelNotFound
这里踩到的是 Oxidized 的 Model 加载机制。
我甚至进一步查看了:
text
oxidized-0.37.0/lib/oxidized/manager.rb
其中核心逻辑大致可以理解成:
ruby
require File.join dir, file + '.rb'
klass = namespace.constants.find {
|const| const.to_s.casecmp(file).zero?
}
这一段非常关键。
假设文件叫:
text
vrp_old.rb
Oxidized 加载后不仅仅检查:
text
文件是否存在
还会寻找对应的 Ruby Class。
也就是说:
text
文件名
↓
Ruby Class
↓
Oxidized Model 名
三者需要能够正确对应。
所以:
text
vrp_old.rb
里面如果仍然只是:
ruby
class VRP < Oxidized::Model
那么 Oxidized 并不会因为文件叫 vrp_old.rb,就自动产生一个:
text
vrp_old
Model。
这也是为什么:
"把官方 vrp.rb 复制一份改个文件名"
不一定能够工作。
六、自定义 Model 的正确思路
假设需要建立:
text
vrp_old
那么应该建立真正独立的 Model,例如:
ruby
class VRP_OLD < Oxidized::Model
不过这里还要注意 Ruby 常量名称和 Oxidized Loader 对类名的识别方式。
因此实际生产环境中,我更建议:
能通过现有
vrpModel 解决,就尽量不要为了一个 Prompt 问题直接复制 Model。
优先修改:
- Prompt;
- expect;
- cmd;
- pre/post login 行为。
实在存在明显设备代际差异,再建立单独 Model。
否则后期会出现:
text
官方 vrp.rb 升级
↓
自定义 vrp_old.rb 不同步
↓
维护两套甚至多套 Model
维护成本会越来越高。
七、输出使用 Git,而不是简单保存文件
Oxidized 支持多种 Output。
对于配置备份,我比较推荐:
text
Git
例如:
yaml
output:
default: git
git:
user: Oxidized
email: oxidized@example.com
repo: "/root/.config/oxidized/git"
这样每次配置发生变化:
text
Oxidized
↓
重新抓取 Running Config
↓
比较配置
↓
有变化
↓
Git Commit
于是配置备份从:
text
"保存最新配置"
升级成:
text
"保存配置变化历史"
这对于网络运维非常有价值。
比如某天凌晨出现网络故障,可以直接查看:
text
故障前配置
↓
故障后配置
快速判断是否有人:
- 修改 VLAN;
- 修改路由;
- 修改 ACL;
- 修改端口配置;
- 修改链路聚合;
- 修改 SNMP;
- 修改 AAA。
并且支持配置比对

八、Web 页面怎么打开
Oxidized 可以提供 Web/API。
配置中:
yaml
rest: 0.0.0.0:8888
Docker:
yaml
ports:
- "8888:8888"
然后访问:
text
http://服务器IP:8888
即可查看设备状态。
这里我也踩过一个版本相关的坑。
一些旧教程中会把:
yaml
rest:
理解成完整的 Oxidized Web 配置方式。
但较新的 Oxidized 环境中,Web 功能已经逐渐通过:
text
oxidized-web / extensions
方式提供。
因此如果照着几年前的文章配置:
text
rest
然后发现提示 deprecated 或行为与教程不同,不一定是配置写错,而很可能是:
教程和当前 Oxidized 版本不是同一个时代的配置方式。
这也是部署开源软件非常常见的问题。
遇到这类情况,我更建议:
text
先确认当前安装版本
↓
再看当前版本代码 / 文档
↓
最后参考博客
而不是反过来。
九、最终实现效果
经过前面的调整以后,最终形成的配置备份链路是:
text
网络设备
Huawei / Cisco / ...
│
│ SSH
▼
┌──────────────┐
router.db ───▶│ Oxidized │
└──────┬───────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
Git Repo Oxidized Web
│
▼
Configuration History
│
▼
配置变化 / Diff / 回溯
对运维来说,这已经不只是一个"配置备份工具"。
进一步结合监控平台以后,其实还有很多可以扩展的地方。
例如:
text
Zabbix 发现网络设备异常
↓
调用 Oxidized 获取配置历史
↓
判断最近是否发生配置变更
↓
Git Diff 获取变更内容
↓
结合设备告警和拓扑分析
↓
辅助判断是否为配置变更导致
这样 Oxidized 就可以从:
text
配置备份
继续演进成:
text
网络故障分析的数据源
总结
Oxidized 本身是一个相对轻量的工具。
真正部署以后会发现,它的核心难点并不在 Docker,而在于:
text
不同厂商
+
不同设备型号
+
不同 CLI 行为
+
不同 Prompt
+
不同配置命令
之间的适配。
从这次实际部署来看,最值得记住的一条经验就是:
不要围绕错误提示不断试参数,而要把 Oxidized 看成 Source → Model → SSH → Prompt → Command → Output 的完整数据链路。
只要明确问题发生在哪一层,大部分 Oxidized 故障其实都能够比较快地定位。
而在成功接入 Git 以后,它也不再只是"定时登录设备执行 display current-configuration"的脚本,而是逐渐成为网络配置管理、变更审计以及后续自动化故障分析体系中的一个基础数据源。