A 480-Router Push, a Broken Regex, and a CI/CD Proposal
A retail chain has 480 branch routers (IOS-XE 17.9) managed from hand-edited templates. Last quarter a bulk change to add an NTP server pushed to all 480 in a single Ansible run. Sixty routers in one region lost management access, because the playbook's lineinfile replaced the ntp server line and also matched the ntp server vrf Mgmt line, which broke the management VRF path. Recovery took nine hours of manual console work. They now want CI/CD. The team is four engineers; two know Python, none have used Git beyond cloning. Routers are reachable only through a jump host. The change window is Sunday 02:00–05:00, and the business requires that no more than one region (80 routers maximum) be affected by a bad change.
1. Design the pipeline with the blast-radius requirement as the primary constraint. Explain each gate and why it catches this specific failure.
Read the worked answer
Design backwards from "80 routers maximum":
- Source of truth in Git, reviewed. Configuration generated from templates and intended-state data, not edited per device. Every change is a pull request with a second reviewer. This is what would have caught the regex: a reviewer sees
lineinfileagainst a file with twontp servervariants and asks the question. - Render, don't patch. Replace
lineinfilewith a structured module (ios_configwith explicit parents/lines) or a data model rendered to a full config.lineinfileis text substitution on a moving target;ios_configis idempotent and reports exactly what it would change. The specific bug - a pattern matching bothntp serverandntp server vrf Mgmt- is a class of bug, not an accident. - Dry run with diff, mandatory.
--check --diffagainst the canary set, with the diff reviewed by a human. Output is attached to the change record. - Canary stage: 1 router in a lab, 2 routers in a non-critical region. Wait the full soak period (e.g. 30 minutes) and run post-checks.
- Regional batches with hard failure limits.
serial: 10,max_fail_percentage: 0(or 5) so a single failure halts the run rather than continuing to 480. Region is a variable, so a run is scoped to one region and the blast radius is structurally capped at 80. - On-device safety net. Wrap the change in the platform's configuration rollback (
configure terminal revert timer 10, witharchive/configuration replacefor the baseline). If the device is not confirmed reachable after the change, it self-reverts. This is what turns a nine-hour console recovery into a ten-minute timer. - Post-change verification that proves reachability, not just task success.
Why this catches the incident: the diff gate and the render-not-patch change would each have caught it independently, and the revert timer would have limited the damage to one batch even if both failed.
2. What does the pipeline verify after a change, and why is 'task ok' not evidence?
Read the worked answer
'ok' means Ansible sent the commands and the CLI accepted them. It does not mean the device is doing what you intended, or that it is still reachable in 30 minutes.
For the NTP change specifically, verification means:
- Reachability from the management network, tested from the orchestrator through the actual path (jump host, management VRF) - not from the device to itself.
- Intended state present: the NTP server entries are exactly the expected set for that device's role, with no duplicate or unintended
ntp server vrf Mgmtline. Compare the rendered config against the running config, not against the playbook's return value. - Functional state:
show ntp statusshows synchronized and a plausible stratum, andshow ntp associationsshows the expected peer. A device can hold a correct config and still be unsynchronised because the source is unreachable. - No collateral damage: routes in the management VRF present, default gateway intact, OSPF/BGP neighbours up if applicable.
- Config diff snapshot archived per device, so the next change has a known-good baseline.
Design rules that follow:
- Verification is code with assertions, not a human reading output - otherwise it does not run at 03:00 on batch four.
- Verify against intent (data model), not against the previous config, or drift is carried forward silently.
- Every check must fail the pipeline and trigger rollback. A warning that nobody reads is not a gate.
Judgement: the difference between a script and an automation system is whether it can tell you that the change failed. Task return codes cannot.
3. The four-person team has limited Python and Git experience and a three-hour window. What do you build first, and what do you deliberately not build?
Read the worked answer
Build the boring path first; resist the platform.
Build now:
- Git with a fixed workflow - main protected, one branch per change, one reviewer, a PR template that asks "what is the rollback and what is the blast radius". This costs a day of training and prevents the largest class of incidents.
- Ansible, not custom Python. Two of the four can support it; the other two can read a playbook in an afternoon. Custom Python raises the ramp cost and concentrates knowledge in two people - a bus-factor problem in a 480-device estate.
- Serial batches, canary, revert timer, automated post-checks. These are configuration, not development, and they deliver most of the risk reduction.
- Secrets handling that works through a jump host - a vault, credentials never in the repo, and an SSH path through the jump host tested as part of the pipeline rather than assumed.
- A dry-run-only pipeline for the first month.
--checkin CI, human applies. Gets the team used to diffs before anything writes.
Deliberately not yet:
- A custom orchestration framework, an event-driven fabric, or full model-driven telemetry migration. Those need skills and time the team does not have, and they do not address the failure mode that actually occurred.
- Automating a process that is still changing. If the NTP/VLAN template is still being negotiated, freeze it first - automating churn just makes churn faster.
The real sequencing judgement: the incident was not caused by a lack of automation tooling. It was caused by an unreviewed, unverifiable text substitution applied without limits. A spreadsheet plus Git plus a revert timer would have prevented it. Tool sophistication is not the constraint; review and rollback are.