How do I keep network diagrams from going stale?
Diagrams go stale because updating them is a separate task from making the change. Generate the diagram from collected state, commit it, and have CI regenerate and compare it, so a drifted diagram fails a check instead of waiting for someone to notice.
- Set up a collect, generate, compare loop that reports drift on its own.
- Make generated diagram output deterministic so the comparison means something.
- Decide what a drift report should do, and what it should never do.
Diagrams go stale because updating the diagram is a separate task from making the change. Generate the diagram from collected state instead of updating it by hand, commit the generated file, and have a scheduled job regenerate and compare it, so drift reports itself.
This note is the operational half of diagrams as code. The generation half, turning neighbor output into a picture, is in generating a diagram from LLDP output.
Why the usual fixes do not hold
These three fixes get tried the most, and each one fails the same way.
A documentation step in the change process. It works while someone is watching. The first emergency change at 2am skips it, and after that the diagram is wrong, so the next person stops trusting it. Once nobody trusts it, nobody bothers to update it either.
A quarterly documentation review. This finds drift three months late and produces a large, tedious correction that one person does under protest. In between reviews the diagram is exactly as wrong as it was before.
One owner for the diagram. This works until that person is on vacation, changes teams, or leaves. It also concentrates the knowledge, so everyone else stops looking at the picture.
The pattern underneath all three is the same. The drawing is downstream of the change and connected to it by human memory, so under pressure the documentation step is the one that gets dropped.
The loop
Four steps, and the only one anybody runs by hand is the first setup.
- Collect neighbor state from the devices into structured data.
- Generate the diagram file from that data with code.
- Commit the generated file next to the configs it describes.
- Compare a fresh generation against the committed file on a schedule.
Without step 4 you have a diagram that was accurate the day someone ran the script.
Making the output diffable
The comparison only works if the same network produces the same file every time. Skip this step and the check fails every night for no reason, until somebody turns it off.
Sets and dictionaries do not promise you an order. Neither does the order your devices answered. Sort everything you iterate:
def to_mermaid(links):
"""Deterministic output: same input, same file, every run."""
lines = ["flowchart TD"]
# Devices in sorted order, not first-seen or collection order.
devices = sorted({d for link in links for d in (link["local"], link["remote"])})
for device in devices:
lines.append(f' {node_id(device)}["{device}"]')
# Links sorted by their canonical key, so re-cabling moves one line.
for link in sorted(links, key=link_key):
label = f'{link["local_intf"]} to {link["remote_intf"]}'
lines.append(
f' {node_id(link["local"])} ---|"{label}"| '
f'{node_id(link["remote"])}'
)
return "\n".join(lines) + "\n"
Sorting devices alphabetically rather than first-seen means adding one switch inserts one line instead of reordering the file. The trailing newline keeps every diff from showing a change on the last line.
The scheduled comparison
The job collects, generates into a temporary file, and compares against what is committed:
#!/usr/bin/env bash
set -euo pipefail
python collect.py --inventory inventory.yaml --out /tmp/neighbors.json
python generate_diagram.py /tmp/neighbors.json --out /tmp/topology.mmd
if ! diff -u topology.mmd /tmp/topology.mmd; then
echo "Topology drift: the network no longer matches topology.mmd"
exit 1
fi
echo "topology.mmd matches the network"
The diff -u output names the link that appeared or disappeared. That is the
detail you need when you are chasing down a recabling, instead of guessing
which link moved.
Run it nightly. Nightly catches the changes made outside your change process, which is the drift you have no other way to see. Running it every hour finds the same things and trains people to ignore a red check.
What a drift report should do
A drift report should state the difference and stop there. An automatic commit of the regenerated diagram makes the check green and hides the fact that the network changed without a change record. The picture stays accurate, but nobody can tell why it changed unless a person reads the diff.
The report has two possible causes and they need different responses:
- Somebody recabled and the diagram does not reflect it yet. The diagram is doing its job by telling you.
- Somebody edited
topology.mmdby hand instead of regenerating it. In a generated file that edit is a bug in itself, because the next run overwrites it.
The second one is worth a comment at the top of the generated file saying it is generated and where from.
Where the comparison belongs
Two separate jobs, because they answer different questions.
On a pull request that changes the topology source, regenerate and require that the committed diagram matches. This is a blocking check and it is fair, because whoever changed the topology can fix the diagram before merging.
On a schedule against the live network, run the drift comparison and open an issue or post the diff to the channel where the network team already reads things. This one does not block anybody’s merge. A developer fixing a typo in a README should not be stopped by a switch somebody recabled last night.
Mixing these is the most common way this ends up disabled. A blocking check that can fail for reasons unrelated to your change gets removed, and it takes the useful check with it.
What this costs
Setting this up costs three real things.
You need credentials that can read neighbor state from every device, stored
somewhere a scheduled job can use them. That is a real security decision, and
read-only accounts scoped to show commands are the version worth arguing for.
You need the collection to handle devices being unreachable without producing a diff. A switch down for maintenance should not read as a removed link. Skipping unreachable devices and reporting them separately is the usual answer, and it means your comparison covers the devices that answered rather than all of them.
And somebody has to read the drift report. If nobody does, this becomes a scheduled job that has been failing for six weeks, which leaves you with the stale diagram you started with and a red check nobody looks at.
Where to go from here
The generation side is in generating a diagram from LLDP output, and you can try the parse against your own output in the network diagram generator without an account.
The course lesson on versioning the source of truth in git is what makes the diff-in-a-pull-request part work, and the free Mermaid lesson has the graded version of the generator this loop runs.