Python в DevOps: автоматизация сетевых устройств
Основные методы автоматизации сетевого оборудования на Python
Библиотека Netmiko – наиболее эффективное решение
Netmiko предоставляет высокоуровневый интерфейс для SSH-подключения к сетевым устройствам различных вендоров (Cisco, Juniper, Arista, Huawei и др.). Она построена на основе Paramiko, но добавляет удобные методы для отправки команд, ожидания вывода, обработки ошибок и автоматического определения типа устройства.
from netmiko import ConnectHandler
cisco_device = {
'device_type': 'cisco_ios',
'host': '192.168.1.10',
'username': 'admin',
'password': 'secret',
}
with ConnectHandler(**cisco_device) as conn:
output = conn.send_command('show ip interface brief')
print(output)Python для сетевых инженеров автоматизация сети (python для автоматизации сети)
В примере создается словарь параметров подключения, затем объект соединения. Метод send_command отправляет команду и возвращает вывод. Библиотека автоматически обрабатывает pagination (more prompts) и ожидание приглашения.
Частые проблемы и ошибки:
- Неверный device_type – приводит к ошибке аутентификации или зависанию соединения. Следует уточнять точное имя типа из документации Netmiko.
- Отсутствие поддержки SSH-ключей – по умолчанию парольная аутентификация; для ключей требуется настроить агент или передать параметр use_keys=True.
- Ошибка таймаута при низкой скорости сети – рекомендуется увеличить timeout в словаре подключения.
Как выполнять SSH-подключения без дополнительных библиотек?
Библиотека Paramiko – низкоуровневая реализация протокола SSH. Она позволяет самостоятельно управлять каналами, ожиданием вывода и обработкой приглашений. Подходит для нестандартных сценариев, когда Netmiko не поддерживает нужное устройство или требуется тонкая настройка.
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.1.10', username='admin', password='secret')
channel = client.invoke_shell()
channel.send('show ip interface brief\n')
import time
time.sleep(1)
output = channel.recv(65535).decode('utf-8')
print(output)
client.close()После вызова invoke_shell() создается интерактивная сессия. Требуется вручную обрабатывать задержки и фрагментацию вывода. Такой подход менее надежен, чем Netmiko, но дает полный контроль.
Типичные ошибки:
- Неполучение всего вывода из-за недостаточной задержки – решается циклическим чтением до появления приглашения.
- Проблемы с кодировкой при использовании кириллицы – рекомендуется указывать кодировку в decode().
- Автоматическое добавление ключей хоста может быть небезопасно в production – лучше использовать load_host_keys.
Как получить унифицированный доступ к конфигурации разных вендоров?
Библиотека NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) предоставляет единый API для работы с конфигурациями (get_config, load_merge_candidate, commit) и сбора фактов (get_facts, get_interfaces). Она использует Netmiko или другие драйверы под капотом.
from napalm import get_network_driver
driver = get_network_driver('ios')
with driver('192.168.1.10', 'admin', 'secret') as device:
facts = device.get_facts()
print(facts['hostname'], facts['os_version'])
config = device.get_config()
print(config['running'][:200])С помощью get_network_driver выбирается драйвер под вендора. Метод get_config возвращает словарь с running, startup и other конфигурациями. NAPALM упрощает написание кроссплатформенных скриптов.
Проблемы при использовании:
- Ограниченная поддержка некоторых моделей – перед развертыванием стоит проверить доступные драйверы.
- Методы load_merge_candidate и commit могут быть не реализованы для всех платформ.
- Высокое потребление памяти при работе с большими конфигурациями – рекомендуется получать только нужные фрагменты через фильтры.
Как добиться высокой производительности при SSH-подключениях?
Библиотека Scrapli (Scalable Router Automation with Python) ориентирована на асинхронное взаимодействие и высокую скорость. Она поддерживает как синхронный, так и асинхронный режимы, а также работу через Telnet и SSH.
from scrapli import Scrapli
device = {
'host': '192.168.1.10',
'auth_username': 'admin',
'auth_password': 'secret',
'auth_strict_key': False,
'transport': 'ssh2',
'platform': 'cisco_iosxe'
}
with Scrapli(**device) as conn:
response = conn.send_command('show version')
print(response.result)
# Асинхронный вариант
from scrapli.driver.core import AsyncIOSXEDriver
import asyncio
async def run():
async with AsyncIOSXEDriver(**device) as conn:
result = await conn.send_command('show clock')
print(result.result)
asyncio.run(run())Scrapli использует собственный парсер для извлечения структурированных данных (например, genie или textfsm). Асинхронный режим позволяет одновременно обслуживать десятки устройств.
Возможные сложности:
- Не все транспортные протоколы доступны на Windows (например, 'paramiko' в Scrapli).
- Для ASCII-вывода может потребоваться настройка печати через encoding.
- Асинхронный код требует аккуратного управления задачами и обработки исключений.
Как управлять сетевыми устройствами через REST API?
Многие современные системы (Cisco Meraki, ACI, Arista CloudVision, Juniper Contrail) предоставляют RESTful API. Использование библиотеки requests позволяет напрямую отправлять HTTP-запросы для получения данных или изменения конфигурации.
import requests
api_key = 'YOUR_MERAKI_API_KEY'
org_id = 'YOUR_ORG_ID'
headers = {
'X-Cisco-Meraki-API-Key': api_key,
'Content-Type': 'application/json'
}
url = f'https://api.meraki.com/api/v1/organizations/{org_id}/networks'
response = requests.get(url, headers=headers)
if response.status_code == 200:
networks = response.json()
for net in networks:
print(net['name'], net['id'])
else:
print(f'Ошибка: {response.status_code}')Работа через REST API избавляет от необходимости использовать SSH и сессионное состояние. Данные возвращаются в формате JSON, который легко обрабатывается Python. Обратите внимание на ограничения по частоте запросов (rate limiting).
Распространенные ошибки:
- Неверные заголовки авторизации или неправильный API-ключ – приводит к 401 Unauthorized.
- Игнорирование пагинации – API может возвращать не все записи; требуется обрабатывать поля nextPage или cursor.
- Отсутствие обработки HTTP-ошибок – важно проверять статус и писать логику повторных попыток.
Расширенные примеры автоматизации сети с Python
Пример 1: Резервное копирование конфигураций коммутаторов Cisco с помощью Netmiko и Jinja2
Создадим скрипт, который подключается к списку устройств, забирает running-config и сохраняет в файл с именем, содержащим дату и имя хоста. Для формирования имени используется шаблон Jinja2.
from netmiko import ConnectHandler
from jinja2 import Template
from datetime import datetime
import os
devices = [
{'host': '192.168.1.10', 'device_type': 'cisco_ios', 'username': 'admin', 'password': 'secret'},
{'host': '192.168.1.20', 'device_type': 'cisco_ios', 'username': 'admin', 'password': 'secret'}
]
backup_dir = 'backups'
os.makedirs(backup_dir, exist_ok=True)
filename_template = Template('backup_{{ hostname }}_{{ date }}.txt')
date_str = datetime.now().strftime('%Y%m%d_%H%M%S')
for device in devices:
try:
with ConnectHandler(**device) as conn:
hostname = conn.send_command('show running-config | include hostname').split()[1]
config = conn.send_command('show running-config')
filename = filename_template.render(hostname=hostname, date=date_str)
filepath = os.path.join(backup_dir, filename)
with open(filepath, 'w') as f:
f.write(config)
print(f'Конфигурация {hostname} сохранена в {filepath}')
except Exception as e:
print(f'Ошибка при работе с {device["host"]}: {e}')Конфигурация Switch-A сохранена в backups/backup_Switch-A_20250315_120500.txt Конфигурация Router-B сохранена в backups/backup_Router-B_20250315_120500.txt
В скрипте используется Jinja2 для динамического формирования имени файла. Обработка исключений позволяет продолжить резервное копирование даже при недоступности одного из устройств.
Пример 2: Параллельный опрос устройств с помощью асинхронного Scrapli
Задача: получить uptime со всех коммутаторов в сети одновременно. Используется asyncio и Scrapli AsyncIOSXEDriver.
import asyncio
from scrapli.driver.core import AsyncIOSXEDriver
devices_params = [
{'host': '192.168.1.10', 'auth_username': 'admin', 'auth_password': 'secret', 'transport': 'ssh2'},
{'host': '192.168.1.11', 'auth_username': 'admin', 'auth_password': 'secret', 'transport': 'ssh2'},
]
async def get_uptime(params):
async with AsyncIOSXEDriver(**params) as conn:
response = await conn.send_command('show version | include uptime')
return params['host'], response.result
async def main():
tasks = [get_uptime(p) for p in devices_params]
for future in asyncio.as_completed(tasks):
host, result = await future
print(f'{host}: {result.strip()}')
asyncio.run(main())192.168.1.10: Switch uptime is 2 weeks, 3 days, 5 hours, 22 minutes 192.168.1.11: Router uptime is 1 week, 6 days, 12 hours, 10 minutes
Асинхронный подход сокращает общее время ожидания – устройства опрашиваются параллельно, а не последовательно.
Пример 3: Сравнение конфигураций с помощью NAPALM
Напишем скрипт, который загружает текущую конфигурацию устройства, затем предлагает изменения (например, добавить новый VLAN) и показывает разницу до коммита.
from napalm import get_network_driver
from napalm.base.exceptions import MergeError
driver = get_network_driver('ios')
with driver('192.168.1.10', 'admin', 'secret') as device:
config_before = device.get_config()['running']
# Подготовка изменений (например, новый VLAN)
candidate = (
'vlan 100\n'
' name TestVLAN\n'
)
device.load_merge_candidate(config=candidate)
diff = device.compare_config()
if diff:
print('Изменения, которые будут применены:')
print(diff)
# При необходимости:
# device.commit()
# device.rollback()
else:
print('Изменения не требуются')Изменения, которые будут применены: [vlan] + vlan 100 + name TestVLAN
Метод compare_config возвращает разницу в формате unified diff. Это удобно для аудита перед применением изменений.
Пример 4: Взаимодействие с Meraki Dashboard API через requests с обработкой пагинации
Получим список всех устройств (здесь – коммутаторов) в организации Meraki с помощью постраничного запроса.
import requests
api_key = 'YOUR_API_KEY'
org_id = 'YOUR_ORG_ID'
base_url = f'https://api.meraki.com/api/v1/organizations/{org_id}/devices'
headers = {
'X-Cisco-Meraki-API-Key': api_key,
'Accept': 'application/json'
}
all_devices = []
url = base_url
while url:
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f'Ошибка: {response.status_code}')
break
data = response.json()
all_devices.extend(data)
# Обработка пагинации (Link header)
link_header = response.headers.get('Link', '')
next_url = None
if 'rel="next"' in link_header:
for part in link_header.split(','):
if 'rel="next"' in part:
next_url = part[part.index('<')+1:part.index('>')]
break
url = next_url
print(f'Найдено устройств: {len(all_devices)}')
for dev in all_devices[:3]:
print(f'{dev["name"]} - {dev["model"]} - {dev["serial"]}')Найдено устройств: 12 Switch-A - MS250-48 - Q2CD-XXXX Switch-B - MS225-24 - Q2CE-YYYY Router-C - MX64 - Q2CF-ZZZZ
Скрипт обрабатывает пагинацию через заголовок Link. Это важно для организаций с большим количеством устройств, чтобы получить полный список.