Глава 9. Расширение Ansible

Содержание

Глава 9. Расширение Ansible
Технические требования
Разработка модулей
Построение базового модуля
Индивидуальные модули
Пример - Простой модуль
Документирование модуля
Предоставление данных фактов
Режим проверки
Поддержка режима проверки
Обработка режима проверки
Разработка встраиваемых модулей
Тип подключения встраиваемого модуля
Встраиваемые модули оболочки
Встраиваемые модули поиска
Встраиваемые модули переменных
Встраиваемые модули кэширования фактов
Встраиваемые модули фильтрации
Встраиваемые модули обратного вызова
Встраиваемые модули действия
Встраиваемые модули распространения
Разработка встраиваемых модулей динамического учёта
Перечисление хостов
Перечисление переменных хостов
Простой встраиваемый модуль учёта ресурсов
Оптимизация производительности сценария
Участие в проекте Ansible
Представление содействия
Репозиторий Ansible
Исполнение тестов
Тестирование элемента
Интеграция тестов
Проверка стиля кода
Выполнение запроса в пуле
Выводы

Следует сказать, что Ansible применяет подход "кухонного слива" к функциональности и пытается предоставлять сразу после установки все те функциональные возможности, которые могут вам потребоваться. На момент написания книги существует доступными для использования внутри Ansible более 2 000 модулей - на 1 200 больше чем имелось на момент самого последнего издания публикации этой книги! Помимо этого имеется богатство подключаемых модулей и архитектур фильтрации с бесчисленными включёнными подключаемыми модулями обратных вызовов, подключаемых модулей просмотра, подключаемых модулей фильтрации и подключаемых модулей динамических описей.

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

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

  • Разработка модулей

  • Разработка подключаемых модулей

  • Разработка динамических подключаемых модулей описей

  • Внесение кода в сам проект Ansible

Технические требования

Ознакомьтесь с видеоматериалами Code in Action.

Разработка модулей

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

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

Построение базового модуля

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

Как правило, ввод предоставляется в разделяемом пробелами синтаксисе key=value и сам модуль выполняет их разбор в используемые данные. Если вы применяете Python, для управления этим существуют удобные функции, а если вы применяете иной язык программирования, тогда полная обработка ввода будет задачей самого кода модуля.

Получаемый вывод представляется в формате JSON. Соглашение диктует чтобы при успешном сценарии получаемый вывод JSON должен иметь по крайней мере один ключ, changed, который является Булевым значением, для указания того то выполнение данного модуля привело к некому изменению. Также могут возвращаться дополнительные данные, что может быть полезным для определения того что именно было изменено, либо для предоставления важной информации обратно в сам плейбук для последующего применения. Кроме того, в этих данных JSON могут возвращаться факты хоста для автоматического создания переменных хоста на основании результатов выполнения данного модуля. Мы рассмотрим это более подробно позднее, в разделе с названием Предоставление данных фактов.

Индивидуальные модули

Ansible предоставляет некий простой механизм использования индивидуальных модулей, отличающихся от тех, которые поставляются совместно с Ansible. Как мы уже изучали в Главе 1, Архитектура системы и проектирование Ansible, Ansible будет выполнять поиск в большом числе местоположений для того чтобы найти необходимый модуль. Одним из таких местоположений, и несомненно самым первейшим, является подкаталог library/ в том пути, в котором расположен самый верхний уровень нашего плейбука. Именно здесь мы и будем размещать свой индивидуальный модуль с тем, чтобы мы могли применять его в своём примере плейбука.

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

Пример - Простой модуль

Для демонстрации простоты написания модулей на основе Python, давайте создадим некий простой модуль. Основной целью данного модуля будет удалённо копирование некого исходного файла в файл назначения, некая простейшая задача, из тех что мы бы могли построить. Чтобы запустить свой модуль, нам потребуется создать файл такого модуля. Для простейшего доступа к своему новому модулю мы создадим этот файл в своём подкаталоге library/ в нашем рабочем каталоге , который мы уже применяли. Мы назовём этот модуль remote_copy.py, и для его запуска нам понадобится поместить в строку sha-bang указание на то, что данный модуль выполняется при помощи Python:


#!/usr/bin/python
#
 	   

Для модулей на основе Python имеется соглашение применять /usr/bin/python в качестве приведённого исполняемого файла. При выполнении в некой удалённой системе для выполнения данного модуля применяется настроенный надлежащим образом интерпретатор Python, поэтому не волнуйтесь если ваш Python не присутствует в данном пути. Далее, мы импортируем некую библиотеку Python, которую мы будем в дальнейшем применять в данном модуле, с названием shutil:


import shutil
 	   

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

Мы сделаем это создав некий объект AnsibleModule и предоставив ему какой- то словарь argument_spec для необходимых параметров:


def main(): 
    module = AnsibleModule( 
        argument_spec = dict( 
            source=dict(required=True, type='str'), 
            dest=dict(required=True, type='str') 
        ) 
    )
 	   

В своём модуле мы предоставляем два параметра. Первым параметром выступает source, который будет задавать файл источника для копирования. Вторым параметром является dest, значение получателя этой копии. Оба этих параметра помечены как необходимые, что возбудит некую ошибку при исполнении если хотя бы один из них не предоставлен. Оба параметра имеют тип string. Местоположение самого класса AnsibleModule пока ещё не было задано, поскольку это произойдёт позднее в данном файле.

Выставив некий объект модуля, мы теперь способны создать тот код, который и выполнит реальную работу в нашем удалённом хосте. Мы воспользуемся shutil.copy и своими предоставленными аргументами для осуществления необходимого копирования:


    shutil.copy(module.params['source'], 
                module.params['dest'])
 	   

Данная функция shutil.copy ожидает некий источник и какое- то назначение, которые мы предоставляем доступом к module.params. Наш словарь module.params содержит все требуемые модулем параметры. Завершив данное копирование, мы теперь готовы вернуть полученные результаты в Ansible. Это осуществляется через другой метод AnsibleModule, exit_json. Данный метод ожидает некий набор параметров key=value и сформирует их как положено для возврата некого JSON. Поскольку мы всегда выполняем некое копирование, мы всегда будем возвращать некое изменение для простоты:


    module.exit_json(changed=True)
 	   

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


from ansible.module_utils.basic import *
 	   

Это всё что требуется! Эта единственная строка предоставляет нам доступ ко всей основе module_utils, некому порядочному набору вспомогательных функций и классов. Имеется всего лишь последний момент, который следует поместить в наш модуль: пара строк кода, сообщающая интерпретатору о необходимости выполнения нашей функции main() при выполнении данного файла модуля:


if __name__ == '__main__': 
    main()
 	   

Теперь файл нашего модуля завершён и мы можем проверить его в неком плейбуке. Мы вызовем свой плейбук simple_module.yaml и сохраним его в том же самом каталоге что и наш каталог library/, в котором мы только что создали свой файл модуля. Для простоты мы выполним воспроизведение в localhost и применим пару имён файлов из /tmp для источника и получателя. Мы также применим некую задачу чтобы обеспечить то, что у нас имеется некий исходный файл чтобы начать:


--- 
- name: test remote_copy module 
  hosts: localhost 
  gather_facts: false 
 
  tasks: 
  - name: ensure foo
    file:
      path: /tmp/rcfoo
      state: touch

  - name: do a remote copy
    remote_copy:
      source: /tmp/rcfoo
      dest: /tmp/rcbar
 	   

Для выполнения этого плейбука мы обратимся к своему файлу mastery-hosts. Если наш файл remote_copy записан в верном местоположении, всё должно работать просто отлично, а ваш экран вывода должен выглядеть следующим образом:

 

Рисунок 9.1



Наша первая задача выполняет touch пути /tmp/rcfoo чтобы убедиться что он существует, а затем наша вторая задача применяет remote_copy для копирования /tmp/rcfoo в /tmp/rcbar. Обе задачи выполнены успешно, приводя в результате каждый раз к изменению.

Документирование модуля

Никакой модуль не может рассматриваться завершённым пока он не содержит относящуюся к выполняемой им операции документации. Документация для модуля содержится внутри самого этого модуля, а именно в особых переменных с названием DOCUMENTATION, EXAMPLES и RETURN.

Значение переменной DOCUMENTATION содержит особым образом сформированную строку, описывающую название данного модуля, номер версии, который был добавлен к Ansble (если он присутствует как положено в Ansible), короткое описание данного модуля, более пространное описание, описание параметров этого модуля, автор и сведения о лицензии, дополнительные требования, а также все прочие соображения, полезные пользователям данного модуля. Давайте добавим строку DOCUMENTATION в свой модуль под уже имеющимся оператором import shutil:


import shutil 
 
DOCUMENTATION = ''' 
--- 
module: remote_copy 
version_added: future 
short_description: Copy a file on the remote host 
description: 
  - The remote_copy module copies a file on the remote host from a given source to a provided destination. 
options: 
  source: 
    description: 
      - Path to a file on the source file on the remote host 
    required: True 
  dest: 
    description: 
      - Path to the destination on the remote host for the copy 
    required: True 
author: 
  - Jesse Keating 
'''
 	   

В действительности форматом данной строки выступает YAML, с некими ключами верхнего уровня, содержащими внутри себя хэш- структуры (то же самое что и ключ options). Каждая возможность имеет подэлемент для её описания, указывая является ли эта возможность обязательной, список всех допустимых сокращений для этой возможности, перечень статических выборов для данного параметра или указание некого значения по умолчанию для этой возможности. Имея данную строку сохранённой в нашем модуле, мы можем проверить своё форматирование чтобы гарантировать правильное построение необходимой документации. Это осуществляется посредством инструмента ansible-doc с неким параметром для указания того где обнаруживать необходимые модули. Если мы выполним его в том же самом месте, где пребывает наш плейбук, данной командой будет ansible-doc -M library/ remote_copy, а вывод будет следующим:

 

Рисунок 9.2



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

Значение строки EXAMPLES используется чтобы предоставить один или более примеров применения данного модуля, фрагментов кода соответствующей задачи, которые вы могли бы применять в неком плейбуке. Давайте добавим какой- то пример задачи для демонстрации надлежащего использования. Это определение переменной обычно следует за определением DOCUMENTATION:


EXAMPLES = ''' 
# Example from Ansible Playbooks 
- name: backup a config file 
  remote_copy: 
    source: /etc/herp/derp.conf 
    dest: /root/herp-derp.conf.bak 
'''
 	   

С применением этой переменной вывод нашего ansible-doc теперь содержит и предоставленный пример, что мы можем видеть на следующем снимке экрана:

 

Рисунок 9.3



Самая последняя переменная документации, RETURN, является достаточно новым свойством документации модуля. Эта переменная используется для описания возвращаемых данных после выполнения этого модуля. Возвращаемые данные зачастую полезны в качестве регистрации переменных для последующего применения, а наличие документации о том какие данные можно ожидать возвращаемыми способно помочь разработке плейбука. Наш модуль пока не имеет никаких возвращаемых данных; следовательно прежде чем документировать что бы то ни было, нам вначале придётся возвращать данные. Это можно сделать видоизменив строку module.exit_json чтобы добавить дополнительные сведения. Давайте добавим в свой возвращаемый вывод данные значений source и dest:


    module.exit_json(changed=True, source=module.params['source'], 
                     dest=module.params['dest'])
 	   

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

 

Рисунок 9.4



Вглядываясь внимательнее в возвращаемые данные мы можем увидеть дополнительные данные к тем что мы поместили в свой модуль. Это в действительности небольшая функциональность вспомогательных функций внутри Ansible; при возврате некого набора данных, содержащего переменную dest, Ansible получает дополнительные сведения относительно этого файла назначения. Дополнительными получаемыми данными являются gid (идентификатор группы), group (название группы), mode (права доступа), uid (идентификатор пользователя), owner (имя владельца), size и state (файл, ссылка или каталог). Мы можем документировать все эти возвращаемые элементы ы своей переменной RETURN, которая добавляется после соответствующей переменной EXAMPLES. Возвращается всё содержащееся между двумя наборами одиночных кавычек (''') - следовательно, данная первая часть возвратит значение пути файла и владельца:


RETURN = ''' 
source: 
  description: source file used for the copy 
  returned: success 
  type: string 
  sample: "/path/to/file.name" 
dest: 
  description: destination of the copy 
  returned: success 
  type: string 
  sample: "/path/to/destination.file" 
gid: 
  description: group ID of destination target 
  returned: success 
  type: int 
  sample: 502 
group: 
  description: group name of destination target 
  returned: success 
  type: string 
  sample: "users" 
uid: 
  description: owner ID of destination target 
  returned: success 
  type: int 
  sample: 502 
owner: 
  description: owner name of destination target 
  returned: success 
  type: string 
  sample: "fred"
 	   

Продолжим эту часть своего модуля описания файла, данный раздел возвращает сведения относительно размера файла, состояния и прав доступа:


mode: 
  description: permissions of the destination target 
  returned: success 
  type: int 
  sample: 0644 
size: 
  description: size of destination target 
  returned: success 
  type: int 
  sample: 20 
state: 
  description: state of destination target 
  returned: success 
  type: string 
  sample: "file" 
'''
 	   

Все возвращаемые элементы перечисляются с неким описанием, тем вариантом когда этот элемент будет присутствовать в своих возвращаемых сведениях, значением типа этого элемента, а также неким образцом его значения. Значение строки RETURN на самом деле дословно повторяется в получаемом выводе ansible-doc, как это показано в следующем (усечённом) примере:

 

Рисунок 9.5



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

Предоставление данных фактов

Аналогично данным, возвращаемым как часть некого exit модуля, какой- то модуль может непосредственно создавать факты для хоста, возвращая данные в ключе с названием ansible_facts. Предоставление фактов напрямую из некого модуля исключает необходимость регистрации всего что возвращает задача с последующей задачей set_fact. Для демонстрации такого применения давайте изменим с тем чтобы выполнять возврат своих данных source и dest в виде фактов. Поскольку такие факты становятся переменными верхнего уровня хоста, нам будет желательно применять более содержательные названия нежели source и dest - измените текущее значение строки module.exit_json в своём модуле приводимым здесь кодом:


    facts = {'rc_source': module.params['source'], 
             'rc_dest': module.params['dest']} 
 
    module.exit_json(changed=True, ansible_facts=facts)
 	   

В свой плейбук мы также добавим некую задачу для применения одного из получаемых фактов в операторе debug:


- name: show a fact 
    debug: 
      var: rc_dest
	  

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

 

Рисунок 9.6



Если наш модуль не возвращает факты (а наша предыдущая версия remote_copy.py не могла), нам придётся зарегистрировать свой вывод и воспользоваться set_fact для создания этого факта для нас, как это показано в приводимом ниже коде:


  - name: do a remote copy 
    remote_copy: 
      source: /tmp/rcfoo 
      dest: /tmp/rcbar 
    register: mycopy 
 
  - name: set facts from mycopy 
    set_fact: 
      rc_dest: "{{ mycopy.dest }}"
	  

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

Режим проверки

Начиная с самых первых дней своего существования, Ansible поддерживал режим проверки (check mode), некий образ действий, который симулирует выполнение изменений в некой системе без реального изменения данной системы. Режим проверки полезен для тестирования того произойдёт ли на самом деле некое изменение, либо некое состояние системы будет отклонено после самого последнего исполнения Ansible. Режим проверки зависит от поддерживающих его модулей и возвращает данные как если бы на самом деле выполнялось данное изменение. Сопровождение режима проверки в нашем модуле требует двух изменений; первое состоит в указании того что этот модуль поддерживает режим проверки, а второе заключается в выявлении того когда режим проверки активен и возвращении данных перед исполнением.

Поддержка режима проверки

Чтобы указать что некий модуль поддерживает режим проверки, при создании объекта данного модуля следует установить некий параметр. Это можно сделать до или после определения значения переменной argument_spec; в данном случае мы сделаем это после его определения:


    module = AnsibleModule( 
        argument_spec = dict( 
            source=dict(required=True, type='str'), 
            dest=dict(required=True, type='str') 
        ), 
        supports_check_mode=True 
    )
	  

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

Обработка режима проверки

Определение того когда режим проверки активен очень простое.Значение объекта модуля имеет некий атрибут argument_spec, который буде установлен в Булево значение true в случае активности режима проверки. Для своего модуля мы желаем определять активен ли режим проверки до выполнения самого копирования. Мы можем просто поместить действие копирования в некий оператор if чтобы избегать копирования в случае активности режима проверки. Помимо приводимых ниже, больше никакие изменения не требуются в нашем модуле:


    if not module.check_mode: 
        shutil.copy(module.params['source'], 
                    module.params['dest'])
	  

Теперь у нас есть возможность исполнения своего плейбука и добавления в наше выполнение значения параметра -C. Данный параметр задействует режим проверки. Мы также выполним проверку чтобы гарантировать что наш плейбук в действительности не создал и не скопировал эти файлы. Давайте взглянем на следующий снимок экрана:

 

Рисунок 9.7



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

Разработка встраиваемых модулей

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

Тип подключения встраиваемого модуля

Всякий раз когда Ansible выполняет соединение с хостом для выполнения некой задачи, применяется некий встраиваемый модуль (plugin) соединения. Ansible поставляется с несколькими встраиваемыми модулями соединений, влючая ssh, docker, chroot, local и smart. Ansible может применять дополнительные механизмы соединений для подключения систем путём создания некого встраиваемого модуля соединения, что может быть полезным когда мы сталкиваемся с подключением нового типа системы, такой как сетевой коммутатор, или, в один из дней холодильник. Создание встраиваемых модулей слегка выходит за рамки данной книги; тем не менее, самый простой способ для тогго чтобы начать состоит в разборе существующих встраиваемых модулей, которыми снабжается Ansible, и если это требуется, возьмите один из них за основу. Такие существующие встраиваемые модули можно найти в plugins/connection/, куда бы не устанавливались в вашей системе библиотеки Ansible Python, например, в моей системе это /usr/lib/python2.7/site-packages/ansible/plugins/connection.

Встраиваемые модули оболочки

Во многом как и со встраиваемыми модулями соединений, Ansible применяет встраиваемые модули оболочки (shell plugin) для исполняемых вещей в некой оболочке среды. Всякая оболочка имеет хитроумные различия, о которых и заботится Ansible чтобы выполнять команды как положено, перенаправляя вывод, выявляя ошибки и всякие прочие взаимодействия. Ansible поддерживает целый ряд оболочек, включая sh, csh, fish и powershell. Мы имеем возможность добавлять ещё оболочки, реализуя новые встраиваемые модули оболочек.

Встраиваемые модули поиска

Встраиваемые модули поиска (lookup plugins), это то как Ansible выполняет доступ к внешним источникам данных из определённой системы хостинга и как реализует свойства языков программирования, например, конструкции циклов (loop или with_*). Некий встраиваемый модуль поиска может быть создан для доступа данных из некого существующего хранилища данных, либо создавать совершенно новый механизм зацикливания. Имеющиеся механизмы нахождения можно обнаружить в plugins/lookup/. Для введения новых способов зацикливания внутри содержимого или для поиска в ресурсах внешних систем могут вводится встраиваемые модули поиска.

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

Конструкции внедрения переменных данных присутствуют в виде встраиваемых модулей переменных (vars plugin). Такие данные как host_vars и group_vars реализуются посредством встраиваемых модулей. Хотя и имеется возможность создания новых встраиваемых модулей переменных, будет лучше создать вместо этого некую индивидуальную исходную опись (inventory), либо модуль фактов.

Встраиваемые модули кэширования фактов

В последних версиях (начиная с 1.8), Ansible получил возможность кэширования фактов между запусками плейбуков. Где именно выполняется кэширование фактов, зависит от настроек применяемого модуля кэширования фактов. Ansible содержит встраиваемые модули для кэширования фактов в memory (на самом деле не кэшируется в промежутках между запусками): memcached, redis {подробнее}, а также jsonfile. Создание некого встраиваемого модуля кэширования фактов (fact-caching plugin) способно разрешать дополнительные механизмы кэширования.

Встраиваемые модули фильтрации

Хотя Jinja2 и содержит целый ряд фильтров, Ansible сделал фильтрацию встраиваемой для расширения функциональности Jinja2. Ansible содержит целый ряд фильтров, которые полузны для работы с Ansible, а пользователи Ansible могут добавлять ещё дополнительные. Имеющиеся встраиваемые модули фильтрации можно найти в plugins/filter/.

Для демонстрации процесса разработки некого встраиваемого модуля фильтрации мы создадим некий образец встраиваемого модуля фильтра для выполнения глупой вещи со строками. Мы создадим некий фильтр, который будет заменять все вхождения слов the cloud (облачное решение) на somebody else's computer (какой- то ещё компьютер). Мы определим свой фильтр в неком файле внутри какого- то нового каталога, filter_plugins/, в своём уже имеющемся рабочем каталоге. Название этого файла не имеет значения, поскольку мы определим это имя фильтра внутри самого файла; поэтому давайте озаглавим свой файл как filter_plugins/sample_filter.py.

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


def cloud_truth(a): 
    return a.replace("the cloud", "somebody else's computer")
 	   

Далее нам требуется построить некий объект FilterModule и определить внутри него свой фильтр. Этот объект является тем, что загружает Ansible, причём Ansible ожидает что в нём будет иметься функция filters, которая возвращает некое множество фильтруемых имён в функции внутри данного файла:


class FilterModule(object): 
    '''Cloud truth filters''' 
    def filters(self): 
        return {'cloud_truth': cloud_truth}
 	   

Теперь мы можем применять этот фильтр в неком плейбуке, который мы вызываем через simple_filter.yaml:


--- 
- name: test cloud_truth filter 
  hosts: localhost 
  gather_facts: false 
  vars: 
    statement: "I store my files in the cloud" 
  tasks: 
  - name: make a statement 
    debug: 
      msg: "{{ statement | cloud_truth }}"
 	   

Теперь давайте запустим свой плейбук и посмотрим на наш фильтр в действии:

 

Рисунок 9.8



Наш фильтр работает и он превращает слова the cloud в somebody else's computer. Это глупый пример без какой бы то ни было проверки ошибок, но он ясно демонстрирует наши возможности расширения возможностей фильтрации Ansible и Jinja2.

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

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

Встраиваемые модули обратного вызова

Обратные вызовы (callback) это места в исполнении Ansible, в которых может встраиваться модуль для дополнительной функциональности. Имеются ожидаемые точки обратных вызовов, которые могут регистрироваться для переключения действий пользователя в таком месте. Вот некий перечень возможных точек для переключения функциональности, имевшихся на момент написания книги:

  • v2_on_any

  • v2_runner_on_failed

  • v2_runner_on_ok

  • v2_runner_on_skipped

  • v2_runner_on_unreachable

  • v2_runner_on_no_hosts

  • v2_runner_on_async_poll

  • v2_runner_on_async_ok

  • v2_runner_on_async_failed

  • v2_runner_on_file_diff

  • v2_playbook_on_start

  • v2_playbook_on_notify

  • v2_playbook_on_no_hosts_matched

  • v2_playbook_on_no_hosts_remaining

  • v2_playbook_on_task_start

  • v2_playbook_on_cleanup_task_start

  • v2_playbook_on_handler_task_start

  • v2_playbook_on_vars_prompt

  • v2_playbook_on_setup

  • v2_playbook_on_import_for_host

  • v2_playbook_on_not_import_for_host

  • v2_playbook_on_play_start

  • v2_playbook_on_stats

  • v2_on_file_diff

  • v2_playbook_on_include

  • v2_runner_item_on_ok

  • v2_runner_item_on_failed

  • v2_runner_item_on_skipped

  • v2_runner_retry

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

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

  1. Прежде всего нам требуется сделать некий новый каталог для помещения в нём своих обратных вызовов. То местоположение, которое будет искать Ansible, это callback_plugins/. В отличие от представленного ранее встраиваемого модуля filter, нам требуется аккуратно именовать свой встраиваемый модуль обратного вызова, поскольку он также должен быть отражён и в неком файле ansible.cfg.

  2. Мы назовём свой callback_plugins/shrug.py. Внутри этого файла нам требуется создать некий класс CallbackModule, выступающий подклассом CallbackModule, определяемого во встраиваемом модуле обратного вызова default, который можно обнаружить в ansible.plugins.callback.default, так как нам требуется изменить лишь одну сторону имеющегося обычного вывода.

  3. Внутри данного класса мы задаём значения переменной для указания того что это версия 2.0 обратного вызова, а также он выступает неким видом обратного вызова stdout, наконец, то, что он имеет название shrug.

  4. Кроме того, внутри этого класса иы задаём одну или более точек обратного вызова, к которым мы бы желали подключаться чтобы делать нечто когда она возникает.

  5. Нам придётся лишь определить те точки, к которым мы желаем подключаться. В нашем случае мы подключаемся к точке v2_on_any с тем, чтобы наш подключаемый модуль запускался во всех точках обратного вызова:

    
    from ansible.plugins.callback import default
    class CallbackModule(default.CallbackModule):
      CALLBACK_VERSION = 2.0
      CALLBACK_TYPE = 'stdout'
      CALLBACK_NAME = 'shrug'
      def v2_on_any(self, *args, **kwargs):
        msg = '\xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf'
        self._display.display(msg.decode('utf-8') * 8)
     	   
  6. Поскольку этот обратный вызов является stdout_callback, нам необходимо создать некий файл ansible.cfg, а внутри него указать что следует применять обратный вызов shrug stdout. Этот файл ansible.cfg можно найти в /etc/ansible/ или в том же самом каталоге, что и сам плейбук:

    
    [defaults] 
    stdout_callback = shrug
     	   
  7. И это всё что нам требуется написать в своём обратном вызове. После его сохранения мы можем вернуться к своему предыдущему плейбуку, который задействует наш sample_filter, но в этот раз мы обнаружим на своём экране нечто иное:

     

    Рисунок 9.9



Это очень глупо, но демонстрирует имеющуюся возможность встраивания в различные точки некого исполнения плейбука. Мы выбрали отображение на экране последовательностей пожатий плечами (shrugs), но мы также запросто можем взаимодействовать с какой- то системой внутреннего аудита и контроля чтобы записывать действия или сообщать о ходе выполнения в IRC или канале Slack.

Встраиваемые модули действия

Встраиваемые модули действия (action plugin) существуют для зацепления за определённую задачу без реального выполнения модуля или для локального выполнения кода в неком хосте Ansible перед выполнением модуля в каком- то удалённом хосте. Вместе в Ansible включён целый ряд встраиваемых модулей действия, которые можно обнаружить в plugins/action/. Одним из таких встраиваемых модулей действия является встраиваемый модуль template, применяемый вместо модуля template. Когда некий автор плейбука пишет какую- то задачу template, эта задача на самом деле будет вызывать для выполнения имеющейся работы такой встраиваемый модуль template. Этот встраиваемый модуль, помимо прочих вещей, выстроит данный шаблон локально до того как скопирует имеющееся содержимое в необходимый удалённый хост. Так как действия должны произойти локально, сама работа выполняется неким встраиваемым модулем действия. Другой встраиваемый модуль, с которым вы уже знакомы, является встраиваемый модуль debug, который мы интенсивно применяем в данной книге для вывода содержимого. При попытке выполнения работы как локально, так и удалённо в одной и той же задаче, будет полезно создать некий индивидуальный встраиваемый модуль действия.

Встраиваемые модули распространения

Во многом аналогично тому как распространяются индивилуальные модули, существуют стандартные места где помещаются индивидуальные встраиваемые модули совместно с плейбуками, которые, как ожидается, применяют эти встраиваемые модули. Местоположениями по умолчанию для встраиваемых модулейц являются те местоположения, которые поставляются вместе с установкой самого кода Ansible, подкаталоги внутри ~/.ansible/plugins/, а также подкаталоги корня самого проекта (того места, в котором хранится самый верхний уровень плейбука). Подключаемые модули могут распространяться внутри тех же самых подкаталогов что и роли. Для применения встриаваемых модулей из любого местоположения нам требуется задать само местоположение для поиска подключаемого модуля конкретного вида подключаемых модулей в неком файле ansible.cfg.

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

  • action_plugins/

  • cache_plugins/

  • callback_plugins/

  • connection_plugins/

  • shell_plugins/

  • lookup_plugins/

  • vars_plugins/

  • filter_plugins/

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

Разработка встраиваемых модулей динамического учёта

Встраиваемые модули описи (inventory plugin) являются небольшим кодом, который создаст данные инвентаризации для некого исполнения Ansible. Во многих средах имеющихся образцов файлов стиля описи источника ini не достаточно для предоставления подлежащей управлению имеющейся в действительности инфраструктуры. В таком случае желательна динамическая инвентаризация источника при которой выявляется содержимое описи и данные времени исполнения при каждом выполнении Ansible. Целый ряд таких динамических источников поставляется с самим Ansible, в первую очередь для работы Ansible с той инфраструктурой, которая встроена в ту или иную облачную вычислительную платформу. Некий краткий, далеко не полный список встраиваемых модулей инвентаризации, поставляемого совместно с Ansible (которых в настоящее время имеется более 20) содержит такие:

  • apache-libcloud

  • cobbler

  • console_io

  • digital_ocean

  • docker

  • ec2

  • gce

  • libvirt_lxc

  • linode

  • openshift

  • openstack

  • rax

  • vagrant

  • vmware

  • windows_azure

Некий встраиваемый модуль на самом деле это просто исполняемый сценарий. Ansible вызывает этот сценарий с набором параметров (--list или --host <hostname>) и ожидает форматированный в JSON вывод на STDOUT. Когда предоставляется некий параметр --list, Ansible ожидает перечень названий подлежащих управлению групп. Каждая из групп может перечислять хосты участников, дочернего участника группы, а также переменные данные. Когда этот сценарий вызывается с паркаметром --host <hostname>, Ansible ожидает возврата специфичных для хоста данных (либо пустой словарь JSON).

Применение источника динамической описи простое. Источник может использоваться напрямую через ссылку на него параметром -i (--inventory-file) в ansible и ansible-playbook, помещая необходимый встраиваемый модуль внутри того каталога, на который выполняется ссылка, либо через путь инвентаризации в ansible.cfg.

Прежде чем создавать некий встраиваемый модуль описи, нам следует разобраться с ожидаемым форматом для того, когда с нашим сценарием применяется --list или --host.

Перечисление хостов

Когда в некий сценарий описи передаётся значение параметра --host, Ansible ожидает соответствующие данные JSON на выходе для настройки ключей верхнего уровня. Эти ключи получают имена для тех групп, которые будут присутствовать в данной описи (inventory). Каждая группа получает свой собственный ключ. Сама структура внутри некого ключа различается в зависимости от того какие данные требуются для представления в этой группе. Если некая группа содержит лишь хосты и никаких переменных уровня групп, имеющиеся внутри такого ключа данные просто представляют перечень имён хостов. Если же такая группа имеет переменные или потомков (группу групп), тогда эти данные должны представлять некий хэш, который может иметь один или более ключей с названиями hosts, vars или children. Значения подчинённых ключей hosts и children имеют некий список хостов, которые мы ожидаем в данной группе или же перечень определённых групп потомков. Значение подчинённого ключа vars имеет значение хэша, в котором каждое название переменной и значение представлены некими ключом и значением.

Перечисление переменных хостов

Когда в некий сценарий инвентаризации передаётся значение параметра --host <hostname>, Ansible ожидает что получаемые на выходе данные JSON просто являются неким хэшом значений переменных, в котором каждое название переменной и её значение представляются некими ключом и значением. Если для данного хоста нет переменных для какого- то заданного хоста, ожидается какой- то пустой хэш.

Простой встраиваемый модуль учёта ресурсов

Чтобы продемонстрировать некий встраиваемый модуль описи, мы создадим такой, который просто выводит на печать те же самые данные хостов, которые применялись в нашем файле mastery-hosts. Интеграция с некими активами системы управления или каким- то поставщиком инфраструктуры слегка выходит за рамки данной книги, а потому мы просто закодируем собственно названия систем в сам встраиваемый модуль. Мы будем писать свой встраиваемый модуль описи в неком файле на самом верхнем уровне корня нашего проекта с названием mastery-inventory.py и сделаем его исполняемым. Для данного файла мы воспользуемся Python для обработки параметров исполнения и форматированием JSON для простоты:

  1. Прежде всего нам потребуется добавить строку sha-bang для указания того что этот сценарий следует выполнять при помощи Python:

    
    #!/usr/bin/env python 
    #
     	   
  2. Далее нам нужно импортировать пару модулей Python, которые нам потребуются позднее в этом встраиваемом модуле:

    
    import json 
    import argparse
     	   
  3. Теперь мы создаём некий словарь Python который содержит все наши группы. Некие из наших групп просто содержат хосты, в то время как прочие имеют переменные или потомков. Мы форматируем каждую группу надлежащим образом:

    
    inventory = {} 
    inventory['web'] = {'hosts': ['mastery.example.name'], 
                        'vars': {'http_port': 80, 
                                 'proxy_timeout': 5}} 
    inventory['dns'] = {'hosts': ['backend.example.name']} 
    inventory['database'] = {'hosts': ['backend.example.name'], 
                             'vars': {'ansible_ssh_user': 'database'}} 
    inventory['frontend'] = {'children': ['web']} 
    inventory['backend'] = {'children': ['dns', 'database'], 
                            'vars': {'ansible_ssh_user': 'blotto'}} 
    inventory['errors'] = {'hosts': ['scsihost']} 
    inventory['failtest'] = {'hosts': ["failer%02d" % n for n in 
                                       range(1,11)]}
     	   
  4. Для создания нашей группы failtest (вы обнаружите её в действии в нашей следующей главе), которая является неким файлом описи, она будет представлена как failer[01:10], мы будем применять некий представительный список Python для предоставления нам такого перечня, форматируя значения элементов в данном списке в точности также как и в нашем файле описи в формате ini. Все прочие записи групп описывают себя самостоятельно.

  5. Наша первоначальная опись также имела некую групповую переменную all, которая предоставляла какую- то переменную по умолчанию, ansible_ssh_user, для всех групп (которую могут перекрывать группы), которую мы определим тут и будем применять далее в своём файле:

    
    allgroupvars = {'ansible_ssh_user': 'otto'}
     	   
  6. Затем нам требуется ввести некие особые для хоста переменные в их собственном словаре. Только один узел в нашей первоначальной описи имеет особые для хоста переменные - мы также добавим некий новый хост, scsihost, для последующей разработки своего примера:

    
    hostvars = {} 
    hostvars['mastery.example.name'] = {'ansible_ssh_host': '192.168.10.25'} 
    hostvars['scsihost'] = {'ansible_ssh_user': 'jfreeman'}
     	   
  7. После того как мы определили все данные, мы теперь можем перейти к тому коду, который будет обрабатывать синтаксический разбор параметров. Это осуществляется при помощи модуля argparse, который мы импортировали ранее в этом файле:

    
    parser = argparse.ArgumentParser(description='Simple Inventory')
    parser.add_argument('--list', action='store_true', help='List all hosts')
    parser.add_argument('--host', help='List details of a host')
    args = parser.parse_args()
     	   
  8. После выполнения синтаксического разбора параметров мы имеем дело либо с действиями --list, либо с --host. Если требуется некий список, мы просто выводим на печать некое представление JSON своей описи. Именно здесь мы будем учитывать имеющиеся данные allgroupvars; для каждой группы значением по умолчанию выступает ansible_ssh_user. Мы обойдём в цикле все группы, создадим некую копию имеющихся данных allgroupvars, обновим эти данные теми, которые уже имеются в этой группе, затем заменим значения данных групповых переменных вновь обновлённой копией. Наконец, мы выведем на печать окончательный результат:

    
    if args.list: 
        for group in inventory: 
            ag = allgroupvars.copy() 
            ag.update(inventory[group].get('vars', {})) 
            inventory[group]['vars'] = ag 
        print(json.dumps(inventory))
     	   
  9. Наконец, мы обработаем действие --host построив некий словарь для всех переменных, применимых для тех хостов, которые передаются в сценарий при помощи некой аппроксимации предыдущего заказа, использованного Ansible при анализе описи в формате ini. Этот код является итеративным, но для целей данного примера он подойдёт. Получаемый вывод представлен в формате переменных данных JSON для предоставляемых хостов, либо некий пустой хэш, когда отсутствуют особые для хоста данные конкретного предоставляемого хоста:

    
    elif args.host:
        hostfound = False
        agghostvars = allgroupvars.copy()
        for group in inventory:
            if args.host in inventory[group].get('hosts', {}):
                hostfound = True
                for childgroup in inventory:
                    if group in inventory[childgroup].get('children', {}):
                        agghostvars.update(inventory[childgroup].get('vars', {}))
        for group in inventory:
            if args.host in inventory[group].get('hosts', {}):
                hostfound = True
                agghostvars.update(inventory[group].get('vars', {}))
        if hostvars.get(args.host, {}):
            hostfound = True
        agghostvars.update(hostvars.get(args.host, {}))
        if not hostfound:
            agghostvars = {}
        print(json.dumps(agghostvars))
     	   

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

 

Рисунок 9.10



[Совет]Совет

Не забывайте давать разрешение на исполнение своему сценарию динамической описи - например: chmod +x mastery-inventory.py.

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

 

Рисунок 9.11



Аналогично, когда мы запустим этот сценарий Python со значением параметра --host в виде названия хоста, который, как нам известно, находится в данной описи, мы обнаружим значения переменных хоста для того имени хоста который мы передали. Если мы передадим некое название группы, не будет возвращено ничего, так как этот сценарий возвращает данные лишь для допустимых индивидуальных названий хостов:

 

Рисунок 9.12



Теперь мы готовы применять свой файл описи в Ansible. Давайте создадим некий новый плейбук (inventory_test.yaml) для отображения собственно названия хоста и данных имени пользователя ssh:


--- 
- name: test the inventory 
  hosts: all 
  gather_facts: false 
 
  tasks: 
  - name: hello world 
    debug: 
      msg: "Hello world, I am {{ inventory_hostname }}. 
            My username is {{ ansible_ssh_user }}"
 	   

Имеется ещё один момент, который нам следует сделать прежде чем мы сможем применять свой новый встраиваемый модуль описи. По умолчанию (а также в виде функции безопасности), большинство встраиваемых модулей инвентаризации Ansible отключены. Чтобы быть уверенными что наш сценарий динамической описи будет работать, откроем подходящий файл ansible.cfg в неком текстовом редакторе и найдём строку enable_plugins в соответствующем разделе [inventory]. Как минимум, она должна выглядеть следующим образом (хотя вы можете выбрать включение большего числа встраиваемых модулей, если пожелаете):


[inventory]
enable_plugins = ini, script
 	   

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

 

Рисунок 9.13



Как вы можете видеть, мы получили те хосты, которые и ожидали, а также мы получили значение пользователя ssh по умолчанию для master.example.name.backend.example.name, а scsihost всякий раз показывает своё особое для хоста имя пользователя ssh.

Оптимизация производительности сценария

При запуске Ansible, для данного сценария описи он будет исполняться один раз с --list для получения всех групповых данных. Затем Ansible будет выполнять этот сценарий снова с --host <hostname> для каждого выявленного хоста в самый первый раз. Для нашего сценария это требует очень незначительного времени, поскольку имеется очень ограниченное число хостов, а наше исполнение очень быстрое. Однако в некой среде с большим числом хостов или при неком встраиваемым модуле, который требует большего времени работы, получение подобной информации может быть длительным процессом. К счастью, имеется некая оптимизация, которая может быть применена к возвращаемым данным из вызова --list, которая предотвращала бы повторное выполнение данного сценария для всех хостов. Все особые для хоста данные могут получены за один раз внутри возвращаемых данных групп, внутри некого ключа верхнего уровня с названием _meta, который имеет подчинённый ключ с названием hostvars, который в свою очередь и содержит сам по себе переменные хоста все переменные данные. Когда Ansible встречает некий возвращаемый в --list ключ _meta, он буде пропускать все вызовы --host и полагать все особенные для хоста данные уже получены, потенциально сберегая значительный объём времени! Давайте изменим свой сценарий описи с тем, чтобы он возвращал переменные хоста внутри _meta, а также создадим некое условие ошибки внутри значения параметра --host чтобы показывать что --host ещё не был вызван:

  1. Прежде всего мы добавим в словарь описи сам ключ _meta после всех тех hostvars, которые мы построили при помощи того же самого алгоритма что и ранее и сразу после синтаксического разбора параметров:

    
    hostvars['scsihost'] = {'ansible_ssh_user': 'jfreeman'}
    
    agghostvars = dict()
    for outergroup in inventory:
        for grouphost in inventory[outergroup].get('hosts', {}):
            agghostvars[grouphost] = allgroupvars.copy()
            for group in inventory:
                if grouphost in inventory[group].get('hosts', {}):
                    for childgroup in inventory:
                        if group in inventory[childgroup].get('children', {}):
                            agghostvars[grouphost].update(inventory[childgroup].get('vars', {}))
            for group in inventory:
                if grouphost in inventory[group].get('hosts', {}):
                    agghostvars[grouphost].update(inventory[group].get('vars', {}))
            agghostvars[grouphost].update(hostvars.get(grouphost, {}))
    
    inventory['_meta'] = {'hostvars': agghostvars}
    
    parser = argparse.ArgumentParser(description='Simple Inventory')
     	   

    Затем мы изменим свой --host для обработки возбуждения некой исключительной ситуации:

    
    elif args.host:
        raise StandardError("You've been a bad boy")
     	   
  2. Теперь мы повторно запустим свой плейбук inventory_test.yaml чтобы гарантировать что мы всё ещё получаем верные данные:

     

    Рисунок 9.14



  3. Просто чтобы убедиться, мы вручную запустим свой встроенный модуль инвентаризации с этим параметром --hosts чтобы показать такую исключительную ситуацию:

     

    Рисунок 9.15



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

Участие в проекте Ansible

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

Представление содействия

Проект Ansible применяет GitHub для управления кодом репозиториев, проблем и прочих сторон проекта. Собственно организация Ansible это то место где можно обнаружить репозитории кода. Основным репозиторием выступает репозиторий ansible, в котором можно найти сам код ядра Ansible, основные модули и их документацию. Для разработки некого вклада следует клонировать этот репозиторий.

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

Этот проект Ansible применяет совершенно новую ветку devel вместо обычной master. Большинство новых разработок нацелено именно на ветку devel, либо на некую стабильную ветвь.

Репозиторий Ansible

Основной репозиторий Ansible имеет в своём корне множество файлов и папок. Эти файлы в основном являются файлами документации верхнего уровня, лицензионными кодами или непрерывной интеграцией тестовых конфигураций платформы.

Что касается имеющихся каталогов, следует обратить внимание на следующие:

  • bin: Источник различных исполняемых ядер Ansible

  • contrib: Источник для вкладов в плагины описей и хранилищ (vault)

  • docs: Источник для документации API, сам вебсайт, а также все основные страницы

  • hacking: Руководства и инструментарий по хакингу исходного кода самого Ansible

  • lib/ansible: Исходный код ядра Ansible

  • test: Код блока тестирования и его интеграции

Скорее всего, вклад в Ansible будет осуществляться в этих папках.

Исполнение тестов

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

Прежде чем можно будет осуществить тестирование, для проверки кода Ansible должна быть подготовлена среда оболочки. Для установки необходимых переменных имеется файл некой среды оболочки, который можно активировать такой командой:


$ source ./hacking/env-setup
		

Обеспечения прохождения проверок до внесения изменения может сэкономит впоследствии много времени на отладку.

Тестирование элемента

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

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

Выполнение проверок может потребовать собственно установки дополнительного программного обеспечения. При применении некой виртуальной среды Python для управления установками программного обеспечения Python будет лучше создать некий новый venv для применения при тестировании Ansible - того, которое не имеет уже установленного в нём Ansible.

Чтобы иметь целью для выполнения определённый набор тестов, можно непосредственно вызывать утилиту pytest (порой доступную как py.test), с неким путём, предоставляемым для тестируемого каталога или особого файла. В данном примере выполняется всего лишь проверки элемента parsing:

 

Рисунок 9.16



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

Интеграция тестов

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

  • Неразрушительные

  • Разрушительные

  • Облачные

  • Windows

  • Сетевой среды

Более подробное описание всех категорий проверок можно найти в файле README.md, который содержится в test/integration/README.md.

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

Многие из проверок интеграции требуют функциональности ssh к вашему локальному хосту. Убедитесь что ssh работает, причём в идеале без приглашения на ввод пароля. Удалённые хосты можно применять изменяя соответствующий файл описи, используемый для проверок (test/integration/inventory).

Как и для проверок элемента, можно выполнять индивидуальные тесты интеграции при помощи соответствующей утилиты ansible-test, расположенной в test/runner/ansible-test. Это исключительно важно, поскольку многие из проверок интеграции требуют внешних ресурсов, например, учётных записей вычислительного облака. Каждый каталог в test/integration/targets является некой целью, которую можно проверять индивидуально. Например, для проверки функциональности ping воспользуйтесь имеющейся целью ping:

 

Рисунок 9.17



Обращаем ваше внимание на то, что некие тесты в этом комплекте разработаны на получение отказа - и при этом в самом конце вы наблюдаете ok=7 и failed=0, означающие успешное прохождение проверки. Большой набор совместимых с POSIX не разрушающих проверок интеграции запускают системы непрерывной интеграции на предлагаемых изменения Ansible, которые выполняются следующим образом:


$ test/runner/ansible-test integration -v posix/ci/
		
[Замечание]Замечание

На время написания книги ряд имеющихся проверок posix/ci не проходили под macOS. Рекомендуется выполнять эти проверки в самой последней среде Fedora.

Проверка стиля кода

Третьей категорией проверок Ansible является категория стиля кода. Эти проверки изучают тот синтаксис, который применяется в ваших файлах Python, обеспечивая единообразное представление по всему базовому коду. Этот стиль кода принудительно устанавливается в PEP8, неком руководстве стиля для Python. Дополнительные сведения доступны в test/sanity/pep8/README.md. Данный стиль применяется через выставление целью для pep8. Если нет ошибок, такая цель не выводит никаких сообщений об ошибках; тем не менее, можно проверить значение кода возврата. Возвращаемое значение 0 означает отсутствие ошибок:

 

Рисунок 9.18



[Совет]Совет

Для выполнения этих проверок могут потребоваться дополнительные модули Python - соответствующий метод для их установки может отличаться от системы к системе, и обычно может применяться через инструментарий pip. В моей тестовой системе CentOS7, чтобы запустить такие проверки, мне приходилось выполнять следующую команду: sudo yum install python2-packaging python2-pycodestyle.

Если некий файл Python имеет отклонения от pep8, получаемый вывод отразит эти отклонения:

 

Рисунок 9.19



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

Выполнение запроса в пуле

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

После размещения, можно открыть некий запрос при помощи вебсайта GitHub. Это создаст некий запрос на помещение, который запустит непрерывные проверки интеграции и уведомит просматривающих о новом предложении. Обратитесь за дополнительными сведениями относительно запросов размещения в Github.

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

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

Выводы

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

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

В Главе 11, Предоставление инфраструктуры, мы изучим применение Ansible в создании необходимой инфраструктуры для управления.