Oxidized Network Configuration Backup: Install and Configure - 夜莺博客

Oxidized Network Configuration Backup: Install and Configure

Configuration backup is the one automation project that pays for itself the first time a switch dies. Oxidized is the modern RANCID replacement: it logs into every device on a schedule, pulls the running configuration, and stores each revision in a Git repository so you can diff, blame and restore. This guide installs it on Debian/Ubuntu and RHEL-family hosts, explains the config file and inventory, sets up Git output with change notifications, and covers the failures you will actually hit on the first run against a mixed Cisco/Juniper/Aruba estate.

Install

# Debian / Ubuntu
sudo apt update
sudo apt install -y ruby ruby-dev libssl-dev libsqlite3-dev git cmake pkg-config
sudo gem install oxidized oxidized-web

# RHEL / Rocky
sudo dnf install -y epel-release
sudo dnf config-manager --set-enabled crb
sudo dnf module enable ruby:3.1
sudo dnf install -y ruby-devel rubygems openssl-devel libyaml-devel sqlite-devel gcc gcc-c++ make git
sudo gem install oxidized oxidized-web

sudo useradd -r -m -d /var/lib/oxidized -s /bin/bash oxidized
sudo -u oxidized oxidized --version

Never run Oxidized as root: it executes vendor CLI parsing code and stores device credentials. A dedicated service account with a home directory it owns is the baseline. Point it at a consistent home so config, logs and the Git repo stay in one place:

export OXIDIZED_HOME=/etc/oxidized
sudo -u oxidized oxidized      # first run creates a default config
sudo -u oxidized oxidized

How Oxidized Is Put Together

Oxidized answers one question on a loop: "what does device X look like right now, and has it changed since last time?" It is deliberately small, and understanding its four moving parts removes most of the mystery when something does not work.

  • Source - the inventory of devices to poll. CSV (router.db), SQL (SQLite/MySQL/PostgreSQL), HTTP(S) JSON, or a static list. The source defines the device name, the model (which driver to use) and, optionally, per-device credentials.
  • Input - how the config is fetched. SSH, telnet, or vendor-specific transports, with default: ssh, telnet meaning "try SSH first, fall back to telnet". Device credentials can also live here instead of in the inventory.
  • Model - the per-vendor driver that knows the login banner, the enable sequence, the command to print the running configuration, and any cleanup needed to make the output diff-friendly. This is where 95% of "empty file" problems come from.
  • Output - where revisions land. Git is the right default because you get versioning, diffs, blame and a remote push for free; file, HTTP webhook and a handful of other backends exist for specific workflows.

Each poll cycle walks the source, runs the model against the device, and hands the result to the output. If the fetched config is byte-identical to the previous revision, nothing is committed - so a healthy repo shows commits only when a human changed something. That property is what makes Oxidized useful for audit as well as for recovery, and it is worth designing around: a repo with 40 commits per device per day means something is generating noise (timestamps, counters or uptime strings inside the config) and the model needs trimming.

Outputs, Groups and Hooks: Making Backups Usable

Storing configs is the easy half. The other half is making a change visible to a human being the moment it happens, and Oxidized supports that without extra tooling. Groups let you give different platforms different credentials, models and outputs while sharing one process; hooks run a command when a change is committed, which is where notification lives:

# extra config: /etc/oxidized/config
output:
  default: git
  git:
    user: Oxidized
    email: oxidized@example.com
    repo: "/var/lib/oxidized/network.git"

hooks:
  push_to_remote:
    type: exec
    events: [post_store]
    cmd: "cd /var/lib/oxidized/network.git && git push --quiet origin master"
  notify_change:
    type: exec
    events: [post_store]
    cmd: "/usr/local/bin/notify-config-change.sh"

# group-specific overrides
groups:
  cisco:
    username: netbackup
    password: !ruby/object:Oxidized::String 'SuperSecret'
    model: ios
    interval: 1800
  juniper:
    username: netbackup
    password: !ruby/object:Oxidized::String 'SuperSecret'
    model: junos
    interval: 3600

The post_store event fires after a commit, which is exactly when you want a Slack or email message: it means "somebody changed a switch and here is the diff". Pushing to a remote Git host on every commit gives you an off-box copy that survives the backup server dying - the one failure mode people forget until the day it happens. Keep the notification script dumb (read stdin, post to a webhook, exit 0) so a webhook outage never blocks a backup.

Two smaller knobs are worth setting deliberately. rest: 0.0.0.0:8888 gives oxidized-web a dashboard showing last-poll status per device; bind it to a management interface or behind a reverse proxy, never to a public address, and ideally with authentication in front of it. And per-group interval values let you poll backbone routers more often than lab switches, which keeps the load profile predictable on large estates.

The configuration file

---
username: netbackup
password: !ruby/object:Oxidized::String 'SuperSecret'
model: junos
resolve_dns: true
interval: 3600
rest: 0.0.0.0:8888
log: /var/log/oxidized/logs
input:
  default: ssh, telnet
  ssh:
    secure: false
output:
  default: git
  git:
    user: Oxidized
    email: oxidized@example.com
    repo: "/var/lib/oxidized/network.git"
source:
  default: csv
  csv:
    file: "/etc/oxidized/router.db"
    delimiter: !ruby/regexp /:/
    map:
      name: 0
      model: 1
      username: 2
      password: 3

The interval is seconds between polls; one hour is the usual compromise between change visibility and device load. secure: false on SSH is Oxidized's way of permitting legacy key exchange algorithms for older switches - enable it deliberately, and prefer a jump host or out-of-band path over weakening SSH everywhere.

Inventory (router.db)

core-sw1:ios:netbackup:SuperSecret
core-sw2:ios:netbackup:SuperSecret
mx204-edge:junos:netbackup:SuperSecret
aruba-6300:aruba:netbackup:SuperSecret
sw-nxos-1:nxos:netbackup:SuperSecret

Use a per-device map when credentials differ by platform. The model field selects the driver - check the model list for your platforms and version: an incorrect model typically reveals itself as an empty or truncated backup file rather than an error, which is why verification matters more than installation.

Run it as a service and verify real backups

sudo cp /usr/local/lib/ruby/gems/*/gems/oxidized-*/extra/oxidized.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now oxidized
sudo journalctl -u oxidized -f

cd /var/lib/oxidized/network.git
git log --oneline -5
git log -p -- core-sw1

The only meaningful health check is a Git log with recent commits per device. Build a quick report instead of trusting the service state:

for d in $(git --git-dir=/var/lib/oxidized/network.git log --name-only --pretty=format: | sort -u | grep -v '^$'); do
  age=$(( ( $(date +%s) - $(git --git-dir=/var/lib/oxidized/network.git log -1 --format=%ct -- "$d") ) / 3600 ))
  [ "$age" -gt 24 ] && echo "STALE: $d (${age}h)"
done

Common first-run failures

Auth works in SSH but Oxidized fails: the account needs privilege and terminal length settings; on Cisco add terminal length 0 in the driver, and on ArubaOS-CX check that the account can enter enable mode. Empty files: model mismatch or an enable password prompt the driver does not answer. Only some devices update: inventory entries with the same name but different case, or DNS resolving to a stale address. No commits despite changes: the Git repo is owned by root after a manual run. Pair this with change-aware tooling - see our guides on NAPALM config diff, Ansible inventory and backups and Netmiko scripting if you want push-based automation next to pull-based backup.

Restoring a Configuration from a Backup

The point of all this is the day a switch needs to be rebuilt. Because the output is a plain Git repository, recovery is ordinary Git plus ordinary CLI work - no proprietary restore tool, no vendor-specific format:

# find the last known-good revision for one device
git clone /var/lib/oxidized/network.git
cd network
git log --oneline -- core-sw1 | head
git show 4f2a1c9:core-sw1 > /tmp/core-sw1-good.txt

# compare what changed between two revisions
git diff 4f2a1c9 HEAD -- core-sw1

# hand the config to the device (example: Cisco IOS via console/TFTP or scp)
scp /tmp/core-sw1-good.txt netadmin@core-sw1:/flash/restore.txt
# on the device: copy flash:restore.txt running-config

Practise this before you need it. A restore that has never been tested is a hypothesis, not a backup, and the first attempt usually reveals practical problems: the running config contains interface descriptions with quotes that need escaping, the device refuses a config block that references a VRF that no longer exists, or the file includes a boot system line pointing at an image that is not on the replacement hardware. Reading the diff and staging the change with configure replace (IOS) or a commit-based session (Arista/Junos) keeps you in control. On platforms that support transactional configuration, prefer the rollback-capable path over pasting text into a live console.

Every so often, verify the whole estate rather than a single device: check that every entry in router.db has a commit within the last 24 hours, and that the config stored actually contains a device-identifying line (hostname, system host-name). A backup that belongs to the wrong device is worse than no backup at all, and it happens more often than anyone admits when inventory copies an entry and forgets to change the hostname field.

Security and Operational Notes

Oxidized holds a credential that can read the configuration of every device in the estate, which makes it a high-value target. Treat it accordingly:

  • Dedicated, least-privilege accounts. A read-only account that can run show running-config and nothing else. Where the platform supports it, use a command-account or aaa authorization profile to enforce that, and never reuse a personal admin login.
  • Keep credentials out of world-readable files. /etc/oxidized/config should be mode 0600 and owned by the service account. The !ruby/object:Oxidized::String wrapper exists so Ruby does not dump the password into logs or the REST API; keep using it.
  • Protect the repository. The Git repo contains the same secrets as the devices themselves, so encrypt or tightly permission the backup host, and make sure the push target is not a public repo. A leaked config archive is a leaked credential set.
  • Watch the process, not just the service. systemctl status oxidized returning "active" only means the daemon is alive. The health signal is the age of the newest commit per device - build a stale-device check into your monitoring so a silently failing driver surfaces the same day.
  • Plan for device password rotation. When the estate's credentials change, update the inventory (or the group) and restart Oxidized; a driver that keeps failing authentication will otherwise retry forever and hide the real problem in the log.

Combined with change-aware tooling, this turns configuration backup from a compliance ritual into an operational feedback loop: a Git commit tells you what changed, a diff tells you what it does, and a stale-device alert tells you when the tool itself has stopped working. If you want push-based automation alongside pull-based backup, the same inventory data feeds your playbooks - see Ansible facts collection and config backup and the ios_config module deep dive for the playbook side, and pyATS and Genie if you would rather diff structured state than raw text.

Why Not Just Use RANCID?

RANCID still runs, and there is nothing wrong with it if it already covers your platform list. The reason to move is mostly ergonomics: Oxidized is a single Ruby daemon with a YAML config instead of a Perl script tree with a large cloginrc-style router.db and per-vendor expectation scripts; it has a REST API and a web UI for status; its models are smaller and easier to read when you need to add a platform; and its default output is Git rather than a directory of timestamped files, which makes diff, blame and remote push standard rather than bespoke. Both tools poll over SSH and both need per-platform drivers, so the migration is mostly a matter of porting the inventory and validating that each device's first poll produces a non-empty, correctly identified config.

Backing up through a jump host or an out-of-band path

Most estates cannot reach every device directly from the backup server: management VRFs, out-of-band networks and bastion requirements all get in the way. Rather than weakening SSH globally with secure: false, put the routing and the legacy algorithms in one per-service SSH config file, where the weakening is explicit and auditable.

# /var/lib/oxidized/.ssh/config - used by any driver that speaks net-ssh
Host core-sw* aruba-* mx204-*
  User netbackup
  ProxyJump bastion.example.com
  StrictHostKeyChecking accept-new
  KexAlgorithms +diffie-hellman-group14-sha1
  HostKeyAlgorithms +ssh-rsa

sudo -u oxidized ssh core-sw1 "show version | include uptime"

That single file solves three first-run problems at once: it reaches devices behind a jump host, it enables the legacy key exchange and host-key algorithms that older switches still demand, and it keeps the decision visible to whoever reviews the host. Prefer it over loosening the global SSH input, and prefer an out-of-band path over both whenever one exists.

Scaling past a file-based inventory

router.db is perfectly adequate to a few hundred devices. Beyond that, keep the inventory in the system of record and let Oxidized read it, so nobody edits two lists and forgets one.

source:
  default: sql
  sql:
    adapter: sqlite
    database: "/etc/oxidized/devices.sqlite3"
    table: devices
    map:
      name: hostname
      model: model
      group: group

The group column is the useful part: it lets one process apply different models, credentials, intervals and even different outputs per platform, so backbone routers can be polled every thirty minutes while lab switches are polled once a day. Stagger those intervals deliberately - a flat one-hour interval across a large estate means every device is contacted in the same minute, which is a device-load and authentication-rate problem you create for yourself. Whether the inventory lives in a file or a database, the health signal stays the same: a commit per device inside the expected interval. Push-based automation can consume the same table, as described in Ansible facts collection and config backup and Netmiko scripting.

原文链接:https://github.com/ytti/oxidized