Python в DevOps: автоматизация сетевых устройств

Раздел: DevOps -> Сетевые технологии с Python

Основные методы автоматизации сетевого оборудования на 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. Это важно для организаций с большим количеством устройств, чтобы получить полный список.

Python для автоматизации сети - comments

En
Python для сетевых инженеров автоматизация сети (python)