Nornir + NAPALM: аудит конфигурации 120 коммутаторов за 40 секунд и результаты в Elasticsearch
Автоматизируем аудит сетевого оборудования через Nornir и NAPALM: проверяем соответствие baseline, параллельно опрашиваем коммутаторы, результаты пишем в Elasticsearch.
NAPALM и Nornir набирают популярность как Python-библиотеки для сетевой автоматизации - сообщество активно переходит от одиночных скриптов к фреймворкам
Задача пришла от клиента с развитой сетевой инфраструктурой: около 120 коммутаторов Cisco и несколько Juniper, и нужно регулярно проверять, что конфигурация не разошлась с baseline. «Разошлась» - это когда кто-то зашёл руками и что-то поменял. NTP-серверы не те, SNMP community не тот, AAA настроена не по шаблону. Инвентаризация вручную делалась раз в квартал и занимала несколько дней. Хотелось чаще и без человека.
Ansible для сетевых задач мы уже щупали, писали про коллекции network.* - см. опыт с Ansible Collections. Но именно для аудита он избыточен: у него нет параллельного выполнения из коробки без нагромождений с async/poll, а главное - NAPALM как абстракция над vendor API там используется через обёртки. Решили попробовать нативный стек: Nornir как фреймворк + NAPALM напрямую.
Почему Nornir, а не очередной набор скриптов
Nornir - это Python-фреймворк для автоматизации сети. Не DSL, не YAML-конфиги с магией, а именно Python: задачи пишутся как функции, инвентарь - обычные YAML-файлы или любой плагин, параллелизм встроен через concurrent.futures. Версия 3.0 вышла весной, API заметно почистили.
Главное, что он даёт - не надо самому управлять пулом потоков и агрегацией результатов. Написал task-функцию, передал в nr.run() - получил структурированный AggregatedResult по каждому хосту. Упавшие хосты не роняют весь прогон.
NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) - отдельная библиотека, которая даёт единый API поверх разных вендоров. get_facts(), get_interfaces(), get_ntp_servers() - возвращают одинаковую структуру данных независимо от того, Cisco IOS это или Juniper JunOS. Это и нужно для multi-vendor аудита.
Структура задачи
Инвентарь в Nornir живёт в hosts.yaml:
sw-access-01:
hostname: 192.168.10.1
platform: ios
groups:
- access_switches
sw-core-01:
hostname: 10.0.1.1
platform: junos
groups:
- core_switches
Группы наследуют параметры из groups.yaml, там credentials через переменные окружения. Реальных паролей в репозитории нет.
Сам аудит - набор task-функций. Каждая проверяет один аспект конфигурации:
from nornir import InitNornir
from nornir_napalm.plugins.tasks import napalm_get
def check_ntp(task):
result = task.run(task=napalm_get, getters=["ntp_servers"])
configured = set(result[0].result["ntp_servers"].keys())
expected = set(task.host.data.get("expected_ntp", BASELINE["ntp_servers"]))
missing = expected - configured
extra = configured - expected
return Result(
host=task.host,
result={"missing": list(missing), "extra": list(extra)},
failed=bool(missing or extra),
)
Такие же функции для SNMP community, banner, AAA, VTP mode. Каждая возвращает структурированный результат - что не так и в каком направлении.
Запуск параллельный, по умолчанию Nornir использует 20 потоков:
nr = InitNornir(config_file="nornir.yaml")
results = nr.run(task=full_audit)
На 120 хостах весь прогон занимает порядка 40 секунд. Большая часть времени - сетевые задержки при подключении к оборудованию, а не обработка.
Результаты в Elasticsearch
Хранить результаты только в stdout - потерять историю. Мы пишем каждый прогон в Elasticsearch: один документ на хост на прогон аудита. Индекс network-audit-YYYY.MM, ILM настроен - горячая фаза 30 дней, потом в холодную.
from datetime import datetime
from elasticsearch import Elasticsearch
es = Elasticsearch(ES_HOSTS)
def push_result(host_name, audit_result, run_id):
doc = {
"@timestamp": datetime.utcnow().isoformat(),
"run_id": run_id,
"host": host_name,
"compliant": not audit_result.failed,
"checks": {
"ntp": audit_result.result.get("ntp"),
"snmp": audit_result.result.get("snmp"),
"aaa": audit_result.result.get("aaa"),
}
}
es.index(index=f"network-audit-{datetime.utcnow():%Y.%m}", body=doc)
run_id - UUID на каждый запуск, чтобы можно было сравнивать прогоны между собой в Kibana. Смотрим trend: коммутатор был compliant неделю назад, сейчас нет - значит что-то менялось.
Про возможности Elasticsearch для аналитики недавно писали в посте про ES 7.9 - там ILM и transforms как раз по теме.
Что нашли в первом прогоне
Первый аудит показал картину, которую никто не ожидал в таких масштабах. Из 120 коммутаторов:
- NTP. На двух десятках хостов прописан дополнительный NTP-сервер, которого нет в baseline. Оказался адрес старого сервера, который давно выведен из эксплуатации. Коммутаторы его опрашивают, тот не отвечает - все живут, но конфигурация не чистая.
- SNMP community. Несколько коммутаторов имеют readonly-community не из стандартного списка. По одному даже нашли community «public» - классика жанра.
- Banner. Примерно треть хостов без login banner или с устаревшим текстом. Формально нарушение регламента.
- AAA. Тут всё относительно чисто - отклонений мало, и те в группе access-коммутаторов одного здания. Видимо, кто-то когда-то накатывал конфиг с ошибкой на партию.
Ничего критичного - но это именно то, что при ручном аудите либо не находят вообще, либо находят не полностью. Автоматика даёт полный охват без устатка и human error.
Где сейчас и что не решено
Запуск пока делается вручную или по cron - раз в сутки ночью. В идеале хотелось бы event-driven: изменение конфигурации на оборудовании -> syslog -> триггер аудита конкретного хоста. Syslog в Elasticsearch у клиента уже идёт, но склейка с аудитом - следующий шаг.
Nornir с NAPALM закрыл основную задачу: полный аудит за разумное время, результаты в хранилище, история видна. На Cisco IOS работает стабильно, с Juniper JunOS тоже без сюрпризов. Для аудита инфраструктуры это сейчас один из рабочих инструментов, который проверяется на реальном парке.
Единственное, что потребовало времени - разобраться с обработкой таймаутов. Если коммутатор в принципе недоступен, Nornir корректно пишет это в результат и идёт дальше, но SSH-таймаут по умолчанию великоват. В production пришлось тюнить через параметры Netmiko (NAPALM использует его под капотом для IOS).