Sổ tay kỹ thuật

Nornir cơ bản - inventory, task, filter

how-to#nornir#python#netmiko#napalm· cập nhật 2026-11-06

Dùng khi muốn chạy tác vụ song song trên nhiều router bằng Python thuần với Nornir, kết hợp plugin netmiko/napalm và xử lý kết quả.

Sửa trang này
Mục lục

Tóm tắt: Nornir là framework Python (không phải DSL như Ansible): đọc inventory → nr.filter(...) chọn host → nr.run(task=...) chạy song song bằng thread → nhận AggregatedResult (dict host → list Result). Kết nối thiết bị nằm ở plugin riêng: nornir_netmiko, nornir_napalm, nornir_scrapli. In kết quả bằng print_result của nornir_utils.

Luồng hoạt động

flowchart LR A["config.yaml"] --> B["InitNornir()"] B --> C["SimpleInventory: hosts/groups/defaults"] C --> D["nr.filter(F(...))"] D --> E["nr.run(task)"] E -->|"thread pool"| F[("Router 1..N")] F --> G["AggregatedResult"] G --> H["print_result / failed_hosts"]

Cấu hình

Cài đặt

python3 -m pip install nornir nornir-utils nornir-netmiko nornir-napalm

Inventory

config.yaml
inventory:
  plugin: SimpleInventory
  options:
    host_file: inventory/hosts.yaml
    group_file: inventory/groups.yaml
    defaults_file: inventory/defaults.yaml
runner:
  plugin: threaded
  options:
    num_workers: 20
logging:
  log_file: nornir.log
inventory/hosts.yaml
pe1-hni:
  hostname: 198.51.100.11
  groups: [iosxr]
  data: { site: hni, role: pe }
mx1-hcm:
  hostname: 198.51.100.21
  groups: [junos]
  data: { site: hcm, role: pe }
inventory/groups.yaml
iosxr:
  platform: iosxr            # napalm driver name
  connection_options:
    netmiko:
      platform: cisco_xr     # netmiko device_type
junos:
  platform: junos
  connection_options:
    netmiko:
      platform: juniper_junos
    napalm:
      extras:
        optional_args: { port: 830 }

platform ở host/group được plugin dùng làm tên driver. Netmiko và NAPALM đặt tên khác nhau (cisco_xr vs iosxr), nên ghi đè trong connection_options.<plugin>.platform.

Không ghi mật khẩu vào defaults.yaml, nạp từ biến môi trường:

run.py
import os
from nornir import InitNornir
from nornir.core.filter import F
from nornir_netmiko.tasks import netmiko_send_command
from nornir_utils.plugins.functions import print_result
 
nr = InitNornir(config_file="config.yaml")
nr.inventory.defaults.username = os.environ["NET_USER"]
nr.inventory.defaults.password = os.environ["NET_PASS"]
 
pe_hni = nr.filter(F(role="pe") & F(site="hni"))
res = pe_hni.run(task=netmiko_send_command, command_string="show version")
print_result(res)

Filter

Mục đích Cú pháp
Theo field/data đơn giản nr.filter(site="hni", role="pe")
Thuộc group nr.filter(F(groups__contains="iosxr"))
AND / OR / NOT F(a=1) & F(b=2), F(a=1) | F(a=2), ~F(role="pe")
Key lồng trong data (dict) F(mgmt__vrf="MGMT") (data mgmt: {vrf: MGMT})
Hàm tuỳ ý nr.filter(filter_func=lambda h: h.name.startswith("pe"))
Xem host sau filter print(list(pe_hni.inventory.hosts))

Task tự viết, gom nhiều bước

tasks.py
from nornir.core.task import Task, Result
from nornir_netmiko.tasks import netmiko_send_command
from nornir_napalm.plugins.tasks import napalm_get, napalm_configure
 
def check_bgp(task: Task) -> Result:
    # Sub-task: kết quả con nằm trong MultiResult của host
    r = task.run(task=napalm_get, getters=["bgp_neighbors"])
    peers = r.result["bgp_neighbors"]["global"]["peers"]
    down = [ip for ip, p in peers.items() if not p["is_up"]]
    return Result(host=task.host, result=down, failed=bool(down))
 
def push_desc(task: Task, dry_run: bool = True) -> Result:
    cfg = task.host["desc_cfg"]            # lấy từ data của host
    r = task.run(task=napalm_configure, configuration=cfg, dry_run=dry_run)
    return Result(host=task.host, result=r.result, changed=r.changed, diff=r.diff)
res = nr.run(task=check_bgp, name="BGP down?")
res = nr.filter(site="hni").run(task=push_desc, dry_run=True)   # chỉ xem diff

Xử lý kết quả

Mục đích Cú pháp
Có host nào lỗi không res.failed
Danh sách host lỗi res.failed_hosts (dict host → MultiResult)
Kết quả task chính của 1 host res["pe1-hni"][0].result
Kết quả sub-task thứ 2 res["pe1-hni"][1].result
Exception gốc res["pe1-hni"][0].exception
Diff (napalm_configure) res["pe1-hni"][0].diff
In gọn, chỉ host lỗi print_result(res.failed_hosts) hoặc lặp res.items()
Chạy lại trên host lỗi nr.run(task=..., on_good=False, on_failed=True)
Xoá danh sách host lỗi đã ghi nhận nr.data.reset_failed_hosts()
for host, multi in res.items():
    status = "FAIL" if multi.failed else "OK"
    print(f"{host:12} {status:4} {multi[0].result}")

Host đã lỗi ở một nr.run sẽ bị bỏ qua ở các lần run sau (mặc định on_failed=False). Muốn chạy tiếp trên mọi host thì reset hoặc truyền on_failed=True.

Ví dụ đẩy config bằng netmiko và commit

from nornir_netmiko.tasks import netmiko_send_config, netmiko_commit
 
def xr_desc(task: Task) -> Result:
    task.run(task=netmiko_send_config,
             config_commands=["interface GigabitEthernet0/0/0/1",
                              "description to-pe2-hcm"],
             exit_config_mode=False)
    task.run(task=netmiko_commit, comment="CHG-1234")
    return Result(host=task.host, result="committed", changed=True)
 
nr.filter(F(groups__contains="iosxr")).run(task=xr_desc)

Muốn diff/commit confirmed chuẩn hơn thì dùng napalm_configure (Junos hỗ trợ revert_in), xem NAPALM cheatsheet.

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
PluginNotRegistered Chưa cài plugin (nornir-utils, nornir-netmiko...) hoặc sai tên plugin inventory/runner pip list | grep nornir, kiểm tra plugin: trong config
Netmiko báo Unsupported 'device_type' Dùng platform: iosxr cho netmiko Đặt connection_options.netmiko.platform: cisco_xr
Host không chạy ở lần run thứ hai Host nằm trong failed_hosts từ lần trước nr.data.reset_failed_hosts() hoặc on_failed=True
Kết quả rỗng, không lỗi Filter không khớp host nào In list(nr_f.inventory.hosts) trước khi run
Treo khi chạy nhiều host num_workers lớn, AAA/TACACS giới hạn session Giảm num_workers (10-20)
KeyError khi task.host["x"] Thiếu key trong data host/group/defaults Dùng task.host.get("x") hoặc bổ sung inventory

Tham khảo