четверг, 25 мая 2017 г.

Ansible для Cisco - изменение конфигурации на свичах

Первое, если вы услышали про Ansible в первые и не знаете как его готовить, есть потрясающий ресурс с книгой (спасибо человеку который ее написал) https://www.gitbook.com/book/natenka/ansible-dlya-setevih-inzhenerov. После прочтения данного материала жизнь налаживается и хочется испробовать инструмент на полную.

У нас стояла задача изменить настройки сервера времени на паре десятков свичей. Печаль пришло тогда, когда выяснилось что часть свичей древняя и с еще более старой прошивкой, в которой есть только Telnet. И если при работе с SSH и Ansible все легко и просто, то с telnet есть некоторые трудности. Google выдает разные варианты решения вопроса от https://github.com/ansible/ansible/pull/4230 и https://habrahabr.ru/post/328486/ до https://www.packetgeek.net/2015/08/using-ansible-to-push-cisco-ios-configurations/. В любом случае придется немного по извращатся. 

И так, начнем с легкого.

SSH

Нам понадобится:
   - Создать invetory с перечнем хостов
   - Создать файл с переменными  
   - И playbook с задачами

Расписывать назначение каждого из файлов я не буду. Если вы прочли книжку из первого абзаца, то вам это уже не понадобится.

Inventory назовем cisco-switch и внесем следующие данные

  [cisco-shh]
  192.82.130.7
  192.82.130.12

  [cisco-telnet]
  192.82.132.1
  192.82.132.11
  192.82.132.12
  192.82.132.14
  192.82.132.15

Мы сразу разделили две группы для удобства использования.

Файлы с переменным распологаем в каталоге /group-vars. У нас будет три файла. 
1. Определяет общие переменные для всех устройств all.yml

cli:
  host: "{{ inventory_hostname }}"
  username: "admin"
  password: "SomePassword"
  transport: cli
  authorize: yes
  auth_pass: "SomePassword"

В файле мы указываем:

inventory_hostname - это специальная переменная, которая указывает на тот хост, для которого Ansible выполняет действия.
host - имя или IP-адрес удаленного устройства
port - к какому порту подключаться
username - имя пользователя
password - пароль
transport - тип подключения: CLI или API. По умолчанию - cli
authorize - нужно ли переходить в привилегированный режим (enable, для Cisco)
auth_pass - пароль для привилегированного режима

2. Создаем файлы переменных для каждой группы хостов. 
Для групп устройств, переменные должны находится в каталоге group_vars, в файлах, которые называются, как имя группы.

В наших файлах мы задаем две переменные NTP Server и временную зону.

cisco-shh.yml
ntp_server: 192.82.130.50
time_zone: EET 2

cisco-telnet.yml
ntp_server: 192.82.132.50
time_zone: EET 2

3. И самое вкусное - создаем Playbook. Кроме основных функций по передаче команд оборудования, мы решили еще немного поиграться и проверить а применились ли наши команды переданные через Ansible. Обращайте внимание на то что в файле не должно быть табуляций а иерархическая структура формируется пробелами. 

ntp-config.yml

- name: Configure NTP
  hosts: cisco-kiev
  gather_facts: false
  connection: local

  tasks:

    - name: Send config commands
      ios_config:
        lines:
          - ntp server {{ntp_server}}
          - clock timezone {{time_zone}}
          - clock summer-time EET recurring last Sun Mar 2:00 last Sun Oct 2:00
        replace: block
        backup: yes
        provider: "{{ cli }}"
      register: cfg


    - name: Show config updates
      debug: var=cfg.updates
      when: cfg.changed

    - name: Save config
      ios_command:
        commands:
          - write
        provider: "{{ cli }}"
      when: cfg.changed



    - name: show ntp
      ios_command:
        commands:
                - show clock
                - show ntp status
                - show ntp associations
                - show version
        provider: "{{ cli }}"
      register: out

    - name: Debug registered var
      debug: var=out.stdout_lines

Для Cisco используются следующие модули
ios_command - выполняет команды show
ios_config - выполняет команды конфигурации
ios_facts - собирает факты об устройствах 

Ранее мы создали файл с переменными All.yml и теперь при помощи аргумента provider можем передать все переменные в playbook без явного указания каждый раз данных для авторизации.

И еще немножко о Debug. На каждую задачу, можно задать переменную в атребуте register в которую будет записан результат работы задачи. А потом через debug можно преобразовать все в красивый и читабельный вид или отобразить изменения внесенные в конфигурацию.

Так же стоит обратить внимание на replace: block. Параметр replace указывает как именно нужно заменять конфигурацию. В нашем случае мы указали - отправлять все команды вне зависимости есть ли они в конфигурации.

И что же у нас получилось.
Запускаем  ansible-playbook -i cisco-switch ntp_config.yml. Через параметр -i указываем файл inventory.
Полотнище вывода я показывать не буду. Но можем убедится что все отработало.


PLAY RECAP *********************************************************************************************************
192.82.130.12               : ok=5    changed=1    unreachable=0    failed=0
192.82.130.7                : ok=5    changed=1    unreachable=0    failed=0

Telnet

И так. Для передачи команд оборудованию принимающему подключения только по telnet нам понадобится ранее созданные файл inventory cisco-switch.yml, файлы с переменными, playbook, файл с шаблонами команд и скрипт который будет передавать эти команды. 

Файл с командами будет выглядеть вот так:

ntp-telnet.j2

config t
ntp server {{ntp_server}}
clock timezone {{time_zone}}
clock summer-time EET recurring last Sun Mar 2:00 last Sun Oct 2:00
end
write

Теперь перейдем к скрипту. Скрипт мы решили задействовать из статьи https://www.packetgeek.net/2015/08/using-ansible-to-push-cisco-ios-configurations/. Но как всегда полезли проблемы со всех сторон. Во-первых библиотека netlib уже не та что раньше и часть функций из нее просто убрали. Во-вторых если вы ее еще не ставили, может быть придется повозится.

Для установки:
 1. Скачаем и распакуем git clone https://github.com/jtdub/netlib.git
 2. Убедитесь что у вас стоит pip и установите pexpect (pip install pexpect)
 3. Выполните все рекомендации из файла READ.MD
 4. Если во время установки netlib (python setup.py install) вы столкнулись с ошибкой   

Running setuptools-35.0.2/setup.py -q bdist_egg --dist-dir /tmp/easy_install-JhhA2M/setuptools-35.0.2/egg-dist-tmp-cJZPpx
Segmentation fault 

Выполните pip install --update setuptools
5. Повторите установку netlib.

О скрипте:
Изначально скрипт от автора выглядил вот так
#!/usr/bin/env python

from netlib.netlib.user_creds import simple_yaml
from netlib.netlib.conn_type import SSH

from os.path import expanduser
import sys

creds = simple_yaml()
base_dir = expanduser("~/net-ansible")
hostname = sys.argv[1]
command_file = sys.argv[2]
ssh = SSH(hostname, creds['username'], creds['password'])

ssh.connect()
ssh.set_enable(creds['enable'])

with open(base_dir + "/" + command_file) as f:
    for line in f.readlines():
        line = line.strip()
        ssh.command(line)
f.close()

ssh.close()

НО!!! во время выполнения скрипта (как это делается напишу дальше) мы сталкиваемся с ошибкой No module named netlib.netlib.user_creds, stderr_lines. И все почему - The past methods of password storage, simple_creds and simple_yaml have been depricated and removed from netlib.  KeyRing оставшейся в последний версии библиотеки нам не подошел. И в итоге скрипт мутировал и стал выглядит так.

#!/usr/bin/env python

from netlib.conn_type import SSH
from netlib.conn_type import Telnet

from os.path import expanduser
import sys


base_dir = "/etc/ansible/"
hostname = sys.argv[1]
command_file = sys.argv[4]
username = sys.argv[2]
password = sys.argv[3]
ssh = Telnet(hostname, username, password)

ssh.connect()
#We do not use enable in telnet session with user level 15
#ssh.set_enable(creds['enable'])

with open(base_dir + "/" + command_file) as f:
    for line in f.readlines():
        line = line.strip()
        ssh.command(line)
f.close()

ssh.close()

И нам осталось разобраться с playbook. Создаем ntp_telnet.yml.

- name: Configure NTP telnet
  hosts: cisco-telnet
  connection: local
  gather_facts: false

  tasks:
  - name: IOS | Create NTP Configuration
    template:
      src=input/ntp-telnet.j2
      dest=input/{{ inventory_hostname }}.conf
    delegate_to: 127.0.0.1
  

  - name: NTP Configuration
    command: script/to_telnet.py {{ inventory_hostname }} {{ cli.username }} {{cli.password}} input/{{ inventory_hostname }}.conf
    delegate_to: 127.0.0.1

В данном в rolebook в первой задаче на базе созданного шаблона конфигурации мы создаем файлы с набором команд для каждого хоста. Во второй задаче передаем эти файлы скрипту для выполнения.

Вот и все. Отмучались.

PLAY RECAP *********************************************************************************************************
10.82.132.1                : ok=3    changed=1    unreachable=0    failed=0
10.82.132.11               : ok=3    changed=1    unreachable=0    failed=0
10.82.132.12               : ok=3    changed=1    unreachable=0    failed=0
10.82.132.14               : ok=3    changed=1    unreachable=0    failed=0
10.82.132.15               : ok=3    changed=1    unreachable=0    failed=0

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