Why would you automate a network, and what would you automate first?
Say this first
Automation gives me consistency, speed and a record of every change. The same template produces the same
config on 500 switches, a change that took a week of SSH sessions takes minutes, and every change is
reviewed in Git before it runs. I start with read-only work (config backups, inventory, compliance checks),
then move to small, repetitive, low-risk changes such as VLANs, NTP, SNMP and interface descriptions.
Go deeper
What it fixes
Human error. Typing the same 40 lines on 200 devices guarantees a typo somewhere. A template does not get tired.
Drift. Hand-made changes slowly make every device a little different. Automation lets you compare
every device against one intended state.
Speed and scale. The team stops being the bottleneck for routine requests.
Audit and compliance. Changes are in Git with an author, a reviewer and a ticket number.
Faster troubleshooting. A script collects show output from 300 devices in a minute during an incident.
A sensible order
Read-only. Nightly backups, inventory, "which devices still run this old image", compliance reports.
Nothing can break, and the team learns the tools.
Standard config. NTP, syslog, SNMP, AAA, banners. Low risk, high repetition.
Service requests. VLANs, port configuration, ACL entries, driven from a source of truth.
Full provisioning and closed loop. Day 0 builds from templates, then continuous checks that the
network still matches intent.
What I would say about risk
Automation also multiplies mistakes. A bad template reaches every device as fast as a good one. So the
process matters as much as the tool: peer review, a dry run with a diff, a canary device first, and a
rollback plan.
They will ask next
When is automation not worth it?
A task done twice a year on two devices, where writing and testing the code costs more than doing it by
hand. I still document it as a runbook. If it starts to repeat or grow, it moves to the backlog.
How do you get a team of CLI engineers to adopt it?
Start with something that saves them time on day one, like a backup or a show-command collector. Keep it
in a shared Git repo, pair on the first changes, and never make automation the only way to fix an
outage at 3 a.m. until people trust it.
Traps
Saying automation means fewer engineers. Interviewers want to hear that it moves engineers to higher-value work.
Starting with large config pushes. Read-only wins come first and build trust.
Explain the management, control and data planes with examples.
Say this first
The data plane forwards transit traffic using tables that are already built: FIB, adjacency, ACLs, QoS,
usually in hardware. The control plane builds those tables: OSPF, BGP, STP, ARP, LACP running on the CPU.
The management plane is how people and tools operate the box: SSH, SNMP, NETCONF, syslog, the API.
Go deeper
Side by side
Plane
Job
Examples
Runs on
Data (forwarding)
Move packets that pass through the device
FIB lookup, MAC table lookup, ACL match, NAT, QoS marking
ASICs or the fast software path
Control
Decide where packets should go and build the tables
OSPF, BGP, IS-IS, STP, ARP, LACP, LLDP
Route processor CPU
Management
Configure and monitor the device
SSH, SNMP, NETCONF, RESTCONF, syslog, NTP
Route processor CPU
Why the split matters
Protection. The CPU is small compared to the ASICs. A flood of traffic aimed at the router itself
(punted traffic) can starve OSPF and BGP. Control plane policing (CoPP) rate limits what reaches the CPU.
SDN. Classic SDN moves the control plane off the box into a central controller. Most modern
controllers keep a distributed control plane on the devices and centralize policy and management instead.
Resilience. With NSF/SSO, the data plane keeps forwarding on the old FIB while the control plane restarts.
Quick test
A ping through R1 is data plane. A ping to R1 is punted to the CPU and answered there. An OSPF
hello is control plane. An SSH login is management plane.
They will ask next
Is NTP control plane or management plane?
Usually classed as management plane: it keeps the device clock right for logs, certificates and
authentication. Some vendors group it with control plane services in their CoPP templates. Say which
view you take and why.
What happens to forwarding if the control plane crashes?
Hardware keeps forwarding with the last FIB it was given until neighbors time out. Without graceful
restart the neighbors drop the adjacency after the dead or hold timer and route around the box. With
NSF/SSO or graceful restart they keep using it while the control plane recovers.
Traps
Calling ARP or STP data plane. They build forwarding state, so they are control plane.
Forgetting that management traffic and control traffic both hit the same CPU, which is why CoPP exists.
What is SDN, and how is what vendors sell today different from the original idea?
Say this first
SDN separates the decision making about traffic from the devices that forward it and puts that logic in
software with open APIs. The original idea (OpenFlow, around 2008) moved the whole control plane to a
central controller that programmed dumb switches flow by flow. Today's products, such as Cisco ACI,
SD-Access and SD-WAN, keep routing protocols on the devices and centralize policy, automation and
visibility in a controller you drive through a REST API.
Go deeper
Three flavors you will hear about
Open SDN. A controller programs flow tables in switches with OpenFlow. Logic is fully central.
SDN through APIs. Devices keep their own control plane. A controller or script configures them through
NETCONF, RESTCONF, gNMI or vendor APIs.
Overlay SDN. A virtual network runs on top of an IP underlay with VXLAN or similar tunnels.
Hypervisor switches or leaf VTEPs build the tunnels, and the physical network just routes IP.
What a modern controller still centralizes
One place to declare policy (who may talk to whom).
Automation of device config from that policy.
A network-wide view for assurance and troubleshooting.
A northbound REST API so other systems can drive the network.
What stays distributed
In ACI the fabric still runs IS-IS in the underlay, COOP on the spines for endpoint mapping and MP-BGP for
external routes. If every APIC goes down, the fabric keeps forwarding. You just cannot change policy.
They will ask next
Is the controller a single point of failure?
It is a cluster in any real design. APIC runs as a cluster of at least three in production, and
Catalyst Center can run as a three-node cluster. In most designs the data plane keeps forwarding when
the controller is down. What you lose is the ability to change and monitor things centrally.
What is the difference between SDN and network automation?
Automation is using code to configure and check devices, often device by device through SSH or APIs.
SDN is an architecture where a controller owns the network-wide state and devices take their policy
from it. You can automate without SDN, and every SDN product depends on automation.
Traps
Saying SDN means OpenFlow. OpenFlow is one southbound protocol; most SDN products today do not use it.
Claiming the controller forwards traffic. In nearly all products it never sits in the data path.
What is the difference between northbound and southbound APIs?
Say this first
Northbound APIs face up, from the controller to applications, orchestrators and scripts. They are usually
REST with JSON and speak in business terms like sites, policies and devices. Southbound APIs face down,
from the controller to the network devices: OpenFlow, NETCONF, RESTCONF, gNMI, SNMP, or plain SSH and CLI.
Policy pushed to the Nexus 9000 fabric; Cisco describes OpFlex as the policy protocol
OpenDaylight, ONOS
REST APIs
OpenFlow, NETCONF, BGP-LS, PCEP
East-west
Some people also name an east-west interface: controllers talking to each other in a cluster or between
domains, for example to sync state or federate policy.
They will ask next
Why does the northbound API matter more to an automation team than the southbound one?
The northbound API is what you code against. The controller hides the southbound details, so you send
one request to create a VLAN pool for a site instead of logging in to every switch. When you choose a
controller, the quality and stability of its northbound API decides how much you can automate.
Traps
Mixing up the directions. North is toward apps and people, south is toward devices.
REST is an architectural style, not a protocol. A RESTful API exposes resources at URIs and works on them
with standard HTTP methods, sends representations such as JSON, and is stateless: every request carries
everything the server needs, including authentication. Responses use HTTP status codes and can be cached
when marked cacheable.
Go deeper
The constraints (Roy Fielding, 2000)
Client-server. The client and server evolve separately.
Stateless. No session context is kept between requests. Each call carries its token or credentials.
Cacheable. Responses say whether they can be cached.
Uniform interface. Resources named by URIs, changed through representations, self-descriptive messages.
Layered system. Proxies and load balancers can sit in between without the client knowing.
Code on demand is optional and rare in network APIs.
Anatomy of a request
Output
GET /dna/intent/api/v1/network-device?hostname=SW1 HTTP/1.1Host: catc.example.comAccept: application/jsonX-Auth-Token: eyJ0eXAiOiJKV1Qi...
Method, path (resource), query string (filter), headers, and for POST, PUT and PATCH a body.
Anatomy of a response
Status line (HTTP/1.1 200 OK), headers such as Content-Type: application/json, and a body.
They will ask next
What is the difference between the Accept and Content-Type headers?
Accept says which formats the client wants back. Content-Type says what format the body of this
message is in. A RESTCONF PATCH, for example, sends Content-Type: application/yang-data+json and
often Accept: application/yang-data+json.
Path parameter or query parameter?
The path names the resource (/devices/42). The query string filters, sorts or pages a collection
(/devices?site=dc1&limit=50).
Traps
Saying REST means JSON. REST works with any representation; JSON is just the common one.
Thinking stateless means the server stores nothing. It stores data; it does not keep per-client session state between calls.
Walk me through the HTTP methods. Which ones are idempotent, and why does that matter for automation?
Say this first
GET reads, POST creates or triggers an action, PUT creates or replaces the whole resource at a URI, PATCH
changes part of it, DELETE removes it. GET, PUT and DELETE are idempotent: sending the same request twice
leaves the server in the same state as sending it once. POST is not, and PATCH is not guaranteed to be.
That matters because a script that times out can safely retry an idempotent call.
Go deeper
The table
Method
CRUD
Safe
Idempotent
Typical success
GET
Read
Yes
Yes
200
POST
Create, or run an action
No
No
201 (created) or 200
PUT
Create or replace
No
Yes
201 (created) or 200/204 (replaced)
PATCH
Partial update
No
Not guaranteed
200 or 204
DELETE
Delete
No
Yes
204 or 200
HEAD
Read headers only
Yes
Yes
200
Safe means it does not change state. Idempotent means repeating it has the same effect on the
server as doing it once. The response can differ: a second DELETE may return 404, but the resource is
still gone, so DELETE is idempotent.
The network trap with PUT
PUT replaces the target. If you PUT an interface with only a new description, a RESTCONF server replaces
that whole interface entry with what you sent, and leaves you without the IP address you left out.
Use PATCH (merge) to change one field.
Why automation cares
Networks are unreliable. If a POST times out you do not know whether the object was created, and a retry
may create a duplicate. With PUT you can retry until it succeeds. The same idea is why Ansible modules are
written to be idempotent.
They will ask next
How would you make a POST safe to retry?
Check first (GET by a natural key such as name) and create only if missing, or use an API that supports
an idempotency key or a client-chosen ID. Some APIs let you PUT to a known URI instead.
Which methods does RESTCONF use for what?
GET to read, POST to create a child resource or invoke an RPC, PUT to create or replace, PATCH to merge,
DELETE to remove. POST of something that already exists returns 409 Conflict.
Traps
Saying DELETE is not idempotent because the second call returns 404. Idempotency is about server state.
Using PUT for a one-field change and silently wiping the rest of the object.
What do the common HTTP status codes mean when you call a network API?
Say this first
2xx is success: 200 OK with a body, 201 Created, 204 No Content (success, no body). 4xx is the client's
fault: 400 bad request body, 401 not authenticated, 403 authenticated but not allowed, 404 no such
resource, 409 conflict such as the object already exists. 5xx is the server's fault: 500 internal error,
503 unavailable. My scripts retry 429 and 503 with backoff, re-authenticate on 401, and never blindly retry other 4xx.
Go deeper
The codes to know cold
Code
Meaning
Typical network API cause
200 OK
Success with a body
GET returned data
201 Created
New resource made
POST or PUT created an object; Location header points to it
204 No Content
Success, nothing to return
RESTCONF PUT, PATCH or DELETE that worked
400 Bad Request
Malformed or invalid request
Bad JSON, wrong field name, missing module prefix, value out of range
401 Unauthorized
Not authenticated
Missing, wrong or expired token or password
403 Forbidden
Authenticated, not allowed
Read-only account tries a change; RBAC denies it
404 Not Found
No such resource
Wrong URI, typo in the model path, object not configured
405 Method Not Allowed
Method not supported here
PUT on a read-only resource
409 Conflict
Clashes with current state
POST of an object that already exists; datastore locked
Errors come back with a body that holds error-type, error-tag and error-message. For example
data-exists and lock-denied both map to 409.
They will ask next
What is the real difference between 401 and 403?
401 means "I do not know who you are": the credentials are missing or bad, and retrying with valid
ones can work. 403 means "I know who you are and the answer is no": new credentials for the same user will not help.
Your script gets 500 on one device and 200 on the rest. What do you do?
Log the full response body, because the server usually says what failed. Retry once in case it was
transient. If it repeats, check the device's API process and logs; on IOS-XE,
show platform software yang-management process shows whether the programmability processes are running.
Traps
Swapping 401 and 403.
Treating every non-200 as failure. 201 and 204 are success.
Compare JSON, YAML and XML. Where do you meet each one in network automation?
Say this first
All three describe the same kinds of structured data: objects or mappings, lists and values. JSON is
compact and strict, with no comments; it is the default for REST APIs, RESTCONF and gNMI. YAML is the
human-friendly one, with indentation and comments; Ansible playbooks, inventories, CI pipelines and data
files use it. XML is verbose but has namespaces and schemas; NETCONF uses it, and Junos can show any
config or command output as XML.
YAML 1.2 is a superset of JSON, so a JSON document is almost always valid YAML.
They will ask next
What YAML mistakes have bitten you?
Tabs. YAML indentation must be spaces.
Version numbers: version: 1.10 loads as the float 1.1. Quote it.
YAML 1.1 parsers such as PyYAML (which Ansible uses) read yes, no, on and off as booleans,
so a country code NO becomes false.
The same parsers read an unquoted 22:30 as a base-60 integer (1350), which also hits some MAC-like strings.
Leading zeros: 010 can load as an octal number. Quote anything that is really a string.
Why does NETCONF use XML and not JSON?
NETCONF predates RESTCONF and JSON's popularity, and XML namespaces let one document mix data from
several YANG modules without name clashes. RESTCONF added JSON later and solved the same problem with
module-name: prefixes on member names (RFC 7951).
Traps
Calling YAML a programming language. It is a data format; logic lives in Ansible, Python or Jinja2.
Using yaml.load without a safe loader on untrusted input. Use yaml.safe_load.
What is YANG, and what are containers, lists and leafs?
Say this first
YANG is a data modeling language (RFC 6020, version 1.1 in RFC 7950). It defines the structure, types and
rules of configuration and state data, so a client knows exactly what a device accepts. It is not a
protocol and not an encoding: NETCONF, RESTCONF and gNMI carry YANG-modeled data as XML, JSON or protobuf.
A container groups nodes, a list holds entries identified by a key, a leaf holds one typed value and a
leaf-list holds several values.
Go deeper
A small model
Output
module example-vlans { namespace "urn:example:vlans"; prefix exv; container vlans { list vlan { key "id"; leaf id { type uint16 { range "1..4094"; } } leaf name { type string; } leaf-list tagged-ports { type string; } } }}
The building blocks
Statement
What it is
module
The file-level unit, with a namespace and prefix
container
A grouping node with no value of its own (interfaces)
list
Repeating entries, each identified by key (interface keyed by name)
leaf
One value with a type (description, mtu)
leaf-list
Many values of one type (DNS servers)
typedef, grouping, uses
Reusable types and blocks
augment
Adds nodes into another module's tree (how vendors extend IETF models)
config false
Marks operational state (counters, oper-status), read-only
rpc, action, notification
Operations and events
Why engineers care
The model tells you the path and type before you send anything. The device validates input against it,
so id: 5000 fails cleanly instead of half applying. And the same model works over NETCONF, RESTCONF and gNMI.
They will ask next
What is the difference between config and state data?
Config data is what you set (config true, the default): description, IP address. State data is what
the device reports (config false): counters, oper-status. You read both with NETCONF get, but only
config with get-config, and you can only write config.
Container or list, how do you tell from a path?
A list needs a key to pick one entry: interfaces/interface=GigabitEthernet1 in RESTCONF,
interface[name=GigabitEthernet1] in a gNMI path, interface[name='GigabitEthernet1'] in XPath.
A container is just a name in the path.
Traps
Saying YANG is XML. YANG has its own syntax; XML and JSON are encodings of data that follows the model.
Confusing YANG with SNMP MIBs. Both are models, but MIBs (SMIv2) are mostly read-only and tied to SNMP.
NETCONF (RFC 6241) is a protocol for installing, changing and reading device configuration. It runs as an
SSH subsystem on TCP port 830 (RFC 6242), encodes everything in XML, and uses RPCs such as get,
get-config, edit-config, lock, commit and close-session. Both sides open with a hello that
lists capabilities. Its big advantages over CLI are structured data, validation against YANG and
transactions with a candidate datastore.
get-config returns config only, from the datastore you name (running, candidate, startup). get
returns config plus state data from the running system. Both accept a subtree or XPath filter.
The reply carries the same message-id and the data inside <data>.
Framing
With base:1.0, each message ends with ]]>]]>. When both sides announce base:1.1, they switch to chunked
framing, which cannot be broken by that string appearing inside data.
Turning it on
Cisco IOS
R1(config)# netconf-yang
Junos
set system services netconf ssh
Quick test from a shell: ssh -p 830 netops@10.0.0.1 -s netconf prints the server hello.
They will ask next
Why is NETCONF better than pushing CLI over SSH?
The device validates the whole change against the model and either accepts or rejects it with a
structured error. With a candidate datastore the change is a transaction: all of it or none. And the
data you read back is structured, so no screen scraping.
What does message-id do?
The client sets it on each <rpc> and the server copies it to the <rpc-reply>, so the client can
match replies to requests.
Traps
Saying NETCONF runs on port 22. The standard port is 830; some platforms also allow the SSH subsystem on 22.
Saying NETCONF is the data model. YANG is the model; NETCONF is the protocol.
Ansible is agentless. On servers it copies Python modules over SSH and runs them on the host, but network
devices cannot run Python, so network modules run on the control node and talk to the device through a
connection plugin: network_cli for SSH CLI, netconf, or httpapi for REST-style APIs. An inventory
lists the devices and their variables, a playbook holds plays, and each play runs tasks that call modules
from collections such as cisco.ios or arista.eos.
Go deeper
The pieces
Inventory: hosts and groups, in INI or YAML, static or dynamic (for example from NetBox).
Variables:group_vars/ and host_vars/, plus secrets in Ansible Vault.
Playbook: a YAML list of plays. A play targets hosts and runs tasks in order.
Modules: do the work, ideally idempotently. Network modules live in collections.
Roles: reusable bundles of tasks, templates and defaults.
- name: Collect versions hosts: ios gather_facts: false tasks: - name: Run show version cisco.ios.ios_command: commands: - show version register: ver - name: Print the first line ansible.builtin.debug: msg: "{{ ver.stdout_lines[0][0] }}"
gather_facts: false is common in network plays; call cisco.ios.ios_facts when you need facts.
They will ask next
What does ansible_network_os do?
It tells the connection plugin which platform it is talking to, so it loads the right terminal and
cliconf plugins: how to spot the prompt, disable paging, enter enable and config mode.
How many devices does Ansible work on at once?
forks defaults to 5. Raise it in ansible.cfg for big runs, and use serial in the play to roll a
change out in batches.
Traps
Saying Ansible installs an agent on the switch. Network modules run on the control node.
Using ios_command to push configuration. It runs commands; it does not manage config idempotently.
What does idempotent mean in Ansible, and how do check mode and diff help on network devices?
Say this first
Idempotent means running the playbook again changes nothing if the device is already right: the first
run reports changed, the second reports ok with changed=0. Modules get there by reading the current
state, comparing it with the desired state and sending only the difference. --check runs without
changing anything and reports what would change; --diff shows the before and after. Together they are
my dry run before every change.
Run it in the change ticket and in CI. A playbook that shows changed in check mode on an unchanged
device has an idempotency bug.
They will ask next
Can every module run in check mode?
Only modules that declare check mode support. Others are skipped in check mode, and the documentation
says which. Network config modules and resource modules generally support it.
How do you save the config only when something changed?
ios_config has save_when: modified, which writes memory only if the running config differs from
startup. You can also notify a handler from changed tasks and save once at the end.
Traps
Writing no shutdown with ios_config and wondering why every run reports changed.
Treating changed=0 as proof of correct config. It only shows Ansible saw no difference in the lines it manages.
What is Netmiko, and how does a basic Netmiko script work?
Say this first
Netmiko is a Python library built on Paramiko that makes SSH to network devices easy. It knows each
platform's prompts, disables paging, enters enable and config mode, and waits for output. You build a
device dictionary with a device_type such as cisco_ios, open ConnectHandler, then call
send_command for show commands and send_config_set for changes. It is screen scraping: reliable
and universal, but you get text back unless you parse it.
In Netmiko 4, send_command waits up to read_timeout (10 seconds by default) for the prompt. A big
show running-config on a core switch can need read_timeout=60.
They will ask next
Netmiko or NAPALM or Nornir?
Netmiko is the SSH layer: any command on almost any platform. NAPALM adds a vendor-neutral API with
structured getters and config replace or merge with diffs. Nornir is a Python framework that runs
tasks (Netmiko, NAPALM, others) across an inventory in parallel. They are often used together.
How do you turn CLI output into data?
TextFSM templates (the ntc-templates collection ships hundreds) through use_textfsm=True, Cisco
Genie parsers through use_genie=True, or TTP templates. Better still, ask for structured output where
the platform offers it, such as | json on NX-OS and EOS or | display json on Junos.
Traps
Calling Netmiko an API. It types commands into an SSH session like a person would.
Parsing output with hand-written string splits that break on the next software release.
How do you use Jinja2 templates to generate network configs?
Say this first
Jinja2 separates data from config. The template holds the config text with placeholders; YAML or the
source of truth holds the values per device. {{ var }} prints a value, {% for %} and {% if %}
add loops and conditions, and filters such as upper, default or join transform values. The same
template renders the same structure for every device, which is where consistency comes from.
Go deeper
Template
Output
hostname {{ hostname }}{% for server in ntp_servers %}ntp server {{ server }}{% endfor %}{% if snmp_location is defined %}snmp-server location {{ snmp_location }}{% endif %}
Data
YAML
hostname: ASW1ntp_servers: - 10.0.0.5 - 10.0.0.6snmp_location: Building A floor 2
hostname ASW1ntp server 10.0.0.5ntp server 10.0.0.6snmp-server location Building A floor 2
Things that trip people
Whitespace. Without trim_blocks and lstrip_blocks, every {% %} line leaves a blank line or
indentation. You can also trim per tag with {%- and -%}.
Undefined variables. The default prints an empty string, which can produce ntp server with no
address. StrictUndefined makes it fail instead. Ansible fails on undefined variables by default.
Logic creep. Keep templates simple and push decisions into the data.
In Ansible, the ansible.builtin.template module renders a .j2 file, and modules such as ios_config
accept rendered config through src.
They will ask next
How would you test a template?
Render it for a set of sample devices in CI and compare with known-good output files, and render with
StrictUndefined so missing data fails the build. For extra safety, feed the rendered configs to
Batfish or push them to a lab device.
Traps
Putting device-specific values inside the template instead of the data.
Forgetting whitespace control and shipping configs full of blank lines and stray spaces.
What is a source of truth, and how does NetBox fit into automation?
Say this first
A source of truth is the one authoritative place that says what the network should look like: devices,
interfaces, IP addresses, VLANs, circuits. Automation reads intent from it, renders config and pushes it,
so the network follows the data instead of the data trying to describe the network. NetBox is the common
open source choice: DCIM plus IPAM with a REST and GraphQL API, a Python client (pynetbox) and an Ansible
collection that includes a dynamic inventory.
IPAM: VRFs, prefixes, IP addresses, VLANs and VLAN groups, ASNs, route targets.
Circuits and providers, virtualization, tenancy.
Extensibility: custom fields, tags, config contexts (JSON data merged by site, role or platform),
config templates (Jinja2), webhooks and event rules, plugins.
Reading it from Python
Python
import osimport pynetboxnb = pynetbox.api("https://netbox.example.com", token=os.environ["NETBOX_TOKEN"])for dev in nb.dcim.devices.filter(site="dc1", status="active"): print(dev.name, dev.primary_ip4)
The token comes from the NETBOX_TOKEN environment variable.
Intended vs actual
NetBox holds intended state. The devices hold actual state. Automation turns one into the other, and
drift checks compare them. NetBox is not a monitoring system and does not discover the network by itself.
They will ask next
What happens if engineers keep making changes by CLI and never update NetBox?
The source of truth stops being true, and the next automated push overwrites their fixes or fails.
The rule has to be "change the data, not the device", backed by drift reports that catch CLI changes
so they get added to NetBox or reverted.
Can you have more than one source of truth?
One authoritative source per kind of data. NetBox for IPAM and inventory, ISE for identity policy, Git
for templates and playbooks. Problems start when two systems both claim the same data.
Traps
Calling a config backup repository a source of truth. Backups record what is, not what should be.
Everything that defines the network (templates, variables, playbooks, intended configs) lives in a Git
repo. Each change is a short-lived branch, opened as a pull or merge request that runs automated checks and
gets a peer review. Merging to main means "approved", and the pipeline deploys main. Git gives history,
a diff of every change, who made it and why, and an easy way to revert.
Then open a merge request. CI lints, renders and dry-runs the change, a reviewer approves, and it merges.
Commands worth knowing
Command
Use
git status, git diff
What changed and is not committed yet
git log --oneline
Short history
git blame file
Who last changed each line
git revert <commit>
New commit that undoes an old one, safe on shared branches
git pull
Fetch and merge the latest from the remote
What goes in, what stays out
In: templates, data, playbooks, scripts, tests, pipeline files. Out: secrets (use Vault), generated
files you can rebuild, and huge binary images. Config backups usually live in a separate repo.
They will ask next
Why git revert and not git reset to undo a bad change?
revert adds a new commit, so history stays intact and everyone's copy stays valid. reset rewrites
history, which breaks other people's clones when the branch is shared.
Merge or rebase?
Rebasing your own feature branch onto main before the merge gives a clean, linear history. Never rebase
a branch other people already pulled. Many teams squash-merge short change branches into main.
Traps
Long-lived branches per engineer that drift away from main and conflict at merge time.
Committing passwords. Git history keeps them even after deletion.
How does OpenFlow actually work, and why did it not take over the network?
Say this first
An OpenFlow switch holds flow tables. Each entry has match fields, a priority, counters, instructions or
actions and timeouts. The controller connects to the switch over TCP 6653 (optionally TLS), installs
entries with Flow-Mod messages, and gets packets that miss every entry through Packet-In. It stayed niche
because switch TCAM is small, reactive flow setup does not scale, and operators preferred proven
distributed protocols with central policy on top.
Go deeper
The pieces
Flow entry. Match on ingress port, MACs, EtherType, VLAN, IP addresses, protocol, L4 ports (OpenFlow
1.0 had 12 match fields; later versions added many more). Highest priority match wins.
Actions. Output to a port, drop, rewrite a header field, push or pop a VLAN tag, send to the controller.
Table-miss. A priority 0 entry that drops or sends to the controller.
Messages. Hello, Features Request and Reply, Flow-Mod, Packet-In, Packet-Out, Port-Status, Echo.
Reactive vs proactive
Reactive. The first packet of a new flow goes to the controller, which installs an entry. Flexible,
but every new flow costs a round trip, and the controller becomes the bottleneck.
Proactive. The controller installs entries in advance from known topology and policy. Scales far
better and survives a controller outage.
Versions
OpenFlow 1.1 and later added multiple tables in a pipeline and group tables. 1.3 added meters and became
the most widely implemented version. The old controller port was 6633; IANA later assigned 6653.
Where it lived on
Open vSwitch in hypervisors and OpenStack, Google's B4 inter-datacenter WAN, and research. Its ideas moved
into P4, programmable pipelines and controller-based fabrics.
It does not, by itself. A controller application (for example a learning switch app) sees the
Packet-In, records the source MAC and port, and installs entries. All the "intelligence" lives in the
controller apps.
What happens to traffic if the controller dies?
Existing entries keep forwarding until their idle or hard timeouts expire. New flows that need the
controller fail. OpenFlow defines fail-secure mode (keep using the table, drop what misses) and
fail-standalone mode (fall back to normal switching), and which one you get depends on the switch.
Traps
Saying OpenFlow is a routing protocol. It programs forwarding state; it does not compute paths.
Quoting only 6633. Know that 6653 is the assigned port.
Compare Cisco Catalyst Center and ACI APIC. What does each controller manage?
Say this first
Catalyst Center (formerly DNA Center) manages campus and branch: design, provisioning, software images,
plug-and-play, SD-Access fabrics and assurance, exposed through the Intent API. APIC manages an ACI data
center fabric of Nexus 9000 switches with a policy object model (tenants, VRFs, bridge domains, EPGs,
contracts). Everything in the APIC GUI is a REST call against that model.
Go deeper
Catalyst Center
Design: sites, buildings, floors, IP pools, global settings (DNS, NTP, AAA).
Policy: group-based policy with ISE and SGTs for SD-Access.
Provision: templates, PnP zero-touch onboarding, SWIM for image upgrades.
Assurance: health scores, client and device telemetry, issue detection.
SD-Access fabric: LISP control plane, VXLAN data plane, TrustSec (SGT) policy plane.
API: get a token from POST /dna/system/api/v1/auth/token with basic auth, then send it in the
X-Auth-Token header, for example GET /dna/intent/api/v1/network-device.
ACI APIC
Fabric: spine-leaf Nexus 9000 in ACI mode, APIC cluster (three or more in production).
Object model: tenant contains VRFs, bridge domains and application profiles with EPGs. Contracts
allow traffic between EPGs. With no contract, EPGs cannot talk (allow-list model).
Internals: IS-IS underlay, VXLAN overlay, COOP on spines for endpoint lookup, MP-BGP to distribute
external routes.
API:POST /api/aaaLogin.json returns a token (also set as the APIC-cookie cookie). Query a class
with GET /api/class/fvTenant.json or one object with GET /api/mo/uni/tn-Prod.json. The GUI's API
Inspector shows the exact call behind every click.
An endpoint group is a set of endpoints that share policy, for example all web servers. ACI forwards
inside an EPG freely but blocks traffic between EPGs unless a contract allows it. That allow-list model
is how ACI does segmentation without per-switch ACLs.
How would you onboard 50 new branch switches with Catalyst Center?
Build the site hierarchy and settings, attach a day 0 template and an image to the site, claim the
devices in PnP (by serial number), and let them come up on a DHCP scope that points them to Catalyst
Center (DHCP option 43 or a DNS name such as pnpserver). Then check them in assurance.
Traps
Calling ACI "just a GUI". The point is the object model and API; the GUI is one client of it.
Saying Catalyst Center forwards traffic or is in the data path. It never is.
What is intent-based networking, and how is it different from plain automation?
Say this first
In intent-based networking you state the outcome you want ("contractors can reach only the internet"),
and the system translates it into device config, pushes it, and then keeps checking with telemetry that
the outcome is still true. Plain automation runs the steps you wrote. IBN adds the translation from
business terms and the closed loop of continuous assurance.
Go deeper
The loop
Translation. Business or service intent becomes a network policy and then device config.
Activation. The controller pushes config to every device involved.
Assurance. Telemetry and tests confirm the network behaves as intended, all the time.
Remediation. When reality drifts from intent, the system alerts or corrects.
Products that use the term
Cisco Catalyst Center with SD-Access: group-based policies between user and device groups,
assurance on top.
Juniper Apstra: intent for data center fabrics, with continuous validation of the built fabric
against the blueprint.
Cisco ACI: the policy model (EPGs and contracts) is a form of declared intent.
Honest view
The term is marketing-heavy. The useful part is real: declare the desired state, generate config from it,
and verify continuously instead of trusting that a push worked.
They will ask next
How would you explain intent to a non-technical manager?
Today we tell every switch exactly what to do. With intent we tell the system what we want, such as
"guests only reach the internet", and it works out the switch settings and keeps checking that it stays true.
Traps
Describing IBN as "a GUI that pushes templates". Without assurance there is no closed loop and no intent check.
How do network APIs authenticate you, and how do you handle tokens in a script?
Say this first
Three patterns cover most of it. Basic auth sends a base64 user and password on every call, so it is only
acceptable over TLS. Token APIs (Catalyst Center, APIC) take a login call once and return a short-lived
token you send in a header. API keys and OAuth 2.0 bearer tokens (Meraki, cloud platforms) go in the
Authorization header. In scripts I keep credentials out of the code, verify TLS, and handle a 401 by
getting a fresh token and retrying once.
Go deeper
Examples
Platform
How you log in
How you send it after
IOS-XE RESTCONF
Basic auth on every request
Authorization: Basic ...
Catalyst Center
POST /dna/system/api/v1/auth/token with basic auth, returns {"Token": "..."}
X-Auth-Token header
ACI APIC
POST /api/aaaLogin.json with user and password in JSON
APIC-cookie cookie; refresh with /api/aaaRefresh.json
Credentials come from environment variables, Ansible Vault or a secrets manager, never from the repo.
verify=False is a lab shortcut. In production point verify at the company CA bundle.
Tokens expire. A long job must refresh or re-authenticate on 401 instead of failing halfway.
Use a dedicated service account with only the rights the job needs, so the AAA logs show what the automation did.
They will ask next
Is basic auth insecure?
Base64 is encoding, not encryption, so anyone who sees the request sees the password. Over HTTPS the
whole request is encrypted, which makes it acceptable for device APIs. The bigger risk is the password
living in scripts, so store it properly and rotate it.
Why do controllers prefer tokens over sending the password every time?
The password crosses the wire once. The token is short-lived, can be revoked, and limits damage if it
leaks. It also saves the controller from checking the password against AAA on every call.
Traps
Hard-coding a password in a script and committing it to Git. History keeps it even after you delete it.
How do you read a YANG model and find the path you need for a change?
Say this first
I turn the module into a tree with pyang -f tree, find the node, and read its flags: rw is config,
ro is state, * marks a list or leaf-list, ? an optional node, and the list key is in brackets.
To see real data I pull the device's config through NETCONF or RESTCONF and match it to the tree. Cisco
YANG Suite or similar tools do the same in a GUI.
The device itself (NETCONF get-schema, RFC 6022), the vendor's published model set for that release,
and the public YangModels collection that holds IETF, OpenConfig and vendor models by release.
They will ask next
How do you find which model holds a feature you only know by its CLI?
Configure it on a lab device by CLI, then pull the config through NETCONF or RESTCONF and see where it
appears. On Junos, show configuration ... | display xml shows the XML path directly. Then confirm the
node and its type in the tree.
Traps
Writing to a ro node. State data cannot be configured, and the device will reject it.
Forgetting to URL-encode a key with a slash. GigabitEthernet1/0/1 must be GigabitEthernet1%2F0%2F1 in a RESTCONF URI.
What is the difference between native, OpenConfig and IETF YANG models? Which do you use?
Say this first
IETF models are standards (ietf-interfaces, ietf-ip, ietf-routing) and are vendor neutral but cover a
limited set of features. OpenConfig models are written by operators such as Google and Microsoft, are
vendor neutral, and pair config with operational state for streaming telemetry. Native models come from
the vendor (Cisco-IOS-XE-native, the Junos configuration schema) and cover every feature, but only on
that platform. I use OpenConfig or IETF where coverage is good and fall back to native for the gaps.
IOS-XE also ships -oper models for state, for example Cisco-IOS-XE-interfaces-oper and
Cisco-IOS-XE-process-cpu-oper. IOS XR splits native models into -cfg and -oper.
The practical rule
A multi-vendor shop gets the most from OpenConfig, especially for telemetry. But vendors implement
OpenConfig at different depths and publish deviations, so test each model on each platform and release
before you build on it.
They will ask next
Why would a mostly Cisco shop still use native models?
Coverage and fidelity. Native models map one to one to the CLI the team already knows, include every
feature, and behave predictably on that OS. The cost is that the code is not portable.
Traps
Assuming "OpenConfig supported" means every leaf works. Check the vendor's deviations for your release.
Explain the running and candidate datastores, commit, confirmed commit and the edit-config operations.
Say this first
Running is the live config. Candidate (the :candidate capability) is a scratch copy: you edit it, check
it, then commit copies it to running in one step, or discard-changes throws it away. A confirmed commit
applies the change but rolls it back automatically unless a second commit arrives within the timeout
(600 seconds by default). Inside edit-config, the operation attribute decides merge, replace, create,
delete or remove for each node.
Go deeper
Datastores
Datastore
Capability
Notes
running
always
Live config. Writable directly only with :writable-running
candidate
:candidate
Edit, validate, then commit or discard-changes
startup
:startup
Config loaded at boot, where the platform separates it
edit-config operations
Operation
Behavior
merge (default)
Merge the sent data into the target
replace
Replace the target node with the sent data
create
Create; error data-exists if it is already there
delete
Delete; error data-missing if it is not there
remove
Delete if present, no error if absent
default-operation (merge, replace or none) sets the behavior for nodes without an attribute.
error-option is stop-on-error (default), continue-on-error or rollback-on-error (needs
the :rollback-on-error capability).
<commit><confirmed/><confirm-timeout>120</confirm-timeout></commit> applies the candidate and starts a
timer. If your session drops or nobody sends a plain <commit/> before the timer ends, the device
reverts. That is the safety net for changes that might cut your own access. Junos users know the same
idea as commit confirmed.
Platform notes
IOS-XE needs netconf-yang feature candidate-datastore to offer a candidate. Junos maps NETCONF
candidate to its normal candidate configuration.
They will ask next
Why lock the datastore?
lock stops other NETCONF sessions (and on many platforms CLI users) from changing the datastore while
you work, so your commit does not include someone else's half-finished edit. Another session that
tries to lock it gets lock-denied, and its edits are refused until you release it. Always unlock
or close the session.
Merge or replace for a full interface config push?
Replace makes the node match exactly what you sent, which removes anything you left out. That is what
you want for intended-state management, and dangerous if your data is incomplete. Merge is safer but
leaves stale config behind.
Traps
Thinking delete and remove are the same. delete errors when the node is missing; remove does not.
Editing running directly on a platform that has a candidate, and losing the all-or-nothing behavior.
How does RESTCONF work? Show me a request and explain the URI.
Say this first
RESTCONF (RFC 8040) exposes YANG-modeled data over HTTPS with REST methods. The data tree lives under
/restconf/data; the URI is the model path, starting with module-name:node, with list keys after an
equals sign. Bodies use application/yang-data+json or application/yang-data+xml. GET reads, POST
creates, PUT creates or replaces, PATCH merges, DELETE removes. Each request is its own transaction:
there is no candidate, lock or confirmed commit like in NETCONF.
Note the module: prefix on the top member and wherever a node comes from another module (ietf-ip:ipv4).
Useful query parameters
depth=2 limits how deep the reply goes, content=config or content=nonconfig picks config or state,
fields=name;description returns only some leafs, with-defaults controls default values.
They will ask next
How do you change several things atomically with RESTCONF?
Send one PATCH at a common parent that contains all the changes; the server applies one request as a
unit. For edits spread across the tree, YANG Patch (RFC 8072, application/yang-patch+json) lists
several edits in one request. For real multi-step transactions with validate and confirmed commit, use NETCONF.
What status codes does RESTCONF return for a successful PUT?
201 Created if the resource did not exist, 204 No Content if it replaced an existing one. PATCH returns
200 or 204 (most devices send 204), DELETE returns 204, and a POST that creates returns 201.
Traps
Using application/json instead of the YANG media types. Some servers reject it or answer in a different format.
Leaving out the module: prefix on the top-level JSON member, which gives a 400.
Compare NETCONF, RESTCONF and gNMI. When would you pick each?
Say this first
All three carry YANG-modeled data. NETCONF uses SSH on port 830 and XML, and has the strongest
transactions: candidate datastore, lock, validate and confirmed commit. RESTCONF uses HTTPS with JSON or
XML and REST methods, so it is the easiest to call from any tool, but each request is its own transaction.
gNMI uses gRPC over HTTP/2 with protobuf, has an atomic Set, and its Subscribe RPC is the standard for
streaming telemetry. I pick NETCONF for safe config changes, RESTCONF for quick integrations, gNMI for telemetry.
Go deeper
Side by side
NETCONF
RESTCONF
gNMI
Standard
IETF, RFC 6241
IETF, RFC 8040
OpenConfig specification
Transport
SSH, TCP 830 (TLS optional)
HTTPS, usually 443
gRPC over HTTP/2 with TLS; port varies by platform, IANA lists 9339
Encoding
XML
JSON or XML
Protobuf messages; values as JSON, JSON_IETF, proto, bytes or ASCII
Event streams over server-sent events, rarely used
Subscribe: stream, poll, once
Typical models
IETF, native
IETF, native
OpenConfig mostly
Feels like
A config database
A web API
A telemetry and config bus
How I choose
Risky config changes on many devices: NETCONF with candidate and confirmed commit, where supported.
A quick integration, a webhook, a ServiceNow action: RESTCONF, because every language and tool can do HTTPS and JSON.
Monitoring at scale: gNMI Subscribe, often with OpenConfig paths across vendors.
Platform reality: use what the device does well. Some platforms are strongest on one interface.
They will ask next
Can one device run all three at once?
Yes. IOS-XE, IOS XR, Junos, EOS and NX-OS can expose more than one at a time, all backed by the same
configuration database. A change made through one shows up through the others and in the CLI.
Traps
Saying RESTCONF is "NETCONF over HTTP" with the same transaction model. It has no candidate, lock or confirmed commit.
Calling gNMI an IETF standard. It is defined by OpenConfig.
How does gNMI work, and what are its subscription modes?
Say this first
gNMI is a gRPC service with four RPCs: Capabilities (which models and encodings the target supports), Get,
Set (delete, replace and update in one atomic request) and Subscribe. A subscription can be ONCE (send
everything and stop), POLL (send when the client asks) or STREAM. Stream subscriptions are SAMPLE (every
interval), ON_CHANGE (only when a value changes) or TARGET_DEFINED (the device picks per path).
Go deeper
Paths
gNMI paths are XPath-like with keys in brackets and no quotes:
/interfaces/interface[name=Ethernet1]/state/counters/in-octets. An optional origin (often
openconfig) says which model tree the path belongs to.
--insecure is plaintext gRPC for a lab. With TLS use --tls-ca to verify the device certificate, or
--skip-verify to encrypt without checking it.
Which mode for what
Data
Mode
Why
Interface counters, CPU, memory
SAMPLE, 10 to 30 s
Values change constantly
Oper-status, BGP session state, LLDP neighbors
ON_CHANGE
Rare, important changes, sent at once
Inventory, software version
ONCE or POLL
Rarely changes
suppress_redundant and heartbeat_interval let SAMPLE skip unchanged values while still proving
the stream is alive.
Related gRPC services
gNOI for operations (reboot, file transfer, certificates, ping), gRIBI for injecting routes, P4Runtime
for programmable forwarding pipelines.
Enabling it
Arista EOS
management api gnmi transport grpc default
Other platforms have their own commands and default ports; check the release notes.
They will ask next
Why is ON_CHANGE better than polling oper-status every 10 seconds?
You learn about a flap within milliseconds instead of up to 10 seconds later, and you do not send the
same "up" value thousands of times a day. It only works if the platform supports on-change for that path.
What is the difference between dial-in and dial-out?
gNMI Subscribe is dial-in: the collector connects to the device and subscribes. In dial-out the device
is configured to connect to the collector, as with IOS-XE telemetry ietf subscription and gRPC
receivers. Dial-out survives collector restarts without re-subscribing; dial-in keeps all config on the collector side.
Traps
Using SAMPLE every second on thousands of paths and overloading the device CPU and the collector.
What is model-driven telemetry, and why is it replacing SNMP polling?
Say this first
With SNMP the collector polls each device every few minutes, and the device walks MIB tables to answer.
With model-driven telemetry the device pushes YANG-modeled data on a schedule or when it changes, over
gRPC, gNMI or NETCONF. You get seconds instead of minutes, on-change events, structured data, and less CPU
per data point. It does not remove SNMP overnight, because many tools and old devices still depend on it.
update-policy periodic is in centiseconds, so 6000 is every 60 seconds. update-policy on-change
works for paths that support it. Newer releases also offer named receivers, configured separately.
A common collector stack
Telegraf receives the data (the cisco_telemetry_mdt input for Cisco dial-out, the gnmi input for
dial-in), writes it to InfluxDB or Prometheus, and Grafana draws it:
Output
[[inputs.cisco_telemetry_mdt]] transport = "grpc" service_address = ":57000"
Encodings
Compact GPB needs the matching .proto files on the collector. KV-GPB carries field names with the
values, so it is self-describing at the cost of size. JSON is easiest to read and largest.
They will ask next
Would you turn SNMP off once telemetry is in place?
Not straight away. I keep SNMP read-only (v3) for tools that need it and for devices that cannot
stream, move dashboards to telemetry first, and retire polling per device class once the new data is
trusted. Traps stay useful until the same events come from on-change subscriptions or syslog.
Traps
Saying telemetry is "faster SNMP". It is a different model (push, YANG, gRPC), not a tuned poller.
Forgetting that update-policy periodic on IOS-XE is in centiseconds, not seconds.
What are network_cli, netconf and httpapi in Ansible, and how do you set them up?
Say this first
They are connection plugins from ansible.netcommon. network_cli keeps a persistent SSH session and
drives the CLI, used by most ios, nxos and eos modules. netconf opens a NETCONF session on port 830,
used by Junos modules and the generic netconf_config and netconf_get. httpapi talks to a device REST
API such as Arista eAPI or NX-API. You select one with ansible_connection and the platform with
ansible_network_os.
network_cli, netconf and httpapi keep one connection per host open across tasks, instead of
logging in for every task. Two timers matter: the connect timeout and the command timeout, both 30
seconds by default. A slow show tech or a big config needs a larger command timeout.
The old way
Older playbooks used connection: local with a provider dictionary on each task. That pattern is
deprecated; set the connection in inventory variables instead.
Picking one
Need
Plugin
IOS, NX-OS, EOS, IOS XR CLI modules
network_cli
Junos, or any NETCONF device with netconf_config
netconf
EOS eAPI, NX-API, firewalls and appliances with REST APIs
httpapi
They will ask next
A task fails with "command timeout triggered". What do you change?
Raise the persistent command timeout (ansible_command_timeout or [persistent_connection] command_timeout in ansible.cfg) for that task or group, and check that the command really
returns a prompt. A wrong prompt pattern also looks like a timeout.
Traps
Mixing up ansible_network_os values. It must be the full collection name, such as cisco.ios.ios.
What are Ansible network resource modules, and what is the difference between merged, replaced and overridden?
Say this first
Resource modules manage one feature as structured data, for example cisco.ios.ios_vlans or
ios_l3_interfaces, instead of raw CLI lines. The state decides how. merged adds or updates what you
list and leaves the rest. replaced makes each listed item match exactly, removing attributes you left
out. overridden makes the whole resource on the device match your list, which deletes anything not
listed. deleted removes, and gathered, rendered and parsed read or convert without changing anything.
Go deeper
The states
State
Touches the device
Effect
merged
Yes
Add or update listed items; leave others alone
replaced
Yes
Listed items match exactly; unlisted items untouched
overridden
Yes
Whole resource matches the list; unlisted items removed
deleted
Yes
Remove listed items, or all if none listed
gathered
Reads
Return the current config as structured data
rendered
No
Return the CLI commands the config would produce, offline
parsed
No
Turn running-config text you supply into structured data
The result holds before, after and commands. If only VLAN 30 is new, commands is
["vlan 30", "name GUESTS"].
Why they beat ios_config lines
The module understands the feature, so it knows that shutdown is the opposite of no shutdown, removes
attributes correctly in replaced, and stays idempotent without you matching running-config text.
They will ask next
When would you use overridden?
When the source of truth is complete for that resource and the device must match it exactly, such as
the VLAN list on a leaf switch. Run it in check mode first: a missing VLAN in your data deletes a VLAN in production.
How do resource modules help with brownfield onboarding?
gathered pulls today's config as structured data you can store as variables, and parsed does the
same from saved config backups without touching the device. That gives you a starting data model that
matches reality.
Traps
Using overridden with incomplete data. It removes everything you did not list.
Assuming replaced removes unlisted VLANs. It only rewrites the ones you list.
How do you handle credentials and secrets in network automation?
Say this first
Secrets never go into Git in plain text. In Ansible I encrypt them with Ansible Vault or pull them at run
time from a secrets manager such as HashiCorp Vault. In CI they are protected, masked pipeline variables.
Automation uses its own service accounts with least privilege, authenticated through TACACS+ or RADIUS so
every change is logged against that account. If a secret leaks, I rotate it, because Git history keeps it.
A common pattern keeps vault_ variables in an encrypted file and refers to them from normal variables:
ansible_password: "{{ vault_netops_password }}". People can read the variable names in reviews without
seeing the values.
Other places secrets live
Environment variables for scripts, set by the CI system or a wrapper, not hard-coded.
Secrets managers (HashiCorp Vault, CyberArk, cloud key vaults) with short-lived credentials and audit logs.
CI variables marked protected and masked so they only reach protected branches and never print in logs.
Accounts
A separate account per automation system, not a shared admin login.
Read-only accounts for collection jobs (backups, inventory).
AAA accounting on, so the logs show which account changed what.
They will ask next
Someone committed a device password to the repo. What now?
Rotate the password first, everywhere it is used. Then remove it from the repo history (for example
with git filter-repo) and force everyone to re-clone, add a secret scanner to the pipeline, and move
the value into Vault. Deleting the line in a new commit is not enough.
Traps
Thinking a private repo is a safe place for passwords. Access spreads and history is forever.
What does NAPALM give you that Netmiko does not? Explain getters and config replace vs merge.
Say this first
NAPALM is a vendor-neutral Python API over several platforms (core drivers for IOS, IOS XR, NX-OS, EOS and
Junos). Getters such as get_facts, get_interfaces and get_bgp_neighbors return the same structured
dictionaries on every platform. For config it loads a candidate, shows a diff with compare_config, then
commit_config, discard_config or later rollback. Merge adds your lines to the running config; replace
makes the running config exactly equal to the file you load.
Go deeper
Getters
get_facts, get_interfaces, get_interfaces_ip, get_arp_table, get_mac_address_table,
get_lldp_neighbors, get_bgp_neighbors, get_environment, get_config, get_route_to and more.
Not every driver implements every getter; unsupported ones raise NotImplementedError.
Config workflow
Python
from napalm import get_network_driverdriver = get_network_driver("eos")with driver("10.0.0.21", "netops", "secret") as dev: print(dev.get_facts()["os_version"]) dev.load_merge_candidate(config="ntp server 10.0.0.5\n") diff = dev.compare_config() if diff: print(diff) dev.commit_config() else: dev.discard_config()
Merge vs replace
load_merge_candidate
load_replace_candidate
You supply
Only the lines to add or change
The full intended config
Lines on the device you did not mention
Stay
Removed
Good for
Small additive changes
Full config management, drift removal
Risk
Stale config builds up
An incomplete file deletes real config
How it works under the hood
Each driver uses the platform's best mechanism: Junos has a native candidate and commit, EOS uses
configuration sessions, IOS XR has its own commit model. IOS has no candidate, so the driver copies the
candidate file to flash with SCP and uses configure replace for a replace. That is why IOS needs
ip scp server enable for NAPALM config operations.
Validation
compliance_report("validate.yml") compares getter results with expected values in a YAML file, for
example "these BGP peers are up". It is a quick post-check.
They will ask next
Why is replace mode better for automation, and when is it dangerous?
It is the only mode that removes config nobody declared, so it ends drift and makes the device equal to
the source of truth. It is dangerous when the generated file is incomplete: a missing AAA or management
ACL section is deleted on commit. Diff first, and start with a lab device.
Traps
Assuming every getter works on every driver. Check the support matrix for your platforms.
What is Terraform, what is the state file, and where does Terraform fit next to Ansible?
Say this first
Terraform is a declarative infrastructure as code tool. You describe resources in HCL, providers translate
them into API calls, terraform plan shows the difference between your code and reality, and apply
makes it so. The state file maps each resource in the code to the real object ID and lets Terraform know
what it manages. It must be stored remotely with locking, kept private because it can hold secrets, and
never edited by hand. Terraform shines on API-driven platforms (cloud, ACI, firewall managers); Ansible
is stronger for configuring CLI-based devices.
terraform init # download providers, set up the backendterraform plan -out tfplan # compare code, state and realityterraform apply tfplan # apply exactly that planterraform state list # resources Terraform manages
State rules
Remote backend (S3, Azure Storage, GCS, HCP Terraform, GitLab) with locking, so two people cannot apply at once.
Treat it as sensitive: attributes, including some passwords, are stored in plain text.
Change it only with terraform state mv, state rm and import (or import blocks).
If someone changes a managed object by hand, the next plan shows the drift and offers to put it back.
Terraform vs Ansible
Terraform
Ansible
Style
Declarative, whole desired state
Ordered tasks, each module declarative
State
State file
No state file; reads the device each run
Strength
APIs: cloud, controllers, firewall managers
Device config over SSH, NETCONF, APIs
Deletes
Removing code destroys the resource
Removing a task removes nothing
Since version 1.6 Terraform uses the Business Source License; OpenTofu is the open source fork with the same workflow.
They will ask next
What happens if you delete the state file?
Terraform forgets that it manages anything. The next plan wants to create everything again, which
either duplicates objects or fails on name clashes. You recover by restoring the state from backup or
importing each resource.
Traps
Committing terraform.tfstate to Git. It holds secrets and causes conflicting copies.
Thinking removing a resource block is harmless. Terraform destroys the real object on the next apply.
Two vendors both support the same OpenConfig model, yet your code works on one and fails on the other. Why?
Say this first
Support is declared per leaf, not per model. Vendors publish deviation modules that mark nodes as not
supported or change their type or default, they implement only some optional features, and they support
different model revisions. I read what the device announces (NETCONF hello capabilities or the YANG
library, gNMI Capabilities) and test against that, per platform and per software release.
Go deeper
Where the differences hide
Deviations. A vendor module that says, for example:Output
Other forms are deviate add, replace and delete to change defaults, types or constraints.
Features.if-feature makes nodes optional. The server lists the features it implements.
Revisions. The same module name with a different revision can rename or move nodes.
Semantics. Two implementations can accept the same leaf but apply it differently, for example when
an interface needs a sub-interface 0 before an IP address is valid.
How the device tells you
NETCONF 1.0-style servers list each module in the hello as a capability URI:
A deviations= parameter, when present, names the vendor deviation modules. Newer servers point to the
YANG library (ietf-yang-library, RFC 7895 and RFC 8525), which lists modules, revisions, features and
deviations as data you can query. gNMI returns supported models in the Capabilities response.
How I protect the code
Pin the platform and release in the test lab to the ones in production.
Gather capabilities as part of pre-checks and fail fast if a required module or revision is missing.
Keep per-platform translation in one place instead of scattering if vendor == through the code.
They will ask next
Can you just ignore deviations and send the data anyway?
The device rejects nodes it does not support, usually with an unknown-element or invalid-value
error, and a NETCONF edit with the default stop-on-error stops at that point. On a candidate you can
discard, but on running some changes may already be in. Plan for it rather than discover it in production.
Traps
Testing on one release and deploying to another. Model revisions change between releases.
Walk me through a full NETCONF change session, from connecting to closing, including what can go wrong.
Say this first
SSH to port 830 and request the netconf subsystem. Both sides send hello; the server's lists its
capabilities and a session-id. I lock the candidate, send edit-config, run validate, then commit
with confirmed and a timeout. I run my checks, send a confirming commit, unlock and
close-session. Any failure comes back as an rpc-error with a type, tag, severity and message, and I
discard-changes or let the confirmed commit roll back.
Go deeper
Step by step
Transport. SSH to TCP 830, subsystem netconf. Authentication is normal SSH (password or key, often via AAA).
Hello. The server sends its capabilities and a session-id; the client sends its own. If both list
base:1.1, framing switches to chunked.
Lock the candidate (and running, if the platform asks for it).
edit-config to the candidate.
validate the candidate (with :validate).
commit with <confirmed/> and <confirm-timeout>.
Check reachability and state from the automation side.
Confirming commit (a plain <commit/>), then unlock and close-session.
The same flow with ncclient
Python
from ncclient import managerCONFIG = """<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> <interface> <name>GigabitEthernet2</name> <description>to SW1 Gi1/0/48</description> </interface> </interfaces></config>"""with manager.connect(host="10.0.0.1", port=830, username="netops", password="secret", hostkey_verify=False) as m: with m.locked(target="candidate"): m.discard_changes() m.edit_config(target="candidate", config=CONFIG) m.validate(source="candidate") m.commit(confirmed=True, timeout="120") # run post-checks here; if they fail, do nothing and the device rolls back m.commit()
discard_changes() first clears anything another session left in a shared candidate.
hostkey_verify=False is for a lab; in production load known host keys.
error-type is transport, rpc, protocol or application. For lock-denied, error-info names the
session holding the lock. The error-message text varies by vendor.
Common failures
Port 830 refused: NETCONF not enabled, or an ACL on the management plane.
MissingCapabilityError in ncclient: you targeted the candidate on a device that does not offer it.
unknown-element or invalid-value: wrong namespace, a typo, or a node the platform does not support.
lock-denied: another session or a stuck session holds the lock; kill-session frees it if you are sure.
They will ask next
What does persist do on a confirmed commit?
With :confirmed-commit:1.1, <persist> gives the confirmed commit a token so a different session can
confirm or cancel it with <persist-id>. Without it, the confirmed commit is tied to the session that
started it, and closing that session rolls the change back.
Traps
Forgetting the confirming commit. The change works for two minutes and then quietly reverts.
Leaving a lock held by a crashed script. Use a context manager so the lock is always released.
Design a CI/CD pipeline for network changes. What runs before and after the change?
Say this first
A merge request triggers lint and data validation, then renders configs and tests them offline (Batfish)
or in a virtual lab (containerlab), then runs a dry run against production and posts the diff for review.
After approval and inside the change window, deploy runs pre-checks, pushes in batches, runs post-checks
that compare state with the pre-check snapshot, and rolls back if they fail. Every step leaves logs on the ticket.
Go deeper
Stages
Lint:yamllint, ansible-lint, Python linters. Catches syntax before anything runs.
Validate data: schema checks and rules (no duplicate VLAN IDs, every IP inside its prefix, names
follow the standard).
Render and test: build every device config from templates. Batfish loads them and answers
questions: undefined references, ACL behavior, BGP sessions that will not come up, reachability.
Lab: optional containerlab topology with virtual images to test the playbook end to end.
Dry run:--check --diff or NAPALM compare_config against production, posted to the merge request.
Approve and deploy: manual gate, change window, batches.
Post-checks and rollback: compare with the pre-check snapshot; on failure, roll back automatically.
Pre-checks and post-checks
Capture: interfaces up, BGP and OSPF neighbors and prefix counts, route table size, MAC and ARP counts,
STP root, CPU. Pre-checks also act as a gate: do not change a device that is already unhealthy.
pyATS can learn features before and after and diff them; NAPALM getters and compliance_report
work too.
The plan and deploy jobs run on a runner inside the management network with access to devices and Vault.
They will ask next
What does Batfish do that a lab cannot?
It analyzes the configs of the whole network mathematically, without running devices, so it can
answer "can anything in VLAN 30 reach the PCI subnet" for every possible packet. A lab only tests the
packets you send, but it does run the real software, so the two complement each other.
Where do you draw the line between CI and CD for network changes?
CI (lint, validate, render, test, dry run) runs on every merge request automatically. CD to production
usually keeps a human approval and a change window, at least until the team trusts the checks enough
for low-risk change types to deploy on merge.
Traps
A pipeline with no post-checks. A push that "succeeded" can still have broken the network.
Running the deploy job from a shared cloud runner that cannot, and should not, reach production devices.
You need to push a change to 800 switches with automation. How do you make it safe?
Say this first
I treat it as a staged rollout. Pre-checks gate each device, a dry run diff is reviewed, and the change
goes to one or two canary devices first, then a small batch, then the rest, stopping on the first
failure. Where the platform has it I use confirmed commit so a change that cuts my access rolls itself
back. Post-checks run after every batch, and I keep out-of-band access, a tested rollback and a kill switch.
Go deeper
Before
Peer-reviewed merge request with a dry run diff for a sample of device types.
Tested on a lab device with the same platform and release.
Pre-checks: device reachable, healthy, config matches the expected baseline. Skip and report devices that fail.
Rollback written and tested, not just "re-run the old playbook".
serial grows the batches, and max_fail_percentage: 0 stops the rollout as soon as any device in a
batch fails. forks limits how many devices are touched at once, which also protects TACACS servers.
Safety nets per platform
Junos and NETCONF candidate: commit confirmed with a few minutes timeout.
IOS-XE with archive configured: configure terminal revert timer then configure confirm.
Changes that touch management access (AAA, ACLs on vty, management VRF) go last and slowest.
After
Post-checks per batch, compare with pre-checks, then a final report: changed, unchanged, failed, skipped.
They will ask next
What would make you stop the rollout halfway?
Any failed post-check, any device that does not come back reachable, or a jump in alarms from
monitoring. Stopping is cheap; the tooling should make it the default when anything is unexpected.
Traps
Running against all 800 at once because "it worked in the lab".
Changing AAA or management ACLs without a confirmed-commit or console fallback, and locking yourself out everywhere.
How do you detect and deal with configuration drift?
Say this first
Drift is the difference between intended config (rendered from the source of truth and templates) and
what is actually running. I detect it on a schedule by rendering the full intended config and diffing it
against the device: NAPALM load_replace_candidate plus compare_config, Ansible check mode with diff, or
controller compliance reports. Each difference is either an approved emergency change that goes back
into the source of truth, or an unauthorized one that gets remediated by pushing intent again.
Go deeper
Where it comes from
CLI fixes during incidents that never made it back to the data.
Partial pushes that failed halfway.
Software upgrades that change defaults or how config is displayed.
Other tools writing to the same device.
Why merge-only automation cannot see it
If you only merge lines, extra config added by hand never shows up in your diff. Only a full intended
config compared with the full running config (replace semantics) reveals additions as well as changes.
Practical setup
Nightly job renders intended configs and diffs them with the running configs.
Ignore volatile lines: ! Last configuration change at ..., certificate blobs, ntp clock-period.
Report per device and per section, and track the drift count over time.
Backups (Oxidized or RANCID style) in Git give a timeline of when each line appeared.
Ansible: ios_config with diff_against: intended and intended_config, run with --check --diff.
Policy
Decide per section: some are enforced (AAA, SNMP, NTP, ACLs), where intent always wins and drift is
corrected automatically; others are report-only while the team builds trust.
They will ask next
An engineer fixed an outage at 2 a.m. by CLI. Your nightly drift job wants to revert it. What should happen?
The job should report, not blindly revert, for sections where emergency changes are allowed. The
engineer opens a merge request to put the fix in the source of truth. Once merged, the drift
disappears. If nobody claims it in a set time, it is treated as unauthorized and reverted in the next window.
Traps
Auto-remediating everything from day one. A wrong template then "fixes" correct configs across the estate.
How would you bring an existing, hand-built network under automation?
Say this first
Read-only first: back up every config, build the inventory and source of truth from what exists, parse
configs into data. Then take control one section at a time, starting with low-risk global config such as
NTP, syslog, SNMP and banners. For each section I write the template, render it for every device and diff
against the live config until the diff is empty or every remaining line is an agreed fix. Only then is that
section enforced by automation and closed to CLI changes. Over months the coverage grows to the whole config.
Go deeper
Phases
Discover: inventory, backups, NAPALM getters or gathered from resource modules, LLDP for topology.
Populate the source of truth: import devices, interfaces, IPs and VLANs into NetBox. Clean the data
as you go; this is where you find the duplicates and mysteries.
Standardize: agree a standard per section, and accept that some devices must change to meet it.
Template per section: render, diff, adjust the template or the data until the diff is understood.
Enforce: push, then let drift checks protect the section.
Expand: interfaces, VLANs, routing, ACLs, and finally full-config replace where the platform supports it.
Why sections and not whole configs
A whole-config replace on day one means every inconsistency in the estate becomes a change at once. Per
section, each step is small, reviewable and reversible, and the team learns the tools on low-risk config.
They will ask next
How do you handle the 5 percent of devices that are snowflakes?
Capture the exception in data (a host variable or config context) so the template stays the same and
the difference is explicit. If it cannot be expressed as data, leave that section on the device
unmanaged and document it until the device is replaced.
Traps
Starting with a full-config replace on production devices.
Your Python script takes six hours to log in to 2,000 devices. How do you make it fast and safe?
Say this first
The job is I/O bound: most of the time is waiting on SSH and prompts, so run devices in parallel with a
thread pool, asyncio or Nornir, which runs tasks across an inventory with 20 worker threads by default.
Then add the safety parts: a sensible concurrency limit that the TACACS servers and vty lines can take,
timeouts and limited retries per device, per-device results and logs, and a summary of what failed instead
of stopping on the first error.
Go deeper
Thread pool with Netmiko
Python
import osfrom concurrent.futures import ThreadPoolExecutor, as_completedfrom netmiko import ConnectHandlerfrom netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutExceptionUSER = os.environ["NET_USER"]PASSWORD = os.environ["NET_PASS"]def get_uptime(host): device = {"device_type": "cisco_ios", "host": host, "username": USER, "password": PASSWORD} with ConnectHandler(**device) as conn: return conn.send_command("show version | include uptime")with open("hosts.txt") as f: hosts = [line.strip() for line in f if line.strip()]results, failures = {}, {}with ThreadPoolExecutor(max_workers=30) as pool: futures = {pool.submit(get_uptime, h): h for h in hosts} for fut in as_completed(futures): host = futures[fut] try: results[host] = fut.result() except (NetmikoTimeoutException, NetmikoAuthenticationException) as exc: failures[host] = type(exc).__name__print(f"{len(results)} ok, {len(failures)} failed")
AAA: 30 parallel logins per second can overload TACACS+ servers. Ask before you raise concurrency.
vty lines: IOS often has only line vty 0 4 (five sessions). Your job plus engineers can run out.
Jump hosts and firewalls: connection rate limits and SSH session caps.
Errors: catch per device, retry transient failures once or twice with backoff, never retry auth failures in a loop (account lockout).
They will ask next
Threads or asyncio?
Both work for I/O-bound SSH. Threads are simpler and work with Netmiko and NAPALM as they are. asyncio
(for example with Scrapli's async drivers) scales to more connections with less memory, but every
library in the chain must be async.
Traps
Using multiprocessing for an I/O-bound job. It works but costs far more memory than threads for no gain.
Retrying failed logins in a tight loop and locking the service account in AAA.