Модули
Это подробное руководство поможет вам понять порядок выполнения модулей в Ansible. Оно предназначено для разработчиков, работающих с ядром Ansible Engine, отвечающим за выполнение модулей. Разработчики модулей Ansible также могут найти это руководство полезным, но пользователям, просто использующим модули Ansible, оно вряд ли пригодится.
Типы модулей
Ansible поддерживает несколько различных типов модулей в своём кодовом базисе. Некоторые из них обеспечивают обратную совместимость, а другие — гибкость.
Плагины действий
Плагины действий выглядят как модули для конечных пользователей, которые пишут плейбуки, но являются отдельными сущностями с точки зрения этого документа. Плагины действий всегда выполняются на контроллере и иногда могут выполнить всю работу там (например, плагин действия debug, который выводит текст для пользователя, или плагин действия assert, который может проверить, удовлетворяют ли несколько значений в плейбуке определённым критериям.)
Чаще всего плагины действий устанавливают некоторые значения на контроллере, а затем вызывают фактический модуль на управляемом узле, который что-то делает с этими значениями. Простой пример — плагин действия шаблон. Плагин действия шаблон использует значения от пользователя для создания файла во временной папке на контроллере, используя переменные из среды плейбука. Затем он переносит временный файл во временный файл на удалённой системе. После этого он вызывает модуль копирование, который работает на удалённой системе для перемещения файла в его конечное местоположение, установки разрешений файла и так далее.
Модули нового стиля
Все модули, поставляемые с Ansible, относятся к этой категории.
Модули нового стиля содержат аргументы модуля внутри себя. Модули старого стиля должны копировать отдельный файл на управляемый узел, что менее эффективно, так как требует двух соединений через сеть вместо одного.
Python
Модули Python нового стиля используют фреймворк Ansiballz для создания модулей. Все официальные модули (входящие в поставку Ansible) используют либо этот фреймворк, либо фреймворк модулей PowerShell.
Эти модули используют импорты из ansible.module_utils для получения шаблонов кода модуля, таких как парсинг аргументов, форматирование возвращаемых значений в формате JSON и различные операции с файлами.
Примечание
В Ansible до версии 2.0.x официальные модули Python использовали фреймворк Заменитель модулей. Для авторов модулей Ansiballz в значительной степени является надмножеством функциональности Заменителя модулей, поэтому обычно нет необходимости знать об одном из них.
Powershell
Модули PowerShell нового стиля используют фреймворк Заменитель модулей для создания модулей. Эти модули получают библиотеку кода PowerShell, встроенную в них перед отправкой на управляемый узел.
JSONARGS
Скрипты могут организовать размещение строки аргументов внутри себя, поместив строку <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> где-то внутри файла. Модуль обычно устанавливает переменную со значением так:
json_arguments = """<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"""
Что расширяется как:
json_arguments = """{"param1": "test's quotes", "param2": "\"To be or not to be\" - Hamlet"}"""
Примечание
Ansible выводит строку JSON с простыми кавычками. Двойные кавычки используются для цитирования строковых значений, двойные кавычки внутри строковых значений экранируются обратным слешем, а одинарные кавычки могут появляться без экранирования внутри строкового значения. Для использования JSONARGS ваш язык программирования должен иметь способ обработки этого типа строк. Пример использует тройные кавычки Python для этого. Другие языки программирования могут иметь похожий символ кавычек, который не будет путаться с кавычками в JSON, или они могут позволить вам определить свои собственные символы начала и конца кавычек. Если язык не предоставляет ни одного из этих вариантов, вам потребуется написать модуль JSON или модуль старого стиля вместо него.
Модуль обычно анализирует содержимое json_arguments с помощью библиотеки JSON и затем использует их как исходные переменные в остальной части своего кода.
Модули, принимающие JSON
Если в модуле где-либо есть строка WANT_JSON, Ansible обрабатывает его как неродной модуль, который принимает имя файла в качестве единственного параметра командной строки. Имя файла относится к временному файлу, содержащему строку JSON с параметрами модуля. Модуль должен открыть файл, прочитать и проанализировать параметры, обработать данные и вывести свои возвращаемые данные в формате JSON-кодированного словаря в стандартный вывод перед завершением.
Эти типы модулей являются автономными сущностями. Начиная с Ansible 2.1, Ansible изменяет только строку shebang, если она присутствует.
См. также
Примеры неродных модулей, написанных на Ruby, находятся в репозитории Ansible для Rubyists.
Бинарные модули
Начиная с Ansible 2.2, модули также могут быть небольшими бинарными программами. Ansible не выполняет никаких действий для обеспечения их портативности на разные системы, поэтому они могут быть специфичными для системы, на которой были скомпилированы, или требовать другие зависимости бинарного времени выполнения. Несмотря на эти недостатки, иногда сайт может не иметь иного выбора, кроме как скомпилировать пользовательский модуль с использованием определённой бинарной библиотеки, если это единственный способ получить доступ к определённым ресурсам.
Бинарные модули принимают свои аргументы и возвращают данные Ansible таким же образом, как и модули JSON.
См. также
Один пример бинарного модуля, написанного на Go, находится в исходном коде.
Модули старого стиля
Модули старого стиля похожи на модули JSON, за исключением того, что файл, который они принимают, содержит пары key=value для своих параметров вместо JSON.
Ansible определяет, что модуль является старого стиля, если он не содержит никаких маркеров, которые указывали бы, что он относится к другому типу.
Как выполняются модули
Когда пользователь использует ansible или ansible-playbook, он указывает задачу для выполнения. Эта задача обычно представляет собой имя модуля вместе с несколькими параметрами, которые передаются модулю. Ansible обрабатывает эти значения различными способами перед их окончательным выполнением на удалённой машине.
executor/task_executor
TaskExecutor получает имя модуля и параметры, которые были распарсены из плейбука (или из командной строки в случае /usr/bin/ansible). Он использует имя, чтобы определить, рассматривает ли он модуль или плагин действия. Если это модуль, он загружает обычный плагин действия и передаёт имя, переменные и другую информацию о задаче и плейбуке этому плагину для дальнейшей обработки.
Обычный плагин действия
Плагин действия normal выполняет модуль на удалённом узле. Он является основным координатором большей части работы по фактическому выполнению модуля на управляемой машине.
- Он заботится о создании соединения с управляемой машиной, создавая экземпляр класса
Connection, соответствующий конфигурации инвентаризации для этого узла. - Он добавляет любые внутренние переменные Ansible к параметрам модуля (например, те, которые передают
no_logмодулю). - Он заботится о создании любых временных файлов на удалённой машине и очищает их после использования.
- Он выполняет фактическую работу по передаче модуля и параметров модуля на удалённый узел, хотя код module_common, описанный в следующем разделе, выполняет работу по определению формата, который они будут использовать.
- Он обрабатывает любые особые случаи, относящиеся к модулям (например, различные сложности с модулями Windows, которые должны иметь те же имена, что и модули Python, чтобы внутренние вызовы модулей из других плагинов действий работали.)
Большая часть этой функциональности происходит от класса BaseAction, который находится в plugins/action/__init__.py. Он использует объекты Connection и Shell для выполнения своей работы.
Примечание
Когда задачи выполняются с параметром async:, Ansible использует плагин действия async, а не плагин действия normal, для его вызова. Данный поток выполнения в настоящее время не документирован. Для получения информации о его работе, обратитесь к исходному коду.
executor/module_common.py
Код в executor/module_common.py отвечает за сборку модуля для отправки на управляемый узел. Сначала модуль читается, затем анализируется для определения его типа. Модули PowerShell и модули JSON-args передаются через Заменитель Модулей. Модули нового типа Python-модули собираются с помощью Ansiballz. Модули не-родные-want-JSON, бинарные модули и модули старого стиля не затрагиваются ни одним из этих методов и проходят без изменений. После этапа сборки выполняется одно последнее изменение для всех модулей, имеющих строку shebang. Ansible проверяет, имеет ли интерпретатор в строке shebang определённый путь, настроенный через переменную инвентаризации ansible_$X_interpreter. Если это так, Ansible заменяет этот путь на путь интерпретатора, указанный в модуле. После этого Ansible возвращает полные данные модуля и тип модуля в Нормальный плагин действия, который продолжает выполнение модуля.
Далее мы рассмотрим подробности двух фреймворков сборки.
Заменитель Модулей
Фреймворк Заменителя Модулей — это оригинальный фреймворк, реализующий модули нового типа. По сути, это препроцессор (как препроцессор C для тех, кто знаком с этим языком программирования). Он выполняет прямые подстановки определённых подстрочных шаблонов в файле модуля. Существует два типа подстановок:
- Замены, происходящие только в файле модуля. Это общедоступные строки замены, которые модули могут использовать для получения полезного шаблона или доступа к аргументам.
-
from ansible.module_utils.MOD_LIB_NAME import *заменяется содержимымansible/module_utils/MOD_LIB_NAME.py. Эти значения следует использовать только с модулями Python нового типа. -
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>эквивалентноfrom ansible.module_utils.basic import *и также должно применяться только к модулям Python нового типа. -
# POWERSHELL_COMMONподставляет содержимоеansible/module_utils/powershell.ps1. Оно должно использоваться только с модулями PowerShell нового типа.
-
- Замены, используемые кодом
ansible.module_utils. Это внутренние шаблоны замены. Они могут использоваться внутри, в вышеуказанных общедоступных заменах, но не должны использоваться напрямую модулями.-
"<<ANSIBLE_VERSION>>"заменяется версией Ansible. В модулях Python нового типа в рамках фреймворка Ansiballz правильным способом является создание экземпляраAnsibleModuleи доступ к версии из :attr:AnsibleModule.ansible_version. -
"<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>"заменяется строкой, являющейся строкой Pythonreprпараметров модуля, закодированных в формате JSON. Использованиеreprдля JSON-строки делает её безопасной для встраивания в файл Python. В модулях Python нового типа в рамках фреймворка Ansiballz это лучше получить, создав экземплярAnsibleModuleи используяAnsibleModule.params. -
<<SELINUX_SPECIAL_FILESYSTEMS>>подставляет строку, являющуюся списком файловых систем, в которых есть контекст безопасности, зависящий от файловой системы в SELinux. В модулях Python нового типа, если вам действительно нужно это, вы должны создать экземплярAnsibleModuleи использоватьAnsibleModule._selinux_special_fs. Переменная также изменилась со строки, разделённой запятыми, имён файловых систем на фактический список имён файловых систем в Python. -
<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>подставляет параметры модуля в виде JSON-строки. Необходимо соблюдать осторожность, чтобы правильно заключить строку в кавычки, так как данные JSON могут содержать кавычки. Этот шаблон не подставляется в модулях Python нового типа, так как они могут получить параметры модуля другим способом. - Строка
syslog.LOG_USERзаменяется везде наsyslog_facility, который был указан вansible.cfgили любой переменной инвентаризацииansible_syslog_facility, которая применима к этому узлу. В модулях Python нового типа это немного изменилось. Если вам действительно нужно к этому обратиться, вы должны создать экземплярAnsibleModuleи использоватьAnsibleModule._syslog_facilityдля доступа к нему. Это больше не фактическая служба syslog и теперь это имя службы syslog. Подробности см. в документации по внутренним аргументам.
-
Ansiballz
Ansible 2.1 перешёл от фреймворка Заменителя Модулей к фреймворку Ansiballz для сборки модулей. Фреймворк Ansiballz отличается от заменителя модулей тем, что он использует реальные импорты Python-вещей в ansible/module_utils вместо простого препроцессинга модуля. Это делается путём создания архива zip, который включает в себя файл модуля, файлы в ansible/module_utils, которые импортируются модулем, и некоторый шаблон для передачи параметров модуля. Затем архив zip кодируется в Base64 и оборачивается небольшим скриптом Python, который декодирует Base64-кодирование и помещает архив zip в временную директорию на управляемом узле. Затем он извлекает только скрипт модуля Ansible из архива zip и помещает его в временную директорию. Затем он устанавливает PYTHONPATH для поиска Python-модулей внутри архива zip и запускает python на извлечённом скрипте модуля Ansible.
Примечание
Ansible оборачивает архив zip в скрипт Python по двум причинам:
- для совместимости с Python-2.6, который имеет менее функциональную версию команды Python
-mкомандной строки. - чтобы обеспечить правильную работу конвейеризации. Конвейеризации необходимо передать модуль Python в интерпретатор Python на удалённом узле. Python понимает скрипты из стандартного ввода, но не понимает архивы zip.
В Ansiballz любые импорты Python-модулей из пакета ansible.module_utils вызывают включение этого Python-файла в архив zip. Экземпляры #<<INCLUDE_ANSIBLE_MODULE_COMMON>> в модуле преобразуются в from ansible.module_utils.basic import *, и ansible/module-utils/basic.py включается в архив zip. Файлы, которые включаются из module_utils, сами проверяются на импорты других Python-модулей из module_utils, чтобы их тоже включить в архив zip.
Предупреждение
В настоящее время фреймворк Ansiballz не может определить, должен ли включаться импорт, если это относительный импорт. Всегда используйте абсолютный импорт, содержащий ansible.module_utils, чтобы позволить Ansiballz определить, что файл должен быть включён.
Передача аргументов
В Заменителе Модулей аргументы модуля преобразуются в закодированную строку JSON и подставляются в комбинированный файл модуля. В Ansiballz закодированная строка JSON передаётся в модуль через стандартный ввод. При создании экземпляра ansible.module_utils.basic.AnsibleModule, он анализирует эту строку и помещает аргументы в AnsibleModule.params, где они доступны для остального кода модуля.
Примечание
Внутренне AnsibleModule использует вспомогательную функцию ansible.module_utils.basic._load_params(), чтобы загрузить параметры из стандартного ввода и сохранить их во внутреннюю глобальную переменную. Очень динамичные пользовательские модули, которым нужно проанализировать параметры до создания экземпляра AnsibleModule, могут использовать _load_params для получения параметров. Обратите внимание, что _load_params — внутренняя функция и может измениться в разрыве, если это необходимо для поддержки изменений в коде. Однако мы постараемся не ломать её впустую, что нельзя сказать ни о способе передачи параметров, ни о внутренней глобальной переменной.
Внутренние аргументы
И Заменитель Модулей, и Ansiballz отправляют дополнительные аргументы в модуль помимо тех, которые пользователь указал в playбуке. Эти дополнительные аргументы являются внутренними параметрами, которые помогают в реализации глобальных функций Ansible. Модулям зачастую не нужно знать об этом явно, так как функции реализованы в ansible.module_utils.basic, но некоторые функции требуют поддержки от модуля, поэтому полезно знать о них.
_ansible_no_log
Это булево значение. Если оно True, значит, в playбуке указана no_log (в параметрах задачи или как параметр play). Это автоматически влияет на вызовы AnsibleModule.log(). Если модуль реализует собственное логирование, ему нужно проверить это значение. Лучший способ — создать экземпляр AnsibleModule и проверить значение AnsibleModule.no_log.
Примечание
no_log , указанные в аргументе_spec модуля, обрабатываются другим механизмом.
_ansible_debug
Это булево значение, которое включает более подробную запись в журнал. Если модуль использует AnsibleModule.debug() вместо AnsibleModule.log(), то сообщения записываются в журнал только если это значение True. Это также включает запись в журнал внешних команд, которые выполняет модуль. Это можно изменить, изменив настройку debug в ansible.cfg или переменную среды ANSIBLE_DEBUG. Если по какой-то причине модулю необходимо получить доступ к этому значению, он должен создать экземпляр AnsibleModule и получить доступ к AnsibleModule._debug.
_ansible_diff
Это булево значение включается с помощью опции командной строки --diff. Если модуль поддерживает эту опцию, он будет показывать унифицированную разницу изменений, которые необходимо внести в шаблонированные файлы. Правильный способ доступа к этому значению для модуля — создать экземпляр AnsibleModule и получить доступ к AnsibleModule._diff.
_ansible_verbosity
Это значение может использоваться для более точного управления записью в журнал. Однако в настоящее время оно не используется.
_ansible_selinux_special_fs
Это список имён файловых систем, для которых должен быть установлен специальный контекст SELinux. Они используются методами AnsibleModule для работы с файлами (изменение атрибутов, перемещение и копирование). Список имён устанавливается через строку имён файловых систем, разделённых запятыми, из ansible.cfg:
# ansible.cfg [selinux] special_context_filesystems=nfs,vboxsf,fuse,ramfs
Если модуль не может использовать встроенные методы AnsibleModule для управления файлами и ему нужно знать о специальных файловых системах с контекстом, он должен создать экземпляр AnsibleModule и затем проверить список в AnsibleModule._selinux_special_fs.
Это заменяет ansible.module_utils.basic.SELINUX_SPECIAL_FS из Замены Модулей. В замене модулей это была строка имён файловых систем, разделённых запятыми. В Ansiballz это фактический список.
Новое в версии 2.1.
_ansible_syslog_facility
Этот параметр управляет тем, в какую системную службу журналов (syslog) записывает модуль Ansible. Его можно изменить, изменив значение syslog_facility в ansible.cfg Большинству модулей следует просто использовать AnsibleModule.log(), который затем будет использовать это значение. Если модулю необходимо использовать это самостоятельно, он должен создать экземпляр AnsibleModule и затем получить имя системной службы журналов (syslog) из AnsibleModule._syslog_facility Код будет немного отличаться от того, что был в Замене Модулей из-за того, насколько хакерским был старый метод.
# Old way import syslog syslog.openlog(NAME, 0, syslog.LOG_USER) # New way import syslog facility_name = module._syslog_facility facility = getattr(syslog, facility_name, syslog.LOG_USER) syslog.openlog(NAME, 0, facility)
Новое в версии 2.1.
_ansible_version
Этот параметр передаёт версию Ansible, которая выполняет модуль. Для доступа к нему модуль должен создать экземпляр AnsibleModule и затем получить его из AnsibleModule.ansible_version Это заменяет ansible.module_utils.basic.ANSIBLE_VERSION из Замены Модулей.
Новое в версии 2.1.
Особые моменты
Пайплайнинг
Ansible может передать модуль на удалённую машину двумя способами:
- он может записать модуль в временный файл на удалённом хосте, а затем использовать второе соединение с удалённым хостом для его выполнения с интерпретатором, необходимым для модуля
- или он может использовать то, что известно как пайплайнинг, для выполнения модуля, передавая его в стандартный ввод интерпретатора на удалённой машине.
Пайплайнинг работает только с модулями, написанными на Python, на данный момент, так как Ansible знает, что Python поддерживает этот режим работы. Поддержка пайплайнинга означает, что любой формат, который имеет модуль-загрузка до отправки по сети, должен быть исполняем Python через стандартный ввод.
Почему передавать аргументы через стандартный ввод?
Передача аргументов через стандартный ввод была выбрана по следующим причинам:
- В сочетании с пайплайнингом это предотвращает временное сохранение аргументов модуля на диск на удалённой машине. Это затрудняет (но не делает невозможным) злоумышленнику на удалённой машине получить доступ к любой конфиденциальной информации, которая может присутствовать в аргументах.
- Аргументы командной строки небезопасны, так как большинство систем позволяют непроверенным пользователям читать весь командный ряд процесса.
- Переменные окружения, как правило, более безопасны, чем аргументы командной строки, но некоторые системы ограничивают общий размер окружения. Это может привести к усечению параметров, если мы достигнем этого предела.
© 2012–2018 Michael DeHaan
© 2018–2019 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.4/dev_guide/developing_program_flow_modules.html