← Interview Prep

Nornir — Theory

Network automation as pure Python: the Nornir object model (inventory → filter → task → runner → results), inventory inheritance, connection plugins (netmiko/napalm/scrapli), failure handling, and Nornir vs Ansible.

Nornir is network automation as pure Python — no YAML DSL, no templating language standing between you and the logic. It gives you the boring-but-hard parts (a typed inventory, threaded parallelism, structured results, pluggable connections) and lets you write tasks as ordinary functions. It's the answer to "I've outgrown Ansible's YAML" without dropping to raw scripts.

The model: a Nornir object holds an inventory of hosts; you filter to a subset, then .run() a task against them; the runner executes it in parallel and hands back a structured Result per host.

Hello, Nornir

The smallest end-to-end program — initialize from config, define a task, run it across the inventory in parallel, print the structured result:

# hello_nornir.py
from nornir import InitNornir
from nornir.core.task import Task, Result
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")   # inventory + threaded runner

def hello(task: Task) -> Result:
    return Result(host=task.host, result=f"Hello from {task.host.name} ({task.host.hostname})")

result = nr.run(task=hello)                     # runs on every host, in parallel
print_result(result)
# config.yaml
inventory:
  plugin: SimpleInventory
  options:
    host_file: "hosts.yaml"
runner:
  plugin: threaded
  options: { num_workers: 10 }

# hosts.yaml
r1: { hostname: 10.0.0.1, platform: ios }
r2: { hostname: 10.0.0.2, platform: ios }
$ python hello_nornir.py
hello************************************************************
* r1 ** changed : False *******************************************
vvvv hello ** INFO
Hello from r1 (10.0.0.1)
^^^^ END hello ^^^^
* r2 ** changed : False *******************************************
vvvv hello ** INFO
Hello from r2 (10.0.0.2)
^^^^ END hello ^^^^

No device connection yet — a task is just a Python function of task. To actually talk to a Cisco box, swap the body for a netmiko task (set platform: cisco_ios so netmiko picks the right driver).

Collect show version from Cisco

Filter to the Cisco hosts and run a command task — one SSH session per host, all in parallel:

# hosts.yaml
# r1: { hostname: 10.0.0.1, platform: cisco_ios, username: admin, password: secret }
# r2: { hostname: 10.0.0.2, platform: cisco_ios, username: admin, password: secret }

from nornir_netmiko import netmiko_send_command

cisco = nr.filter(platform="cisco_ios")
out = cisco.run(task=netmiko_send_command, command_string="show version")
print_result(out)                       # full output per host

# or use the text programmatically:
for host, mr in out.items():
    first = mr.result.splitlines()[0]   # "Cisco IOS Software, ..."
    print(host, "->", first)

Generate & push a Cisco interface (IP + MTU)

Build the config from per-host data (your source of truth), then apply it with a config task — the list of lines is the generated config:

# hosts.yaml
# r1:
#   hostname: 10.0.0.1
#   platform: cisco_ios
#   username: admin
#   password: secret
#   data:
#     iface: GigabitEthernet0/1
#     ip:   10.0.0.1
#     mask: 255.255.255.0
#     mtu:  9000

from nornir_netmiko import netmiko_send_config

def configure_interface(task: Task) -> Result:
    d = task.host.data
    cfg = [
        f"interface {d['iface']}",
        f" ip address {d['ip']} {d['mask']}",
        f" mtu {d['mtu']}",
    ]
    # preview instead of pushing:  return Result(host=task.host, result="\n".join(cfg))
    r = task.run(task=netmiko_send_config, config_commands=cfg)
    return Result(host=task.host, result=r.result)

print_result(nr.filter(platform="cisco_ios").run(task=configure_interface))
# generated + applied on r1:
interface GigabitEthernet0/1
 ip address 10.0.0.1 255.255.255.0
 mtu 9000

netmiko enters config mode and applies the lines; add a write memory task to persist. For a Jinja2-templated version and a NAPALM dry-run/diff before commit, see Nornir — Practice.

Nornir vs Ansible

AnsibleNornir
You writeYAML playbooks (+ Jinja)Python functions
LogicConstrained (when/loop); complex logic is painfulFull Python — loops, classes, libraries, tests
ParallelismForks (processes)Threaded runner (num_workers)
DebuggingHarder (through the engine)Normal Python (pdb, pytest, stack traces)
Best forDeclarative fleet config, big module libraryComplex workflows, scale, dev-heavy teams

They're not enemies — many shops use Ansible for straightforward pushes and Nornir when the logic gets real. See Ansible — Theory.

Architecture

PieceRole
InventoryHosts, groups, and defaults with inherited data + connection_options. From SimpleInventory (YAML) or a plugin (NetBox, Ansible inventory).
Nornir objectBuilt by InitNornir() — ties inventory + runner + config together.
TaskA Python function def my_task(task, ...); can call subtasks via task.run().
RunnerExecutes the task across hosts — the threaded runner with num_workers controls concurrency.
ResultsAggregatedResult (all hosts) → MultiResult (per host, one entry per subtask) → Result (.result, .failed, .changed, .exception).

Inventory & inheritance

Three files in the classic layout — a host inherits from its groups, which inherit from defaults:

Filtering selects which hosts a task hits — the everyday operation: nr.filter(platform="ios") or the F object for richer queries (F(groups__contains="edge"), F(site="lhr") & F(role="spine")).

Connections & plugins

Nornir itself doesn't talk to devices — connection plugins do. You pick the library per task:

PluginGives
nornir_netmikoScreen-scraping CLI over SSH/telnet (netmiko_send_command/config).
nornir_napalmMulti-vendor getters + config diff/commit (napalm_get, napalm_configure).
nornir_scrapliFast, async-capable CLI transport.
nornir_jinja2Render templates in a task.
nornir_utils / nornir_netboxprint_result; NetBox as the inventory source.

Results & failure handling

When to reach for Nornir

Complex, conditional workflows; anything you want to unit-test; large fleets where threaded control and structured results matter; teams comfortable in Python. For simple, declarative "make these lines present" changes, Ansible is often less code.

Likely interview questions

Related: Nornir — Practice · Ansible — Theory · Python Automation.