Docker部署Oxidized实现网络设备配置自动备份
1. 创建目录(部署路径/home/docker/oxidized)
mkdir -p /home/docker/oxidized
mkdir -p /home/docker/oxidized/config
mkdir -p /home/docker/oxidized/config/model
mkdir -p /home/docker/oxidized/ssh
mkdir -p /home/docker/oxidized/config/output
mkdir -p /home/docker/oxidized/config/output/configs2. 创建docker-compose.yml
services:
oxidized:
image: oxidized/oxidized:latest
container_name: oxidized
restart: unless-stopped
ports:
- "8888:8888" # Web UI and REST API
volumes:
- ./config:/home/oxidized/.config/oxidized
- ./ssh:/home/oxidized/.ssh
environment:
- TZ=Asia/Shanghai
- CONFIG_RELOAD_INTERVAL=600
networks:
- oxidized-net
networks:
oxidized-net:
driver: bridge3. 首次启动(生成默认配置后停止容器)
cd /home/docker/oxidized
docker compose up -d
# 提取内置VRP模型,后面需要修改
docker cp oxidized:/var/lib/gems/3.3.0/gems/oxidized-0.37.0/lib/oxidized/model/vrp.rb ./config/model/vrp.rb
docker compose down4. 主配置config
创建主配置文件config
cat > /home/docker/oxidized/config/config << 'EOF'
# config - Main Oxidized configuration file
# Controls how devices are accessed and where configs are stored
# 备份间隔(秒),3600 = 每小时一轮
interval: 3600
# 日志:同时输出到控制台和文件
logger:
appenders:
- type: stdout
- type: file
file: /home/oxidized/.config/oxidized/oxidized.log
# Debug 模式(生产环境设为 false)
debug: false
# 并发连接数,同时备份 10 台设备
threads: 10
# 单次操作超时(秒),如等待提示符、执行命令
# 默认 20 秒,部分设备响应慢需调大
timeout: 600
# 整个备份任务的总时限(秒),超过即报 Job timelimit reached
# 默认 300 秒,飞塔等配置较大的设备需调大
timelimit: 600
# 失败重试次数,0 = 不重试
retries: 1
# 全局默认账号密码,router.db 未指定时使用
username: backup_user
password: backup_password
# 提示符检测:由模型自动识别
# Most devices are detected automatically by the model
# 设备清单:从 router.db 读取
source:
default: csv
csv:
file: /home/oxidized/.config/oxidized/router.db
delimiter: !ruby/regexp /:/
map:
name: 0 # 设备名称
ip: 1 # 管理 IP
model: 2 # 设备模型(vrp / fortios)
username: 3 # 登录账号
password: 4 # 登录密码
group: 5 # 分组名称,用于 Web 界面分类展示
gpg: false
# 连接方式:先 SSH 后 Telnet 自动降级
input:
default: ssh, telnet
debug: false
telnet:
secure: false
timeout: 15 # Telnet TCP 连接超时(秒)
ssh:
secure: false
timeout: 15 # SSH TCP 连接超时(秒)
utf8_encoded: true
# 备份存储:本地文件
output:
default: file
file:
directory: /home/oxidized/.config/oxidized/output/configs
# Web 管理界面,浏览器访问 http://服务器IP:8888
extensions:
oxidized-web:
load: true
listen: 0.0.0.0
port: 8888
# 模型别名(方便 router.db 用简写)
model_map:
cisco: ios
juniper: junos
arista: eos
huawei: vrp
hw: vrp
EOF5. 设备清单router.db
格式:设备名称:IP:模型:账号:密码:分组
创建设备清单文件router.db
cat > /home/docker/oxidized/config/router.db << 'EOF'
交换机01_SW01:10.1.1.1:vrp:admin:password:交换机
防火墙01_FW01:10.1.1.2:fortios:admin:password:防火墙
EOF- 模型名写vrp(华为/H3C/海康通用)或fortios(飞塔)
- 分组只用于Web界面分类,不影响连接方式
- 账号密码留空则使用全局默认值,中间冒号不能省
6. 自定义VRP模型
模型文件已在“步骤3”中提取到./config/model/vrp.rb。按如下方式手动修改三处:
修改1:兼容Login:和Username:两种登录提示
找到:username /^Username:$/
改为:username /^([Ll]ogin|[Uu]sername):\s*$/
修改2:兼容Password:后带空格
找到:password /^Password:$/
改为:password /^Password:\s*$/
修改3:去掉all参数(部分设备不支持)
找到:cmd 'display current-configuration all' do |cfg|
改为:cmd 'display current-configuration' do |cfg|
7. 启动并验证
启动
cd /home/docker/oxidized
docker compose up -d验证模型语法docker exec oxidized ruby -c /home/oxidized/.config/oxidized/model/vrp.rb
正常应输出Syntax OK
Web界面
浏览器打开http://服务器IP:8888,可按分组查看设备,查看配置备份情况,手动触发单台备份
8. 问题处理
登录提示不统一
现象:无法正常登录交换机获取配置,telnet的debug日志卡在登录提示符“Login:”
原因:登录提示不统一,华为提示Username:,H3C和海康OEM提示Login:,且冒号后有空格,内置模型只匹配Username:`
解决:已在步骤“6. 自定义VRP模型”中解决
screen-length 0 temporary不被部分设备支持
现象:交换机读取配置卡住,未能完整加载配置信息,telnet的debug日志如下
received ""
received ""
received "Login: "
sent cmd "admin\r\n"
received "admin\r\n"
received "Password: "
sent cmd "password\r\n"
received "\n"
received "******************************************************************************\r\n* Copyright (c) 2004-2022 New H3C Technologies Co., Ltd. All rights reserved.*\r\n* Without the owner's prior written consent, *\r\n* no decompiling or reverse-engineering shall be allowed. *\r\n******************************************************************************\r\n\r\n<BJSW10-2F-XinShiYanShi>"
sent cmd "screen-length 0"
received "scre"
received "en-length"
received " 0"
received "\r\n ^\r\n % Unrecognized command found at '^' position.\r\n<BJSW10-2F-XinShiYanShi>"
sent cmd "display version"
received "display version\r\n"
received "H3C Comware Software, Version 7.1.070, Release 6343P05\r\nCopyright (c) 2004-2022 New H3C Technologies Co., Ltd. All rights reserved.\r\n"
received "H3C S5120V3-52P-LI uptime is 77 weeks, 2 days, 0 hours, 17 minutes\r\n"
received "Last reboot reason : Cold reboot\r\n"
received "\r\n"
received "Boot image: flash:/s5120v3_li-cmw710-boot-r6343p05.bin\r\nBoot image version: 7.1.070, Release 6343P05\r\n Compiled Aug 26 2022 11:00:00\r\nSystem image: flash:/s5120v3_li-cmw710-system-r6343p05.bin\r\nSystem image version: 7.1.070, Release 6343P05\r\n Compiled Aug 26 2022 11:00:00\r\n"
received "\r\n"
received "\r\nSlot 1:\r\nUptime is 77 weeks,2 days,0 hours,17 minutes\r\nS5120V3-52P-LI with 1 Processor\r\nBOARD TYPE: S5120V3-52P-LI\r\nDRAM: 512M bytes\r\nFLASH: 256M bytes\r\nPCB 1 Version: VER.B\r\nBootrom Version: 146\r\nCPLD 1 Version: 001\r\nRelease Version: H3C S5120V3-52P-LI-6343P05\r\n---- More ----"原因:H3C和海康设备不支持screen-length 0命令(正确命令是screen-length disable)
解决:登录每台设备,永久关闭分页:
user-interface vty 0 4
screen-length 0主机名含空格
现象:某交换机主机名含空格(SW-04_JiFang 5700S),VRP提示符正则[\w.-]+不匹配空格,报“PromptUndetect”
解决:修改主机名,去掉空格
9. 最终文件清单
/home/docker/oxidized/
├── docker-compose.yml
└── config/
├── config # 主配置文件(手动创建)
├── router.db # 设备清单(手动创建)
├── oxidized.log # 运行日志(自动生成)
├── crash # 崩溃日志(自动生成)
├── pid # 进程PID文件(自动生成)
├── model/
│ └── vrp.rb # 自定义VRP模型(手动创建)
└── output/ # 备份文件存放目录(自动生成)
└── configs/ # 备份文件存放目录(手动创建)10. 打包Oxidized配置并备份到远程FTP
Shell脚本
#!/bin/bash
#===============================================================================
# oxidized_backup.sh
# 功能: 打包 oxidized config 目录,按日期命名,本地留存 + lftp 上传远程 FTP
#===============================================================================
set -euo pipefail
#==================== 配置区(按需修改)====================
SOURCE_DIR="/home/docker/oxidized/config"
BACKUP_DIR="/home/Backup"
RETENTION_DAYS=15
# ---------- 远程 FTP 配置 ----------
FTP_HOST="192.168.1.1"
FTP_PORT="21"
FTP_USER="username" # ← 改成实际用户名
FTP_PASS="password" # ← 改成实际密码
FTP_PATH="/Backup/网络设备/自动备份/oxidized"
# FTP 连接模式: "passive" 或 "active"
FTP_MODE="passive"
# SSL/TLS 模式: "none" | "explicit" (FTPES/显式TLS) | "implicit" (FTPS/隐式TLS)
FTP_SSL="none"
#==================== 脚本逻辑 ====================
DATE_STAMP=$(date +%Y%m%d)
ARCHIVE_NAME="oxidized_config_${DATE_STAMP}.tar.gz"
ARCHIVE_PATH="${BACKUP_DIR}/${ARCHIVE_NAME}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
#------------------- 1. 准备本地目录 -------------------
mkdir -p "${BACKUP_DIR}"
if [ ! -d "${SOURCE_DIR}" ]; then
log "ERROR: 源目录不存在: ${SOURCE_DIR}"
exit 1
fi
#------------------- 2. 打包 -------------------
log "开始打包: ${SOURCE_DIR} -> ${ARCHIVE_PATH}"
tar -czf "${ARCHIVE_PATH}" -C "$(dirname "${SOURCE_DIR}")" "$(basename "${SOURCE_DIR}")"
log "打包完成: ${ARCHIVE_PATH} ($(du -h "${ARCHIVE_PATH}" | cut -f1))"
#------------------- 3. 上传 FTP -------------------
log "开始上传到 ${FTP_HOST}:${FTP_PORT}${FTP_PATH}"
# 根据 SSL 模式确定协议前缀
case "${FTP_SSL}" in
implicit) PROTO="ftps" ;; # 隐式 TLS,端口通常是 990
explicit) PROTO="ftp" ;; # 显式 TLS (FTPES),端口默认 21
none|*) PROTO="ftp" ;;
esac
# 构建 lftp 的 set 命令
LFTP_SETS=""
if [ "${FTP_MODE}" = "passive" ]; then
LFTP_SETS="${LFTP_SETS}set ftp:passive-mode yes;"
else
LFTP_SETS="${LFTP_SETS}set ftp:passive-mode no;"
fi
case "${FTP_SSL}" in
none)
LFTP_SETS="${LFTP_SETS}set ftp:ssl-allow no;"
;;
explicit)
LFTP_SETS="${LFTP_SETS}set ftp:ssl-allow yes;"
LFTP_SETS="${LFTP_SETS}set ftp:ssl-force yes;"
LFTP_SETS="${LFTP_SETS}set ftp:ssl-protect-data yes;"
LFTP_SETS="${LFTP_SETS}set ssl:verify-certificate no;"
;;
implicit)
LFTP_SETS="${LFTP_SETS}set ftp:ssl-allow yes;"
LFTP_SETS="${LFTP_SETS}set ftp:ssl-force yes;"
LFTP_SETS="${LFTP_SETS}set ftp:ssl-protect-data yes;"
LFTP_SETS="${LFTP_SETS}set ssl:verify-certificate no;"
;;
esac
# 先用一条简单的 open 命令测试连接
log "测试 FTP 连接..."
CONNECTION_OK=$(lftp -c "
${LFTP_SETS}
set net:timeout 15
set net:max-retries 1
open -u '${FTP_USER}','${FTP_PASS}' -p ${FTP_PORT} ${PROTO}://${FTP_HOST}
ls
bye
" 2>&1)
if echo "${CONNECTION_OK}" | grep -qiE "login failed|not connected|connection refused|no route|timeout|cannot|denied"; then
log "ERROR: FTP 连接失败!服务器返回:"
echo "${CONNECTION_OK}"
log ""
log "============ 排查建议 ============"
log "1. 检查 IP/端口是否正确: telnet ${FTP_HOST} ${FTP_PORT}"
log "2. 检查用户名密码: lftp -u ${FTP_USER},${FTP_PASS} -p ${FTP_PORT} ${PROTO}://${FTP_HOST}"
log "3. 尝试切换 SSL 模式: FTP_SSL=\"explicit\" 或 \"implicit\""
log "4. 尝试切换连接模式: FTP_MODE=\"active\""
log "5. 检查防火墙是否放行 FTP 数据端口"
log "=================================="
exit 1
fi
log "FTP 连接成功,开始上传..."
# 实际上传
lftp -c "
${LFTP_SETS}
set net:timeout 30
set net:max-retries 3
set net:reconnect-interval-base 10
open -u '${FTP_USER}','${FTP_PASS}' -p ${FTP_PORT} ${PROTO}://${FTP_HOST}
cd '${FTP_PATH}' || mkdir -p '${FTP_PATH}' && cd '${FTP_PATH}'
put -c '${ARCHIVE_PATH}' -o '${ARCHIVE_NAME}'
bye
"
if [ $? -eq 0 ]; then
log "上传成功: ${FTP_PATH}/${ARCHIVE_NAME}"
else
log "ERROR: 上传失败"
exit 1
fi
#------------------- 4. 清理本地过期备份 -------------------
if [ "${RETENTION_DAYS}" -gt 0 ]; then
log "清理 ${RETENTION_DAYS} 天前的本地备份..."
find "${BACKUP_DIR}" -name "oxidized_config_*.tar.gz" -type f -mtime +${RETENTION_DAYS} -print -delete | while read -r f; do
log "已删除: ${f}"
done
fi
log "全部完成!"