Глава 4. Инфраструктура автоматизации Python - основы Ansible

Наши предыдущие две главы постепенно вводили различные способы взаимодействия с сетевыми устройствами. В Главе 2, Взаимодействие с сетевым устройством на нижнем уровне мы обсудили Pexpect и Paramiko, которые автоматически управляют интерактивными сеансами. В Главе 3, Работа с сетями через API и для достижения целей мы обнаружили, что сетевые среды связаны не только с индивидуальные устройства, поскольку именно они являются для нас неким способом соединения отдельных частей воедино, но наша общая цель обычно требует более высокой бизнес логики. Например, представим себе всю эту иерархию умозрительного процесса, начиная с формирования нашей бизнес- цели и вплоть до реального выполнения действий на сетевом уровне (при этом его вершина находится ближе к бизнес- логике): мы рассмотрели различные API, которые представляют некий структурированный подход обратной связи от устройства а также некоторую хорошо определённую структуру команд. Оба этих метода находятся на достаточно низком уровне, что означает, что мы сосредоточены на определённом индивидуальном устройстве, посредством которого мы выполняем свои действия.

Однажды утром вы просыпаетесь и думаете про себя, что у вас имеется ценная информация о вашей сетевой среде, поэтому вам следует предпринять некие меры предосторожности в отношении своих сетевых устройств, чтобы сделать их более безопасными!

Затем вы начинаете подразделять данную цель на активных элемента, с которых следует начинать:

  • Обновление всех устройств до самой последней версии программного обеспечения, которое потребует:

    1. Загрузку необходимого образа на соответствующее устройство.

    2. Сообщение данному устройству необходимости выполнить загрузку с такого нового образа.

    3. Мы выполняем перезагрузку данного устройства.

    4. Проверка того, что данное устройство работает под управлением нового образа программного обеспечения.

  • Настройка соответствующего списка доступа к имеющимся сетевым устройствам, которая включает в себя:

    1. Построение необходимого списка доступа для данного устройства.

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

Будучи ориентированным на автоматизацию инженером Python вы приступаете к написанию сценариев с использованием Pexpect, Paramiko или API. Однако как вы можете обнаружить, любая сетевая задача представляет собой некую цель с её реальным исполнение в самом конце все операции. В течении длительного периода времени все инженеры будут транслировать свою бизнес логику в сетевые команды, затем мы документирум их в стандартные процедуры обработки с тем, чтобы мы могли возвращаться обратно к данному процессу либо самостоятельно, либо через своих коллег.

В этой и следующей главе мы начнём обсуждать некое средство автоматизации с открытым исходным кодом с названием Ansible. Именно это средство упрощает весь процесс перехода от бизнес логики к сетевым командам. Оно способно настраивать системы, развёртывать программное обеспечение, а также координировать некое сочетание задач. Ansible написан на Python и проявляется как одно из самых ведущих инструментов автоматизации, поддерживаемое производителями сетевого оборудования.

В данной главе мы рассмотрим следующие вопросы:

  • Некий пример Ansible на скорую руку

  • Все преимущества Ansible

  • Архитектуру Ansible

  • Модули и примеры Ansible Cisco

  • Модули и примеры Ansible Juniper

  • Модули и примеры Ansible Arista

На момент написания данной книги выпуск Ansible 2.2 был совместим с Python 2.6 и 2.7, а также с с технической экспертизой поддержки для Python 3. Однако, в точности как в случае с Python, многие полезные свойства Ansible поступают через модули расширения предлагаемые сообществом. После того как модуль ядра пройдёт проверку на стабильность с Python 3 (надеемся, в скором будущем), потребуется некоторое время для переноса всех имеющихся модулей расширения с Python 2 на Python 3. На протяжении всей оставшейся книги мы будем применять для Ansible Python 2.7.

[Замечание]Замечание

Для получения самой последней информации по поддержке Ansible Python 3 проверьте http://docs.ansible.com/ansible/python_3_support.html .

Как уже говорилось в предыдущей главе, я полагаюсь на изучение на примерах. В точности как и сам лежащий в основе Ansible код Python, весь синтаксис для конструкция Ansible достаточно прост для понимания если вы не работали с Ansible прежде. Если у вас имеется некий опыт работы с YAML и Jinja2, вы быстро воссоздадите необходимую взаимосвязь между самим синтаксисом и всеми предназначенными для этого процедурами. Давайте для начала взглянем на некий пример.

Быстрый пример Ansible

Как и все прочие инструменты автоматизации, Ansible вначале стартует с управляющего сервера прежде чем расшириться до управления всем сетевым оборудованием. Для основной части все модули и то, что в Ansible называется плейбуками (планами) одинаковы для серверных модулей и модулей сетевых устройств, однако всё же имеются некоторые различия. По этой причине полезно для начала рассмотреть пример такого сервера и позднее обрисовать сравнения когда мы начнём просмотр сетевых модулей.

Установка управляющего узла

Для начала давайте проясним ту терминологию, которую мы будем применять в контексте Ansible. Мы будем называть все виртуальные машины с установленным Ansible управляющими машинами, а машины под их управлением целевыми машинами или управляемыми узлами. Ansible может устанавливаться на большинство имеющихся систем Unix с единственной зависимостью от Python 2.6 или 2.7; текущий Windows не поддерживается в качестве основной управляющей машины. Хост Windows всё ещё может управляться Ansible, так как он только не поддерживается в качестве самой управляющей машины.

[Замечание]Замечание

Когда Windows 10 приспособится к Linux Ubuntu, Ansible вскоре будет готов исполняться также и под Windows.

В требованиях к управляющему узлу вы можете заметить, что требуется Python 2.4 или более поздняя версия. Это справедливо для управления целевыми узлами с операционными системами, подобными Linux, однако очевидно, что не всё сетевое окружение поддерживает Python. Мы увидим как это требование обходится для сетевых модулей.

[Замечание]Замечание

Для машин Windows, все лежащие в основе модули написаны в PowerShell, средстве автоматизации Microsoft и её инфраструктуре управления настройкой.

Мы установим Ansible на свою виртуальную машину Ubuntu, а за инструкциями по установке на прочих операционных системах проверьте наличие документации по установке (http://docs.ansible.com/ansible/intro_installation.html). Далее вы увидите все шаги для установки необходимых программных пакетов.


$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible
 	   
[Замечание]Замечание

У вас может возникнуть соблазн просто воспользоваться для установки pip install ansible, однако далее это может повлечь определённые проблемы. Следуйте всем инструкциям по установке на страницах документации Ansible для своей операционной системы.

Теперь вы можете выполнить быструю проверку:


$ ansible --version
ansible 2.2.1.0
 config file = /etc/ansible/ansible.cfg
 configured module search path = Default w/o overrides
 	   

Итак, мы готовы рассмотреть некий пример.

Ваш первый плейбук Ansible

Наш первый плейбук (план) будет содержать следующие шаги:

  1. Проверка того, что наш управляющий узел может применять авторизацию на основе ключа.

  2. Создание некоторого файла учёта ресурсов.

  3. Создание некоего плейбука.

  4. Его исполнение и проверка.

Авторизация общедоступного ключа

Самый первый момент подлежащий исполнению, это скопировать ваш общедоступный ключ SSH с вашей управляющей машины на нужную вам целевую машину. Полное руководство по Инфраструктуре общедоступных ключей выходит за рамки данной книги, но вото быстрая прогулка по вашему управляющему узлу:


$ ssh-keygen -t rsa <<<< создание пары ключей public-private на вашей машине хоста
$ cat ~/.ssh/id_rsa.pub <<<< копирование всего содержимого данного вывода и помещение его в файл ~/.ssh/authorized_keys на необходимом вам целевом хосте
 	   
[Замечание]Замечание

Вы можете прочесть дополнительную информацию о PKI в https://en.wikipedia.org/wiki/Public_key_infrastructure. {Прим. пер.: также см. https://ru.wikipedia.org/wiki/Инфраструктура_открытых_ключей, Приступая к работе с SSH (Kimmo Suominen), Глава 4. Сертификаты в Windows Server 2016 (Mastering Windows Server 2016), Глава 4. Работа с сертификатами (Windows Server 2016 Cookbook), Безопасная оболочка (Pro Linux System Administration, 2ed), 10.3 Безопасная оболочка (SSH) (How Linux Works, 2ed)}.

Так как мы используем аутентификацию на основании ключа, мы можем отключить аутентификацию на базе пароля в своём удалённом узле и быть в состоянии большей безопасности. Теперь вы в состоянии выполнить ssh с управляющего узла к удалённому узлу без применения своего публичного ключа и без приглашения на ввод пароля.

[Замечание]Замечание

Можете ли вы автоматизировать самое начальное копирование общедоступного ключа? Это возможно, однако очень сильно зависит от вашего варианта применения, применяемых правил, а также среды. Это сопоставимо с первоначальной установкой консоли для сетевого снаряжения с целью установки первоначальной доступности IP. Вы автоматизировали это? Почему, или почему нет?

Файл учёта ресурсов

Нам не нужен Ansible если у нас нет удалённых целей для управления, не так ли? Всё начинается с того факта, что нам необходимо управлять некоторой задачей на каком- то удалённом хосте. Тот способ, которым мы описываем такую потенциальную цель является неким файлом хоста. Мы либо уже можем иметь этот файл, определяющий такой удалённый хост где- то в /etc/ansible/hosts, либо воспользоваться опцией -i для определения расположения данного файла. Что касается лично меня, я предпочитаю иметь такой файл в том же самом каталоге, где находится мой плейбук.

[Замечание]Замечание

Технически этот файл может называться настолько длинным, насколько это допускает формат. Однако, следуя данному соглашению, вы потенциально вы можете уберечь себя и своих коллег в дальнейшем от определённой головной боли.

Файл учёта ресурсов (описи, inventory) является простым, обычным текстовым файлом в стиле INI который устанавливает ваши цели. По умолчанию это может быть либо FQDN DNS, либо адрес IP:


$ cat hosts
192.168.199.170
 	   

Теперь мы можем воспользоваться опцией командной строки для проверки Ansible и его файла хоста:


$ ansible -i hosts 192.168.199.170 -m ping
192.168.199.170 | SUCCESS => {
 "changed": false,
 "ping": "pong"
}
 	   
[Замечание]Замечание

По умолчанию Ansible предполагает что тот же самый пользователь, который исполняет данный плейбук, присутствует и на данном удалённом хосте. Например, я исполняю данный плейбук локально под именем echou; тот же самый пользователь также должен иметься на моём удалённом хосте. Если вы желаете осуществлять исполнение под другим пользователем, вы можете воспользоваться опцией -u при выполнении, то есть -u REMOTE_USER.

Предыдущая строка считывает что мы будем применять свой файл хоста в качестве необходимого файла учёта ресурсов и выполняет модуль ping на данном хосте с именем 192.168.199.170.

ping (http://docs.ansible.com/ansible/ping_module.html) является простейшим модулем проверки, который выполняет соединение с определённым удалённым хостом, проверяет применимость установки Python и возвращает в случае успеха отклик pong.

[Замечание]Замечание

Вы можете также взглянуть на список всех постоянно расширяющиеся модулей http://docs.ansible.com/ansible/list_of_all_modules.html, если у вас имеются какие- либо вопросы о применении имеющихся модулей, которые поставляются с Ansible.

Если вы получили ошибку ключа хоста, это достаточно распространено, так как данный ключ хоста не расположен в вашем файле known_hosts и обычно находится в ~/.ssh/known_hosts. Вы можете либо выполнить соединение ssh к данному хосту и ответить yes при добавлении данного хоста, или же вы можете запретить такую проверку в /etc/ansible/ansible.cfg или ~/.ansible.cfg с помощью такого кода:


[defaults]
host_key_checking = False
 	   

Теперь, когда у нас имеется проверенный файл описи, мы можем сделать свой первый плейбук (план).

Наш первый плейбук

Плейбук является планом Ansible по описанию того что бы мы хотели сделать с данными хостами при помощи модулей. Именно здесь мы будеи проводить большую часть своего времени в качестве операторов при работе с Ansible. Если мы строим некий дом для игр на дереве, плейбук будет нашим руководством, модули будут нашими инструментами, а опись ресурсов будет теми компонентами, с которыми вы будете работать применяя эти инструменты.

Плейбук разработан с целью быть доступным для чтения человеком в формате YAML. Мы взглянем на общий синтаксис и применение в следующем разделе по архитектуре Ansible {Прим. пер.: для больших подробностей обратитесь к Глава 1. Архитектура системы и проектирование Ansible нашего перевода 2-го издания "Полного руководства Ansible"}. На данный момент мы сосредоточимся на исполнении некоторого примера чтобы рассмотреть и почувствовать Ansible.

[Замечание]Замечание

Первоначально под YAML понимался Yet Another Markup Language (Ещё один язык разметки), однако в настоящее время, yaml.org, перепрофилировал этот акроним на YAML Ain't Markup Language (YAML не язык разметки) для своей управляемой данными сущности.

Давайте взглянем на простой плейбук в 6 строк, df_playbook.yml:


---
- hosts: 192.168.199.170

  tasks:
  - name: проверить использование диска
    shell: df > df_temp.txt
 	   

В любом заданном плейбуке вы можете иметь множество воспроизведений (play). В данном случае у нас есть одно воспроизведение (строки 2 - 6). В данном воспроизведении может присутствовать множество задач, но у нас также есть только одна (строки 4 - 6) с названием, описывающим основную цель данного задания и модуль shell, получающий в качестве аргумента df. Модуль shell берёт команду из своего аргумента и выполняет её на определённом удалённом хосте. В данном случае мы исполняем команду df для проверки используемости диска и копирования вывода в некий файл с названием df_temp.txt.

Мы можем исполнить данный плейбук посредством такого кода:


$ ansible-playbook -i hosts df_playbook.yml
PLAY [192.168.199.170] *********************************************************

TASK [setup] *******************************************************************
ok: [192.168.199.170]

TASK [check disk usage] ************************************************
changed: [192.168.199.170]

PLAY RECAP *********************************************************************
192.168.199.170 : ok=2 changed=1 unreachable=0 failed=0
 	   

Если вы зарегистрируетесь на данном управляемом хосте (у меня 92.168.199.170), вы обнаружите что там будет иметься файл df_temp.txt, содержащий вывод команды df. Клёво, да?

Заметим, что на самом деле имелись исполненными две задачи, даже хотя мы определили только одну задачу в данном плейбуке; сам модуль установки автоматически добавляется по умолчанию. Он исполняется Ansible для получения информации о данном удалённом хосте, и которая может применяться позже в данном плейбуке. Например, один из тех фактов, который получил модуль установки это сведения об операционной системе. Данный плейбук может в дальнейшем указать применять apt для хостов на основе Debian и yum для хостов на основе Red Hat для установки новых пакетов.

[Замечание]Замечание

Если вы любопытствуете по поводу имеющегося вывода некоего модуля установки, вы можете обнаружить какую информацию получает Ansible через $ ansible -i hosts <host> -m setup.

Под капотом имеется ряд моментов, которые происходят для задач, например копирование определённого сценария Python, копирование полученного вывода файла в некий временный файл, затем перехват данного вывода и удаление сделанного временного файла. На текущий момент мы можем безопасно игнорировать все эти лежащие в основе подробности, пока они нам не потребуются.

Важно чтобы мы полностью поняли тот простой процесс, который мы только что прошли, так как мы будем возвращаться к этим элементам позже в данной главе. Я целенаправленно выбрал некий пример сервера для представления здесь, так как они будут иметь больше смысла по мере того, как мы будем погружаться в свои сетевые модули и когда нам понадобится отклоняться от них (помните, мы упоминали, что интерпретатор Python, скорее всего, не входит в состав ваших сетевых средств?)

Поздравляем с выполнением вашего первого плейбука Ansible! Мы ознакомимся дополнительно с архитектурой Ansible, однако на текущий момент давайте рассмотрим почему Ansible хорошо подходит для сетевого управления. Помните, что Ansible написан на Python, поэтому раз мы более знакомы с данной инфраструктурой, мы можем применять Python для ей расширения!

Преимущества Ansible

Помимо Ansible имеется множество базовых конструкция автоматизации инфраструктуры, а именно Chef, Puppet и SaltStack {Прим. пер.: CFEngine, Otto, xCAT и пр.}. Каждая такая структура предлагает свои собственные уникальные свойства и модели и при этом нет правильных инструментов, которые подходят всем организациям. В данном разделе я бы хотел перечислить некоторые из имеющихся преимуществ Ansible и почему я рассматриваю его как некий хороший инструмент для автоматизации сетевой среды.

С точки зрения сетевой инженерии, а не просто с одной из перечисленных ниже позиций, я полагаю что именно сочетание всех приводимых причин делают Ansible идеальным для автоматизации сетевых сред.

Отсутствие агента

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

Так как на обслуживаемом удалённом хосте нет никакого агента, Ansible применяет модель помещения (push) для активной доставки всех изменений в обрабатываемое устройство, что противоположно модели извлечения (pull), при которой сам агент изымает всю информацию из имеющегося сервера хозяина (master). Модель активной доставки (push), с моей точки зрения, более предсказуема (deterministic), поскольку всё появляется с самой управляющей машины.

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

Идемпотентность

Соглсно Википедии, идемпотентность является тем свойством определённой операции с математике и вычислительной науке, которое может применяться множество раз без изменения конечного результата при выходе за рамки своего начального применения (https://en.wikipedia.org/wiki/Idempotence {Прим. пер.: идемпотентность}). Говоря более простыми словами, это означает, что исполнение одной и той же продудуры снова и снова не изменяет данную систему после самого первого применения. {Прим. пер.: например, сложение с нулём, операция модуля числа или определения остатка от деления, вычисление наибольшего общего делителя и т.п.}. Ansible слу.ит цели быть индеподентным, что хорошо для сетевых операций, требующих определённой упорядоченности в операциях.

Преимущество индепотентности лучше сравнить со сценариями Pexpext и Paramiko, которые мы уже написали. Помните, что эти сценарии бвли написаны для активной доставки команд, как если бы некий инженер сидел за своим терминалом. Если бы вы исполнили этот сценарий 10 раз, то получили бы 10 внесений изменений таким сценарием. В случае с Ansible плейбуком, выполняющим те же самые изменения, что и подход с Pexpext и Paramiko, вначале будет проверена имеющаяся настройка устройства и данный плейбук исполнится только если эти изменения отсутствуют.

Наличие иденпотентности означает, что мы можем повторно выполнять данный плейбук без какого бы то ни было беспокойства о том, что будут выполнены не нужные изменения. Это будет важно, поскольку нам необходимо автоматически проверять непротиворечивость состояния без каких бы то ни было дополнительных накладных расходов.

Простота и расширяемость

Ansible написан на Python и использует YAML для языка плейбуков (планов), причём оба рассматриваются как относительно простые для изучения. Помните синтаксис Cisco IOS? Именно этот особый для отрасли язык был единственно применимым при управлении вми устройств Cisco IOS или прочим аналогичным структурированным оборудованием; этот язык совсем не бл языком для общих целей за пределами ограниченной сферы деятельности. К счастью, в отличии от прочих инструментов автоматизации, нет никакого дополнительного особенного для данной области языка или DSL для изучения Ansible, так как YAML и Python оба являются широко применяемыми языками.

Как вы могди видеть из предыдущего примера, даже если вы никогда раньше не встречали YAML, легко просто догадаться что данный плейбук пытается сделать. Помимо прочего, Ansible пользуется Jinja2 в качетве механизма шаблонов, который является достаточно распространённым инструсентом, применяемым веб структурами Python, такими как Django и Falsk, поэтому данное знание переносимо.

Я не могу не сказать достаточно об расширяемости Ansible. Как проиллюстрировано в приведённом примере, Ansible начинался имея на уме автоматизацию серверов (в первую очередь Linux). Затем он ответвился на управление машинами Windows при помощи PowerShell. Поскольку всё больше и больше людей в данной отрасли начинали принимать Ansible, сетевая среда стала той темой, которая стала привлекать всё большее внимание. В Ansible были наняты и заимствованы правильные люди и команды, начали вовлекаться профессионалы сетевых сред, а пользователи стали запрашивать у производителей поддержку. Начиная с Ansible 2.0, работа с сетевыми средами стала гражданином первого класса наравне с управлением серверами. Вся экосистема живая и здоровая.

В точности как и сообщество Python, сообщество Ansible дружественно и его позиция состоит в вовлечении новых сообщников и идей. У меня есть опыт работы в команде новичков и попытка понять как стать участником и как писать модули. Я могу засвидетельствовать, что меня всегда приветственно встречали и отдавали дань уважения моему мнению.

Простота и расширяемость в действительности говорят много хорошего о доказательстве будущего. Весь технологический мир быстро развивается, и мы непрерывно пытаемся к нему приспосабливаться. Разве не здорово обучиться некоторой технологии один раз и продолжать применять её независимо от последних тенденций? Очевидно, что никто не обладает кристаллическим шаром для точного предсказания своего будущего, однако послужной список Ansible положительно свидетельствует о его будущем приспособлении к окружению.

Поддержка производителями

Посмотри м правде в глаза, мы не живём в вакууме. Мы просыпаемся каждый день с необходимостью иметь дело с сетевым оборудованием, выпускаемым различными производителями. Мы видели разницу между Pexpect и API в предыдущих главах и такая интеграция API, несомненно, не возникла из чего- то невидимого. Всякому производителю необходимо вкладываться временем и инженерными ресурсами в осуществление интеграции. Готовность определённого производителя поддерживать технологию имеет большое значение в нашем мире. К счастью, все крупные поставщики поддерживают Ansible, о чём ясно свидетельствуют всё возрастающая доступность сетевых модулей (http://docs.ansible.com/ansible/list_of_network_modules.html).

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

Тот факт, что Ansible основывается на Python, языке, который нравится многим сетевым профессионалам, является другим великим движителем для поддержки производителей. Что касается таких производителей, как Juniper и Arista, которые уже сделали существенные инвестиции в PyEZ и Pyeapi, они могут легко получить усиление от всех существующих модулей и быстро интегрировать свои свойства в Ansible. Как вы увидите в нашей следующей главе, мы можем применять свои имеющиеся знания Python чтобы легко писать свои собственные модули.

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

Другой причиной для возрастающей поддержки сетевых производителей также связана с возможностью Ansible предоставить поставщикам возможность выражать свою силу в контексте данного модуля. В следующем разделе мы увидим, что помимо SSH, определённый модуль Ansible может исполняться локально и взаимодействовать с такими устройствами применяя API. Это гарантирует, что такие поставщики могут отразить свои самые последние и самые лучшие функции как только они сделают свои API доступными. С точки зрения сетевых специалистов это означает, что вы можете применять эти функции при выборе поставщиков при использовании Ansible в качестве платформы автоматизации.

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

Архитектура Ansible

Архитектура Ansible состоит из плейбуков, воспроизведений и задач. Давайте взглянем на df_playbook.yml, который мы уже разглядывали:

 

Рисунок 1


Плейбук Ansible

Весь данный файл называется плейбуком (планом) и он содержит одно или более воспроизведений (play). Каждое воспроизведение состоит из одной или более задач. В нашем простом примере у нас имеется только одно воспроизведение, которое, в свою очередь, состоит из единственной задачи. В данном разделе мы рассмотрим следующее:

  • YAML: Этот формат интенсивно применяется в Ansible для выражения плейбуков и переменных.

  • Inventory (Учёт ресурсов): Опись, в которой вы можете определять хосты и группы хостов из своей инфраструктуры. Вы также можете опционально определять в данном файле учёта ресурсов переменные хостов и групп.

  • Variables: Всякое сетевое устройство является особенным. Оно имеет отличительное имя хоста, IP, связи с соседями и так далее. Переменные позволяют нам иметь некий стандартный набор воспроизведений, всё ещё сохраняя при этом различия.

  • Templates: Шаблоны не представляют ничего нового в сетевой среде. На самом деле вы скорее всего используете их, просто не подозревая что они имеют такое название. Что мы обычно делаем, когда нам необходимо предоставить некое новое устройство или заменить RMA? Мы копируем имеющиеся старые настройки и заменяем все обнаруженные различия, такие как имя хоста и IP адрес LoopBack. Ansible поддерживает стандарт своих шаблонов в формате Jinja2, во что мы углубимся подробнее.

В следующей главе мы обсудим некоторые дополнительные разделы, такие как условные зависимости, циклы, блоки, обработчики, роли плейбуков и то, как они могут быть включены в управление сетевой средой.

YAML

YAML является синтаксисом, применяемым для плейбуков и прочих файлов в Ansible. Официальная документация YAML содержит полные спецификации синтаксиса. Вот некоторая компактная версия, относящаяся к наиболее распространённым применениям для Ansible:

  • YAML начинается с трёх тире (---)

  • Пробельный отступ применяется для отражения структуры при её выстраивании, в точности как в Python

  • Комментарии начинаются с символа "решётка" (#)

  • Список участников обозначается начинающим тире (-) при единственном участнике в строке

  • Список также может выделяться квадратными скобками ([]) с разделяемыми запятой (,) элементами.

  • Словари выражаются парой ключ:значение с двоеточием (:) для их разделения.

  • Словари могут выделяться фигурными скобками ({}) с разделяемыми запятой (,) элементами

  • Строки могут не выделяться цитированием, но также могут заключаться в двойные ("") или одинарные ('') кавычки.

Как вы можете видеть, YAML хорошо соответствует типам данных JSON и Python. Если бы я переписал свой предыдущий пример df_playbook.yml в df_playbook.json, это выглядело бы следующим образом:


[
  {
    "hosts": "192.168.199.170",
    "tasks": [
      "name": "check disk usage",
      "shell": "df > df_temp.txt"
    ]
  }
]
 	   

Очевидно, что это не допустимый плейбук, однако его цель состоит в том, чтобы помочь уяснить формат YAML при помощи сравнения с форматом JSON. По большей части, комментарии (#), списки (-) и словари (ключ:значение) являются тем, что вы видите в некотором плейбуке.

Учёт ресурсов

По умолчанию Ansible просматривает файл /etc/ansible/hosts на наличие хостов, определённых в его плейбуке. Как уже упоминалось, я нахожу более вразумительным определять такой файл хостов при помощи параметра -i, как мы это делали до данного момента. Чтобы расширить свой предыдущий пример, мы можем составить свой файл инвентаризации хостов таким образом:


[ubuntu]
192.168.199.170

[nexus]
192.168.199.148
192.168.199.149

[nexus:vars]
username=cisco
password=cisco

[nexus_by_name]
switch1 ansible_host=192.168.199.148
switch2 ansible_host=192.168.199.149
 	   

Как мы можем догадаться, озаглавливающие квадратные скобки определяют названия групп, поэтому позже в своём плейбуке мы можем указывать именно эту группу. Например, в cisco_1.yml и cisco_2.yml я могу осуществить соединение со всеми хостами определёнными под названием группы nexus указав в качестве имени группы nexus:


---
- name: Configure SNMP Contact
  hosts: "nexus"
  gather_facts: false
  connection: local
<пропуск>
 	   

Некий хост может присутствовать более чем в одной группе. Группа может также иметь вложения с потомками:


[cisco]
router1
router2

[arista]
switch1
switch2

[datacenter:children]
cisco
arista
 	   

В предыдущем примере группа datacenter содержит и участников cisco и arista.

В своём следующем разделе мы обсудим переменные. Но вы можете также опционально определять переменные, относящиеся к имеющимся хостам и группам в своём файле учёта ресурсов. В нашем примере файла описи, [nexus:vars] определяет переменные для всей группы nexus. Переменная ansible_host определяет изменяемое значение для всех имеющихся хостов в той же самой строке.

Для получения дополнительной информации по файлу учёта ресурсов обращайтесь к официальной документации http://docs.ansible.com/ansible/intro_inventory.html {Прим. пер.: и нашему переводу 2го издания Полного руководства Ansible Джесса Китинга}.

Переменные

Мы немного обсудили переменные в разделе учёта ресурсов. Так как наши управляемые узлы не совпадают в точности, нам необходимо приспосабливать имеющиеся отличия через переменные. Имена переменных должны состоять из буквенных символов, цифр и знаков подчёркивания, причём всегда начинаться с некоторой буквы. Переменные обычно определяются в трёх местах:

  • Плейбуке

  • Файле учёта ресурсов

  • Отдельных файлов и ролей в виде файлов, подлежащих включению.

Давайте взглянем на некоторый пример определения переменных в некотором плейбуке cisco_1.yml:


---
- name: Configure SNMP Contact
  hosts: "nexus"
  gather_facts: false
  connection: local

vars:
  cli:
    host: "{{ inventory_hostname }}"
    username: cisco
    password: cisco
    transport: cli

  tasks:
    - name: configure snmp contact
      nxos_snmp_contact:
        contact: TEST_1
        state: present
        provider: "{{ cli }}"
	
      register: output

    - name: show output
      debug:
        var: output
 	   

Вы можете видеть конкретное определение переменной cli в имеющемся разделе vars, которое применяется в вашей задаче nxos_snmp_contact.

[Замечание]Замечание

Для получения дополнительной информации по модулю nxos_snmp_contact проверьте информацию в документации интернет- ресурса http://docs.ansible.com/ansible/nxos_snmp_contact_module.html.

Чтобы ссылаться не некую переменную, вы пользуетесь соглашениями системы шаблонов Jinja2 двойных фигурных скобок. Вам не нужно помещать кавычки вокруг этих фигурных скобок, только если вы не начинаете с них некоторое значение. Я обычно нахожу более простым помещать кавычки в любом случае.

Вы также можете заметить присутствие ссылки {{ inventory_hostname }}, которая не объявлена в данном плейбуке. Это одна из определённых по умолчанию переменных, которую Ansible предоставляет автоматически и иногда её называют волшебной переменной.

[Замечание]Замечание

Имеется достаточно много волшебных переменных и вы можете найти полный список в имеющейся документации http://docs.ansible.com/ansible/playbooks_variables.html#magic-variables-and-how-toaccess-information-about-other-hosts.

В предыдущем разделе мы объявили переменные в некотором файле учёта ресурсов:


[nexus:vars]
username=cisco
password=cisco

[nexus_by_name]
switch1 ansible_host=192.168.199.148
switch2 ansible_host=192.168.199.149
 	   

Чтобы применять все переменные в данном файле учёта ресурсов вместо объявления их в своём плейбуке давайте добавим необходимые переменные групп для [nexus_by_name] в своём файле хоста:


[nexus_by_name]
switch1 ansible_host=192.168.199.148
switch2 ansible_host=192.168.199.149
[nexus_by_name:vars]
username=cisco
password=cisco
 	   

Затем измените этот плейбук, cisco_2.yml чтобы ссылаться на данные переменные:


---
- name: Configure SNMP Contact
  hosts: "nexus_by_name"
  gather_facts: false
  connection: local

  vars:
    cli:
      host: "{{ ansible_host }}"
      username: "{{ username }}"
      password: "{{ password }}"
      transport: cli

  tasks:
    - name: configure snmp contact
      nxos_snmp_contact:
        contact: TEST_1
        state: present
        provider: "{{ cli }}"

      register: output

    - name: show output
      debug:
        var: output
 	   

Отметим, что в данном примере мы ссылаемся на имеющуюся в файле учёта ресурсов группу nexus_by_name, переменную хоста ansible_host и групповые переменные имени пользователя и пароля. Это является хорошим способом сокрытия имеющихся имени пользователя и пароля в некотором защищённом на запись файле и публиковать данный плейбук без опасения выставления на обзор ваших чувствительных данных.

[Замечание]Замечание

Чтобы ознакомиться с дополнительными примерами переменных обратитесь к документации Ansible http://docs.ansible.com/ansible/playbooks_variables.html.

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


TASK [show output]
*************************************************************
ok: [switch1] => {
 "output": {
  "changed": false,
    "end_state": {
      "contact": "TEST_1"
    },
  "existing": {
     "contact": "TEST_1"
    },
  "proposed": {
     "contact": "TEST_1"
    },
  "updates": []
 }
}
 	   

Для получения доступа к вложенным данным мы можем применить следующую нотацию, описанную в cisco_3.yml:


msg: '{{ output["end_state"]["contact"] }}'
msg: '{{ output.end_state.contact }}'
 	   

Вы получите только указанное значение:


 TASK [show output in output["end_state"]["contact"]]
 ***************************
ok: [switch1] => {
 "msg": "TEST_1"
} 
ok: [switch2] => {
 "msg": "TEST_1"
} 

TASK [show output in output.end_state.contact]
*********************************
ok: [switch1] => {
 "msg": "TEST_1"
} 
ok: [switch2] => {
 "msg": "TEST_1"
}
 	   

Наконец, мы упомянули что переменные также могут храниться в некотором отдельном файле. Чтобы посмотреть как мы можем использовать переменные в файле роли или включаемом файле, нам следует получить немного дополнительных примеров к себе на поясной ремень, так как они слегка более сложны чтобы сразу начинать с них. Мы рассмотрим примеры ролей в своей следующей главе.

Шаблоны Jinja2

В предыдущем разделе мы применили переменные с синтаксисом Jinja2 {{ variable }}. Хотя вы можете делать в Jinja2 множество сложных вещей, к счастью, для того чтобы начать нам необходимо всего несколько основных моментов.

[Замечание]Замечание

Jinja2 (http://jinja.pocoo.org/) является полностью укомплектованным мощным механизмом шаблонов для Python. Он широко применяется в веб конструкциях Python таких как Django и Flask.

На текущий момент просто достаточно иметь в виду, что Ansible использует Jinja2 в качестве механизма шаблонов. Мы повторно вернёмся к вопросам фильтров, проверок и поиска Jinja2 когда до них дойдёт дело. Вы можете найти дополнительную информацию по шаблонам Jinja2 Ansible здесь: http://docs.ansible.com/ansible/playbooks_templating.html {Прим. пер.: см. также в скором времени наш перевод Главы 3, Высвобождение всей мощи шаблонов Jinja2 2го издания "Полного руководства Ansible".}

Сетевые модули Ansible

Ansible первоначально разрабатывался для управления узлами с полными операционными системами, такими как Linux и Windows; затем он был расширен на сетевые устройства. Вы уже могли заметить тонкие отличия в плейбуках которые мы применяли до сих пор, такие как наличие строк gather_facts: false и connection: local; мы пройдёмся чуть ближе чтобы посмотреть на эти отличия.

Локальные соединения и факты

Модули Ansible являются кодом Python, исполняемым по умолчанию на удалённых хостах. Из- за того факта, что большая часть сетевого оборудования не выставляет Python напрямую, мы почти всегда исполняем свой плейбук локально. Это означает, что данный плейбук (план) вначале интерпретируется локально и команды или настройки активно доставляются (push out) позже по мере необходимости.

Напомним, что данные удалённого хоста были собраны через модуль установки, который был добавлен по умолчанию. Поскольку мы выполняем данный плейбук локально, данный модуль установки собирает все факты на своём локальном хосте вместо удалённого хоста. Это, естественно не требуется, поэтому после установки соединения в локальное мы можем уменьшить этот не нужный этап установив значение сбора фактов в Ложь.

Параметры provider

В Главе 2, Взаимодействие с сетевым устройством на нижнем уровне и в Главе 3, Работа с сетями через API и для достижения целей, с сетевым оборудованием можно было устанавливать соединение и через SSH, и через API, в зависимости от его платформы и редакции программного обеспечения. Все ключевые модули сетевых устройств реализуют некий аргумент provider, который является сборником аргументов, применяемых для определения того как соединяться с данным сетевым устройством. Некоторые модули поддерживают только cli, в то время как какие- то поддерживают прочие значения, например, Arista EAPI и Cisco NXAPI. Именно здесь демонстрируется философия Ansible "позволить производителю сиять". Определённый модуль будет иметь документацию по поводу того какой транспортный модуль поддерживается.

Некоторые из основных аргументов, поддерживаемых таким транспортом перечислены здесь:

  • host: определяет удалённый хост

  • port: определяет порт для соединения

  • username: это имя того пользователя, который должен иметь аутентификацию

  • password: это значение пароля для аутентификации

  • transport: это тип транспорта для данного соединения

  • authorize: включает эскалацию привилегий для требующих этого устройств

  • auth_pass: определяет пароль необходимой эскалации привилегий

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

Эти аргументы просто переменные, поэтому они следуют тем же самым правилам для старшинства переменных. Например, если я изменил cisco_3.yml на cisco_4.yml, то наблюдаю следующее старшинство:


---
- name: Configure SNMP Contact
  hosts: "nexus_by_name"
  gather_facts: false
  connection: local

  vars:
    cli:
    host: "{{ ansible_host }}"
    username: "{{ username }}"
    password: "{{ password }}"
    transport: cli

  tasks:
    - name: configure snmp contact
      nxos_snmp_contact:
        contact: TEST_1
        state: present
        username: cisco123
        password: cisco123
        provider: "{{ cli }}"

      register: output

    - name: show output in output["end_state"]["contact"]
      debug:
        msg: '{{ output["end_state"]["contact"] }}'

    - name: show output in output.end_state.contact
      debug:
        msg: '{{ output.end_state.contact }}'
 	   

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


PLAY [Configure SNMP Contact]
**************************************************
TASK [configure snmp contact]
**************************************************
fatal: [switch2]: FAILED! => {"changed": false, "failed": true,
"msg": "failed to connect to 192.168.199.149:22"}
fatal: [switch1]: FAILED! => {"changed": false, "failed": true,
"msg": "failed to connect to 192.168.199.148:22"}
to retry, use: --limit
@/home/echou/Master_Python_Networking/Chapter4/cisco_4.retry

PLAY RECAP
*********************************************************************
switch1 : ok=0 changed=0 unreachable=0 failed=1
switch2 : ok=0 changed=0 unreachable=0 failed=1
 	   

Пример Cisco Ansible

Поддержка Cisco в Ansible разбита на категории по операционным системам: IOS, IOSXR и NXOS. Мы уже видели ряд примеров NXOS, поэтому в данном разделе давайте попробуем управлять устройствами на основе IOS.

Наш файл хоста будет содержать два хоста, R1 и R2:


[ios_devices]
R1 ansible_host=192.168.24.250
R2 ansible_host=192.168.24.251

[ios_devices:vars]
username=cisco
password=cisco
 	   

Наш плейбук cisco_5.yml будет применять модуль ios_command для выполнения произвольных команд отображения:


---
- name: IOS Show Commands
  hosts: "ios_devices"
  gather_facts: false
  connection: local

  vars:
    cli:
      host: "{{ ansible_host }}"
      username: "{{ username }}"
      password: "{{ password }}"
      transport: cli

    tasks:
      - name: ios show commands
        ios_command:
          commands:
            - show version | i IOS
            - show run | i hostname
          provider: "{{ cli }}"

        register: output

      - name: show output in output["end_state"]["contact"]
        debug:
          var: output
 	   

Тот результат, который мы могли бы ожидать при выводе данной команды отображения:


$ ansible-playbook -i ios_hosts cisco_5.yml

PLAY [IOS Show Commands]
*******************************************************

TASK [ios show commands]
*******************************************************
ok: [R1]
ok: [R2]

TASK [show output in output["end_state"]["contact"]]
***************************
ok: [R1] => {
 "output": {
 "changed": false,
 "stdout": [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 12.4(25g), RELEASE SOFTWARE (fc1)",
 "hostname R1"
 ],
 "stdout_lines": [
 [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 12.4(25g), RELEASE SOFTWARE (fc1)"
 ],
 [
 "hostname R1"
 ]
 ]
 }
} 
ok: [R2] => {
 "output": {
 "changed": false,
 "stdout": [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 12.4(25g), RELEASE SOFTWARE (fc1)",
 "hostname R2"
 ],
 "stdout_lines": [
 [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 12.4(25g), RELEASE SOFTWARE (fc1)"
 ],
 [
 "hostname R2"
 ]
 ]
 }
}
 
PLAY RECAP
*********************************************************************
  R1 : ok=2 changed=0 unreachable=0 failed=0
  R2 : ok=2 changed=0 unreachable=0 failed=0
 	   

Я бы хотел указать на несколько моментов проиллюстрированных в данном примере:

  • Сам плейбук при сравнении NXOS и IOS в основном идентичен

  • Имеющийся синтаксис модулей nxos_snmp_contact и ios_command следует тому же самому шаблону с единственной разницей между самими аргументами для этих модулей

  • Версия IOS для данного устройства достаточно старая с отсутствием распознавания API, однако все модули всё ещё будут иметь тот же самый вид и восприятие

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

Пример Juniper Ansible

Модулю Ansible Juniper необходим пакет PyEZ Juniper и NETCONF. Если вы проследовали рецептом из примера API Главе 3, Работа с сетями через API и для достижения целей, вы готовы продолжить. Если нет, вернитесь назад в раздел за инструкциями по установке, а также некоторыми сценариями проверки, чтобы убедиться в работоспособности PyEZ. Также необходим пакет Python с названием jxmlease:


$ sudo pip install jxmlease
 	   

В своём файле хостов мы определим все устройства и переменные соединений:


[junos_devices]
J1 ansible_host=192.168.24.252

[junos_devices:vars]
username=juniper
password=juniper!
 	   

В своём модуле проверки мы применим модуль junos_facts для сбора основных фактов для данного устройства. Этот модуль эквивалентен модулю установки и становится удобным если нам необходимо предпринять действия в зависимости от возвращаемого результата. Отметим другие значения транспорта и порта в привозимом здесь примере:


---
- name: Get Juniper Device Facts
  hosts: "junos_devices"
  gather_facts: false
  connection: local

  vars:
    netconf:
    host: "{{ ansible_host }}"
    username: "{{ username }}"
    password: "{{ password }}"
    port: 830
    transport: netconf

  tasks:
    - name: collect default set of facts
      junos_facts:
        provider: "{{ netconf }}"

      register: output

    - name: show output
      debug:
        var: output
 	   

После исполнения вы получите такой вывод от устройства Juniper:


PLAY [Get Juniper Device Facts]
************************************************

TASK [collect default set of facts]
********************************************
ok: [J1]

TASK [show output]
*************************************************************
ok: [J1] => {
  "output": {
  "ansible_facts": {
  "HOME": "/var/home/juniper",
  "domain": "python",
  "fqdn": "master.python",
  "has_2RE": false,
  "hostname": "master",
  "ifd_style": "CLASSIC",
  "model": "olive",
  "personality": "UNKNOWN",
  "serialnumber": "",
  "switch_style": "NONE",
  "vc_capable": false,
  "version": "12.1R1.9",
  "version_info": {
  "build": 9,
  "major": [
  12,
  1
  ],
  "minor": "1",
  "type": "R"
  }
  },
  "changed": false
  }
} 

PLAY RECAP
*********************************************************************
J1 : ok=2 changed=0 unreachable=0 failed=0
 	   

Пример Arista Ansible

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

Вот применяемый файл хостов:


[eos_devices]
A1 ansible_host=192.168.199.158
 	   

Сам плейбук также аналогичен тому что мы уже видели:


---
- name: EOS Show Commands
  hosts: "eos_devices"
  gather_facts: false
  connection: local

  vars:
    cli:
      host: "{{ ansible_host }}"
      username: "arista"
      password: "arista"
      authorize: true
      transport: cli

    tasks:
      - name: eos show commands
        eos_command:
          commands:
            - show version | i Arista
          provider: "{{ cli }}"

        register: output

      - name: show output
        debug:
          var: output
 	   

Весь вывод отобразит ту стандартную выдачу, которую мы могли бы ожидать из командной строки:


PLAY [EOS Show Commands]
*******************************************************

TASK [eos show commands]
*******************************************************
ok: [A1]

TASK [show output]
*************************************************************
ok: [A1] => {
 "output": {
 "changed": false,
 "stdout": [
 "Arista DCS-7050QX-32-F"
 ],
 "stdout_lines": [
 [
 "Arista DCS-7050QX-32-F"
 ]
 ],
 "warnings": []
 }
} 

PLAY RECAP
*********************************************************************
A1 : ok=2 changed=0 unreachable=0 failed=0
 	   

Выводы

В данной главе мы предприняли большой тур по структуре автоматизации с открытым исходным кодом Ansible. В отличии от сценариев сетевой автоматизации на основе Pexpect или API Ansible предоставляет более высокий уровень абстракции вызывая плейбуки (планы) для автоматизации ваших сетевых устройств.

Ansible изначально конструировался для управления серверами и позже был расширен на сетевые устройства; по этой причине мы взглянули на некий пример сервера. Затем мы провели сравнение и выделили отличия когда перешли к плейбукам для сетевых устройств. Позже мы рассмотрели примеры плейбуков для устройств Cisco IOS, Juniper JUNOS и Arista EOS.

В следующей главе мы усилим свои знания, полученные в данной главе и начнём рассматривать некоторые дополнительные свойства Ansible.