пятница, 9 апреля 2021 г.

VyOS | Скрипт для рассылки конфигурации

[root@localhost vyos_cfg_v2]# python3 vyos_cfg_v2.py -i all_devices.yaml -d reboot.yaml 


#########################################  Starting "R1"  ##########################################

# REBOOT PHASE  ####################################################################################

['reboot']

...

ValueError: Operation "reboot" not supported

Есть у меня два личных инстанса VyOS которые я администрирую. В последнее время появляются и еще машинки. Есть задача рассылки конфигурации сразу на несколько устройств, которую я решал с помощью скрипта vyos_cfg. В целом он работал довольно прилично, хоть и использовал старый добрый expect. Плюс, код был не самый уж элегантный. Поигравшись с API в прошлом посте я решил переписать скрипт и уйти от expect. Сегодня заметка про это.

Встречайте! vyos_cfg_v2

Думаю, как он написан не особо интересно, да и это я хочу немного затронуть в будущем посте. Сегодня про то как его использовать и что он умеет. 

Какие цели ставились:

  • Поддержка неограниченного количества устройств
  • Возможность разбивать сам процесс на логические стадии (самый простой пример - проверки до, сами изменения и проверки после)
  • Простота использования
А теперь быстро и на примере разберем как им пользоваться. 


Допустим у нас есть все та же сеть из прошлой лабы и мы хотим управлять нижней ее частью, как делали в прошлом посте, когда я щупал API. 

Для начала клонируем репозиторий https://github.com/MelHiour/vyos_cfg_v2

[root@localhost ~]# git clone https://github.com/MelHiour/vyos_cfg_v2.git

Cloning into 'vyos_cfg_v2'...

remote: Enumerating objects: 275, done.

remote: Counting objects: 100% (275/275), done.

remote: Compressing objects: 100% (192/192), done.

remote: Total 275 (delta 136), reused 108 (delta 47), pack-reused 0

Receiving objects: 100% (275/275), 50.73 KiB | 665.00 KiB/s, done.

Resolving deltas: 100% (136/136), done.

[root@localhost ~]# cd vyos_cfg_v2/


Далее нам нужно установить все из requirements.txt.

[root@localhost vyos_cfg_v2]# cat requirements.txt 

click

PyYAML

requests

requests-mock

pytest

mock


[root@localhost vyos_cfg_v2]# pip3 install -r requirements.txt

...

Successfully installed attrs-20.3.0 importlib-metadata-3.10.0 iniconfig-1.1.1 mock-4.0.3 packaging-20.9 pluggy-0.13.1 py-1.10.0 pyparsing-2.4.7 pytest-6.2.3 requests-mock-1.8.0 toml-0.10.2 typing-extensions-3.7.4.3 zipp-3.4.1


Теперь создадим инвенторный файл со всеми устройствами. Указываем адрес, порт и имя ключа. Для примера можно использовать inventory.yaml.

[root@localhost vyos_cfg_v2]# cat all_devices.yaml 

R1:

    address: 192.168.0.21

    port: 443

    key_name: default

R2:

    address: 192.168.0.22

    port: 443

    key_name: default

F1:

    address: 192.168.0.11

    port: 443

    key_name: default

F2:

    address: 192.168.0.12

    port: 443

    key_name: default


В дальнейшем можно создавать несколько файлов в зависимости от логической группировки. В нашем случае напришивается по файлу для файерволов и роутеров.

Создаем сами ключи в key.py. У меня один ключ на всех для примера. 

[root@localhost vyos_cfg_v2]# cat key.py 

# This file should be in .gitignore

default = "SECRET_ONE"


Считаем, что API на устройствах настроен. Можно обратиться к прошлому посту или документации за подсказкой. 

Пришло время для файлика с самими командами. Можно использовать deployment.yaml для примера, но стоит сказать еще пару слов. 

Во-первых, поддерживаются следующие команды
  • show - для отображения части конфигурации. Например, show interfaces вернет все что касается интерфейсов в конфигурации
  • get - получение вывода operation комманд. get interfaces выведет то, что вы получите на устройстве введя show interfaces. Звучит, путано, но все просто
  • delete - для удаления значения или ветки в конфигурации
  • comment - для комментирования какого-то значения.
Демонстрирую разницу между show и get. Для этого создадим простой файлик с двумя командами. В первой смотрим часть конфигурации, во второй смотрим операционные данные.

[root@localhost vyos_cfg_v2]# cat show-vs-get.yaml 

show and get:

    - show high-availability vrrp

    - get vrrp



Запускаем

[root@localhost vyos_cfg_v2]# python3 vyos_cfg_v2.py -i all_devices.yaml -d show-vs-get.yaml 

#######################################  DEPLOYMENT STARTED  #######################################

...

#########################################  Starting "R2"  ##########################################

# SHOW AND GET PHASE  ##############################################################################

['show high-availability vrrp', 'get vrrp']

# RESULTS  #########################################################################################


# COMMAND: show high-availability vrrp

# SUCCESS: True

# ERROR: None

# RESULT:

{'group': {'VLAN11': {'hello-source-address': '10.0.11.2',

                      'interface': 'eth1',

                      'peer-address': '10.0.11.1',

                      'priority': '100',

                      'virtual-address': '10.0.11.254/24',

                      'vrid': '11'},

           'VLAN12': {'hello-source-address': '10.0.12.2',

                      'interface': 'eth2',

                      'peer-address': '10.0.12.1',

                      'priority': '200',

                      'transition-script': {'backup': '/config/vrrp-goes-backup.sh',

                                            'master': '/config/vrrp-goes-master.sh'},

                      'virtual-address': '10.0.12.254/24',

                      'vrid': '12'}}}


# COMMAND: get vrrp

# SUCCESS: True

# ERROR: None

# RESULT:

['Name    Interface      VRID  State      Priority  Last Transition',

 '------  -----------  ------  -------  ----------  -----------------',

 'VLAN11  eth1             11  MASTER          100  15m58s',

 'VLAN12  eth2             12  MASTER          200  15m58s']


Как видно выше, вывод разный. Первая часть это часть конфига, вторая это реальная информация с работающей железки. 

Второй момент, файл можно разбивать на логические стадии которые будут выполняться одна за другой. Самый просто пример - проверки до, сами изменения и проверки после. Но можно придумать и другие сценарии. Команды в каждой секции можно использовать любые.

Теперь попробуем придумать какой-то более реальный пример. Скажееееем, на всех устройствах нужно настроить NTP. Что может быть проще.

Я бы разбил эту задачу на несколько этапов. Смотрим все ли вообще в порядке с устройством, смотрим текущие настройки NTP, удаляем конфиг, шлем новые настройки и проверяем применилась ли конфигурация.

[root@localhost vyos_cfg_v2]# cat ntp.yaml 

GENERAL:

    - get system processes summary

    - get system memory

PRE CHECKS:

    - show system ntp

    - get ntp

NTP CONFIG:

    - delete system ntp

    - set system ntp server 192.168.0.254

POST checks:

    - show system ntp

    - get ntp


Рассылаем на все устройства. Внизу только вывод для первого устройства с комментариями

[root@localhost vyos_cfg_v2]# python3 vyos_cfg_v2.py -i all_devices.yaml -d ntp.yaml 

#######################################  DEPLOYMENT STARTED  #######################################

#########################################  Starting "R1"  ##########################################

# GENERAL PHASE  ###################################################################################

['get system processes summary', 'get system memory']

# RESULTS  #########################################################################################


# COMMAND: get system processes summary

# SUCCESS: True

# ERROR: None

# RESULT:

[' 20:53:33 up 1 day,  1:09,  1 user,  load average: 1.04, 1.04, 1.01'] <<< Выглядит неплохо


# COMMAND: get system memory

# SUCCESS: True

# ERROR: None

# RESULT:

['Total: 484', 'Free:  279', 'Used:  205'] <<< Памяти тоже хватает




# PRE CHECKS PHASE  ################################################################################

['show system ntp', 'get ntp']

# RESULTS  #########################################################################################


# COMMAND: show system ntp <<< Смотрим настройки

# SUCCESS: True

# ERROR: None

# RESULT:

{'server': {'0.pool.ntp.org': {}, '1.pool.ntp.org': {}, '2.pool.ntp.org': {}}} <<< Дефолтные сервера


# COMMAND: get ntp

# SUCCESS: True

# ERROR: None

# RESULT:

["No association ID's returned"] <<< Время они не отдают




# NTP CONFIG PHASE  ################################################################################

['delete system ntp', 'set system ntp server 192.168.0.254']


Do you want to continue? (y/n): y <<< Перед изменениями скрипт спрашивает разрешение


# RESULTS  #########################################################################################


# COMMAND: Batched push of commands above <<< Конфигурационные команды отправляются пачкой

# SUCCESS: True <<< Ошибок нет

# ERROR: None

# RESULT:

None.  <<< API ничего не вернул




# POST CHECKS PHASE  ###############################################################################

['show system ntp', 'get ntp']

# RESULTS  #########################################################################################


# COMMAND: show system ntp

# SUCCESS: True

# ERROR: None

# RESULT:

{'server': {'192.168.0.254': {}}}. < Видим только новый сервер в конфиге


# COMMAND: get ntp

# SUCCESS: True

# ERROR: None

# RESULT:

['     remote           refid      st t when poll reach   delay   offset  jitter',

 '==============================================================================',

 ' 192.168.0.254   27.124.125.251   3 u    -   64    1    0.653   -0.273   0.100'] <<< и ассоциацию



######################################  SAVING CONFIGURATION  ######################################


# COMMAND: Save config   <<< Сохраняемся

# SUCCESS: True

# ERROR: None

# RESULT:

["Saving configuration to '/config/config.boot'...", 'Done']


Можно скрипт запустить без сохранения конфигурации --skip-save или в режиме --brave. В последнем случае никаких вопросов перед конфигурационными командами не будет. Еще одна особенность - конфигурационные команды отправляются пачкой, если только они присутствуют в той или иной секции. Сделано это потому, что в некоторых сложных конфигурациях (ipsec, dhcp, etc) конфигурацию надо комитить разом.

Вместо выводов

Скрипт писался для себя и меня он полностью устраивает за исключением моментов ниже. Особенно радует разбитие на неограниченное число фаз, что существенно повышает качество (ИМХО). 

В будущем:
  • Хочется иметь возможность поставить скрипт в cron и подкладывать ему файлики. Делать это можно и сейчас, но по хорошему нужно переписать/дописать логирование или вообще обличить все это в отдельный процесс. 
  • Хочется провести нагрузочное тестирование и сравнить с expect версией
  • Было бы интересно подумать над реализацией более серьезного варианта. Скажем, разбить саму рассылку конфигов на параллельные задачи, которые будут выполняться отдельными процессами/машинами/контейнерами. Надзором будет заниматься некий оркестратор. В таком варианте можно масштабировать скрипт. Знаю, что это уже придумали, но свой велосипед всегда милей, пусть и с квадратными колесами. 
  • Нужно понимать, что API все еще в rolling release и есть некоторые вопросы. Например, как перезагрузить роутер через API?
  • Не поддерживаются сложные команды с пайпами в operation режиме. Скажем get system processes | no-more не пройдет. Та же история с комментариями. set interface description "Long description" оставит неправильный дескрипшен. Нужно переписывать логику. Завел ишу...

Такая вот заметочка получилась. 

Комментариев нет:

Отправить комментарий