[Системное администрирование, IT-инфраструктура, Серверное администрирование, DevOps] Распутывая Ansible Loops (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В посте рассматриваются следующие Ansible модули loop: with_items, with_nested, with_subelements, with_dict.
Исходный код https://github.com/ctorgalson/ansible-loops
Одна из моих ролей в Chromatic — член команды DevOps. Помимо прочего, это включает в себя работу с нашими серверами и серверами наших клиентов. Это, в свою очередь, означает, что я трачу много времени на работу с Ansible, популярным инструментом для инициализации, настройки и развертывания серверов и приложений.
Проще говоря, машина, на которой запущен Ansible, запускает команды на другом компьютере через SSH. Эти команды указываются декларативно (не обязательно) с использованием небольших участков YAML, называемых задачами. Эти TASKS вызывают модули Ansible, которые специализируются на выполнении опций с определенными компонентами, такими как файлы, базы данных и т. д.
Например, следующая задача использует модуль File (документация, код) для создания определенного каталога, если он еще не существует, и изменяет его атрибуты, если они еще не установлены правильно:
- file:
path: /home/jenkins/.ssh
state: directory
owner: jenkins
group: jenkins
mode: 700
Несколько задач, относящихся к одной задаче, сгруппированы в роли, а несколько ролей могут быть сгруппированы в playbooks. Затем можно использовать playbook для выполнения точно таких же шагов конфигурации на любом количестве серверов одновременно.
Ansible декларативный?
TASKS Ansible записываются декларативно, то есть мы не указываем, какая базовая реализация должна использоваться для выполнения TASKS. Это полезно, поскольку обеспечивает высокий уровень абстракции, очень читаемый и относительно простой для написания кода, а в некоторых случаях позволяет нам использовать одну и ту же задачу на разных платформах. Например, есть модуль Ansible Copy, который используется для копирования файлов на конечный компьютер. В следующей задаче Ansible копирует файл конфигурации в правильный каталог на удаленном компьютере и устанавливает владельца, группу и права доступа к файлу:
- name: Copy SSH config file into Alice’s .ssh directory.
copy:
src: files/config
dest: /home/alice/.ssh/config
owner: alice
group: alice
mode: 0600
Для достижения того же результата мы могли бы, например, написать серию команд или функцию в bash, используя scp, chown и chmod. С Ansible мы можем сосредоточиться на желаемой конфигурации, не слишком заботясь о деталях.
С другой стороны, это также означает, что доступные инструменты иногда кажутся странными или необычными — в основном потому, что разработчики обычно имеют доступ к императивным инструментам в тех случаях, когда декларативный вариант не подходит.
Одно место, где я заметил это в Ansible, — это многократное выполнение одной и той же TASKS с набором разных элементов. В частности, я нашел инструменты циклов Ansible немного странными, не в последнюю очередь потому, что их шестнадцать — по сравнению с PHP, который имеет четыре вида циклов.
На самом деле для этого есть причина, если вас интересует внутреннее устройство Ansible. На странице Loops в документации указано, что «loops на самом деле представляют собой комбинацию вещей с _ + lookup(), поэтому любой плагин поиска можно использовать в качестве источника для цикла». Поиск (Lookups) — это тип плагина Ansible, который используется для «доступа к данным в Ansible из внешних источников», и если вы сравните документацию Loops и каталог плагинов Ansible на Github, вы увидите многие из них с одинаковыми именами.
Однако в документации Ansible поисковые запросы рассматриваются как «расширенная тема», и нет необходимости углубляться в исходный код, чтобы научиться использовать сами циклы. В оставшейся части этого поста описаны несколько наиболее часто используемых циклов Ansible, а также некоторые вещи, которые я узнал о том, как их использовать.
ЦИКЛЫ ANSIBLE
TASKS, выполняемые в следующих примерах, являются более или менее произвольными примерами, связанными с созданием пользователей и их каталогов, но они тесно связаны с реальными задачами, которые могут потребоваться на производственных серверах (но обратите внимание: данные, с которыми мы должны работать, и количество задач, которые мы используем для достижения правильной конфигурации, явно нереально!)
Примеры основываются друг на друге для выполнения следующих простых задач на гипотетическом сервере:
- Убедитесь, что присутствуют четыре пользователя: alice, bob, carol и dan.
- Убедитесь, что домашний каталог каждого пользователя содержит два каталога: .ssh/ и loops.
- Убедитесь, что каждый из четырех домашних каталогов пользователей содержит по одному каталогу для каждого другого пользователя. Например, домашний каталог пользователя alice по завершении должен выглядеть так:
/home/alice/
├── .ssh/
├── bob/
├── carol/
├── dan/
└── loops/
ЦИКЛ 1. СОЗДАНИЕ ПОЛЬЗОВАТЕЛЕЙ WITH_ITEMS
Типичная задача в Ansible может выглядеть примерно так, когда пользователь удаляет пользователя chuck из системы, в которой выполняется задача:
- name: Remove user ‘Chuck’ from the system.
user:
name: chuck
state: absent
remove: yes
Чтобы повторить эту задачу для нескольких пользователей — скажем, нам нужно удалить пользователей Chuck и Craig — мы просто добавляем в задачу параметр with_items. with_items принимает либо список (показанный здесь), либо переменную (как в остальных следующих примерах):
- name: Remove users ‘Chuck’ and ‘Craig’ from the system.
user:
name: "{{ item }}"
state: absent
remove: yes
with_items:
- chuck
- craig
Возвращаясь к нашему первому примеру цикла, мы можем использовать with_items для создания первых пользователей в нашем списке, alice и bob:
Переменные
users_with_items:
- name: "alice"
personal_directories:
- "bob"
- "carol"
- "dan"
- name: "bob"
personal_directories:
- "alice"
- "carol"
- "dan"
TASKS
- name: "Loop 1: create users using 'with_items'."
user:
name: "{{ item.name }}"
with_items: "{{ users_with_items }}"
Здесь мы используем модуль Ansible User для перебора переменной с именем users_with_items. Эта переменная содержит имена и информацию о двух пользователях, но задача только гарантирует, что пользователи существуют в системе, она не создает каталоги, содержащиеся в списке personal_directories каждого пользователя (обратите внимание, что personal_directories — это просто произвольный ключ в массиве данных для нашего примера).
Это примечательная особенность циклов Ansible (и Ansible в целом): поскольку TASKS вызывают определенные модули с определенной проблемной областью, обычно в задаче невозможно выполнять более одного вида вещей. В данном конкретном случае это означает, что мы не можем убедиться, что personal_directories пользователя существуют из этой TASKS (т. е. Потому что мы используем модуль User, а не модуль File).
Цикл with_items работает примерно так же, как этот цикл PHP:
<?php
foreach ($users_with_items as $user) {
// Do something with $user...
}
Мы писали задачу как обычно, за исключением того, что:
• Мы заменили имя переменной item.name на имя пользователя.
• Мы добавили строку with_items, определяющую переменную для перебора.
Также стоит отметить, что внутри цикла Ansible текущая итерация всегда является item, и доступ к любому заданному свойству осуществляется с помощью item.property.
РЕЗУЛЬТАТЫ
/home/
├── alice/
└── bob/
ЦИКЛ 2: СОЗДАВАЙТЕ КАТАЛОГИ ОБЩИХ ПОЛЬЗОВАТЕЛЕЙ, ИСПОЛЬЗУЯ WITH_NESTED
Примечание: Для цикла 2 нужны созданные юзеры, например с помощью цикла 1. Иначе будет ошибка chown failed: failed to look up user
В этом примере мы используем две переменные, users_with_items из цикла 1, и новую, common_directories, которая представляет собой список всех каталогов, которые должны присутствовать в каталоге каждого пользователя. Это означает, что (снова возвращаясь к PHP), нам нужно что-то, что работает примерно так:
<?php
foreach ($users_with_items as $user) {
foreach ($common_directories as $directory) {
// Create $directory for $user...
}
}
В Ansible мы можем использовать цикл with_nested. Циклы with_nested принимают два списка, второй из которых повторяется на каждой итерации первого:
Переменные
users_with_items:
- name: "alice"
personal_directories:
- "bob"
- "carol"
- "dan"
- name: "bob"
personal_directories:
- "alice"
- "carol"
- "dan"
common_directories:
- ".ssh"
- "loops
TASKS
# Note that this does not set correct permissions on /home/{{ item.x.name }}/.ssh!
- name: "Loop 2: create common users' directories using 'with_nested'."
file:
dest: "/home/{{ item.0.name }}/{{ item.1 }}"
owner: "{{ item.0.name }}"
group: "{{ item.0.name }}"
state: directory
with_nested:
- "{{ users_with_items }}"
- "{{ common_directories }}"
Как показано в приведенной выше задаче, к двум спискам в with_nested можно получить доступ, используя item.0 (для users_with_items) и item.1 (для common_directories) соответственно. Это позволяет нам, например, создайте каталог /home/alice/.ssh на самой первой итерации.
РЕЗУЛЬТАТЫ
/home/
├── alice/
│ ├── .ssh/
│ └── loops/
└── bob/
├── .ssh/
└── loops/
ЦИКЛ 3: СОЗДАВАЙТЕ ЛИЧНЫЕ КАТАЛОГИ ПОЛЬЗОВАТЕЛЕЙ, ИСПОЛЬЗУЯ WITH_SUBELEMENTS
Примечание: Для цикла 3 нужны созданные юзеры, например с помощью цикла 1. Иначе будет ошибка chown failed: failed to look up user
В этом примере мы используем другой вид вложенного цикла with_subelements для создания каталогов, перечисленных в переменной users_with_items из цикла 1. В PHP цикл может выглядеть примерно так:
<?php
foreach ($users_with_items as $user) {
foreach ($user['personal_directories'] as $directory) {
// Create $directory for $user...
}
}
Обратите внимание, что мы перебираем массив $users_with_items и $user['personal_directories'] для каждого пользователя.
Переменные
users_with_items:
- name: "alice"
personal_directories:
- "bob"
- "carol"
- "dan"
- name: "bob"
personal_directories:
- "alice"
- "carol"
- "dan"
TASKS
- name: "Loop 3: create personal users' directories using 'with_subelements'."
file:
dest: "/home/{{ item.0.name }}/{{ item.1 }}"
owner: "{{ item.0.name }}"
group: "{{ item.0.name }}"
state: directory
with_subelements:
- "{{ users_with_items }}"
- personal_directories
Цикл with_subelements работает почти так же, как with_nested, за исключением того, что вместо второй переменной он принимает переменную и ключ другого списка, содержащегося в этой переменной — в данном случае personal_directories. Как и в цикле 2, первая итерация этого цикла создает (или проверяет существование) /home/alice/bob.
РЕЗУЛЬТАТЫ
/home/
├── alice/
│ ├── .ssh/
│ ├── bob/
│ ├── carol/
│ ├── dan/
│ └── loops/
└── bob/
├── .ssh/
├── alice/
├── carol/
├── dan/
└── loops/
ЦИКЛ 4: СОЗДАВАЙТЕ ПОЛЬЗОВАТЕЛЕЙ С ИСПОЛЬЗОВАНИЕМ WITH_DICT
Цикл 3 завершил настройку домашних каталогов, принадлежащих alice и bob, но есть еще два выдающихся пользователя, которые нужно создать, carol и dan. В этом примере этих пользователей создаются с помощью новой переменной users_with_dict и цикла Ansible with_dict.
Обратите внимание, что структура данных здесь содержит значимые ключи (dict или dictionary — это имя Python для ассоциативного массива); with_dict может быть лучшим вариантом, если вы вынуждены использовать данные с таким типом структуры. Цикл, который мы создаем здесь в Ansible, в PHP примерно такой:
<?php
foreach ($users_with_dict as $user => $properties) {
// Create a user named $user...
}
ПЕРЕМЕННЫЕ
users_with_dict:
carol:
common_directories: "{{ common_directories }}"
dan:
common_directories: "{{ common_directories }}"
TASKS
- name: "Loop 4: create users using 'with_dict'."
user:
name: "{{ item.key }}"
with_dict: "{{ users_with_dict }}"
Тип цикла with_dict довольно краток и позволяет получить доступ к ключам переменной и соответствующим значениям. К сожалению, у него есть один практический недостаток, а именно то, что невозможно перебрать подэлементы dict с помощью with_dict (так, например, мы не можем использовать with_dict для создания общих каталогов каждого пользователя).
РЕЗУЛЬТАТЫ
/home/
├── alice/
│ ├── .ssh/
│ ├── bob/
│ ├── carol/
│ ├── dan/
│ └── loops/
├── bob/
│ ├── .ssh/
│ ├── alice/
│ ├── carol/
│ ├── dan/
│ └── loops/
├── carol/
└── dan/
ЦИКЛ 5: СОЗДАВАЙТЕ ЛИЧНЫЕ КАТАЛОГИ, ЕСЛИ ОНИ НЕ СУЩЕСТВУЮТ
Поскольку мы не можем легко использовать users_with_dict, нам нужно использовать доступные инструменты Ansible, чтобы сделать это по-другому. Поскольку теперь мы создали необходимых пользователей alice, bob, carol и dan, мы можем повторно использовать цикл with_nested вместе с содержимым каталога /home/. В этом примере используется несколько новых функций, не связанных с циклами, чтобы показать, как циклы могут быть интегрированы в относительно сложные TASKS:
• Регистрируемые переменные Ansible
• Ansible условные выражения
• Jinja2 (переменные)
• Jinja2 (фильтры)
ПЕРЕМЕННЫЕ
common_directories:
- ".ssh"
- "loops"
TASKS
- name: "Get list of extant users."
shell: "find * -type d -prune | sort"
args:
chdir: "/home"
register: "home_directories"
changed_when: false
- name: "Loop 5: create personal user directories if they don't exist."
file:
dest: "/home/{{ item.0 }}/{{ item.1 }}"
owner: "{{ item.0 }}"
group: "{{ item.0 }}"
state: directory
with_nested:
- "{{ home_directories.stdout_lines }}"
- "{{ home_directories.stdout_lines | union(common_directories) }}"
when: "'{{ item.0 }}' != '{{ item.1 }}'"
Здесь у нас есть две TASKS: одна использует модуль shell для выполнения команды find на сервере, а другая использует file для создания каталогов.
При выполнении в каталоге /home команда find \ -type d -prune | sort (выполняется модулем shell) вернет только имена каталогов, найденных внутри /home, другими словами, имена всех пользователей, каталоги которых необходимо подготовить.
Вывод этой команды сохраняется в переменной home_directories строкой register: "home_directories" в задаче. Важная часть этой переменной, которую мы будем использовать в следующей задаче, выглядит так:
"stdout_lines": [
"alice",
"bob",
"carol",
"dan",
],
Вторая задача в этом примере (фактический цикл) почти полностью совпадает с циклом with_nested во втором примере, но следует отметить два отличия:
- Вторая строка в разделе with_nested выглядит несколько необычно:
- "{{ home_directories.stdout_lines | union(common_directories) }}"
- Есть еще одна строка, начинающаяся с when в конце TASKS:
when: "'{{ item.0 }}' != '{{ item.1 }}'"
Давайте пройдемся по ним по очереди. Нечетная строка под with_nested применяет фильтр Jinja2 к новому списку каталогов из первой TASKS выше (это часть home_directories.stdout_lines). Базовый синтаксис фильтров Jinja:
• объект для фильтрации (home_directories.stdout_lines)
• применить фильтр (|)
• имя фильтра плюс аргументы, если есть (union (common_directories))
Другими словами, мы используем фильтр для объединения home_directories.stdout_lines и переменной common_directories из начала этого примера в единый массив:
item:
- .ssh
- alice
- bob
- carol
- dan
- loops
Это означает, что наш цикл with_nested будет перебирать каждый из home_directories.stdout_lines (первая строка with_nested) и гарантировать, что каждый из каталогов во второй строке существует в домашнем каталоге каждого пользователя.
К сожалению, это дало бы нам неверный результат — если бы мы полагались только на цикл, мы бы обнаружили, что домашний каталог каждого пользователя будет содержать каталог с тем же именем, что и домашний каталог! (например, /home/alice/alice, /home/bob/bob и т. д.) Вот где появляются условные выражения Ansible — when — приходят:
when: "'{{ item.0 }}' != '{{ item.1 }}'"
Эта строка не позволяет задаче создать каталог, когда текущий элемент в home_directories.stdout_lines и текущий элемент в нашем объединении home_directories.stdout_lines идентичны (как указано в документации Ansible Loops, «… при объединении when с with_items (или любой другой оператор цикла), оператор when обрабатывается отдельно для каждого элемента »). В PHP то, что мы делаем во второй задаче, будет выглядеть примерно так:
<?php
$users = ['alice', 'bob', 'carol', 'dan'];
$common_directories = ['.ssh', 'loops'];
$directories = $user + $common_directories;
foreach ($users as $user) {
foreach ($directories as $directory) {
if ($directory != $user) {
// Create the directory…
}
}
}
Это дает нам набор результатов, показанных ниже, и завершает подготовку нашего тестового примера.
РЕЗУЛЬТАТЫ
/home/
├── alice/
│ ├── .ssh/
│ ├── bob/
│ ├── carol/
│ ├── dan/
│ └── loops/
├── bob/
│ ├── .ssh/
│ ├── alice/
│ ├── carol/
│ ├── dan/
│ └── loops/
├── carol/
│ ├── .ssh/
│ ├── alice/
│ ├── bob/
│ ├── dan/
│ └── loops/
└── dan/
├── .ssh/
├── alice/
├── bob/
├── carol/
└── loops/
ВЫВОДЫ
Циклы Ansible довольно странные. Они не только декларативны (как и все остальное в Ansible), но и имеют много разных типов, некоторые из имен которых (with_nested? with_subitems?) Трудно распутать.
С другой стороны, они достаточно мощны, чтобы выполнять TASKS, хотя это может потребовать небольшого сдвига в мышлении (во многом подобно языковым функциям, таким как array_filter, array_reduce, array_map и другим подобным функциям, когда вы впервые сталкиваетесь с ними). Прошло некоторое время, прежде чем я действительно начал понимать, что необходимо присоединить цикл к задаче — даже если это иногда означает повторение одних и тех же данных более одного раза — вместо выполнения одной или нескольких задач внутри цикла.
Надеюсь, этот пост поможет вам избавиться от моего первоначального затруднения. С этой целью я собрал виртуальную машину Vagrant (Vagrant изначально поддерживает использование Ansible для подготовки) и Ansible playbook, который я использовал для создания и тестирования этих примеров). Просто следуйте инструкциям в README, чтобы запустить примеры из этого сообщения или попробовать свои собственные. Если у вас есть какие-либо вопросы или комментарии, напишите нам в Twitter по адресу @chromaticHQ!
===========
Источник:
habr.com
===========
===========
Автор оригинала: Christopher Torgalson
===========Похожие новости:
- [IT-инфраструктура, NoSQL, Big Data, Софт] Как лицензируется и чем отличаются лицензии Elastic Stack (Elasticsearch)
- [Информационная безопасность, Системное администрирование, Сетевые технологии, Сетевое оборудование, Интервью] Консилиум с D-Link: базовая настройка управляемого сетевого оборудования
- [Системное администрирование, Assembler, Сжатие данных, Разработка под Windows, История IT] Windows 95 на двух флоппиках
- [Разработка веб-сайтов, Программирование, IT-инфраструктура, Управление проектами, DevOps] Веб-разработка с нуля: руководство для молодых команд по созданию инфраструктуры CI/CD и процесса разработки
- [Git, DevOps, Облачные сервисы] GitOps — плохой и злой (перевод)
- [Системное администрирование, IT-инфраструктура, Серверное администрирование, DevOps] Docker swarm и балансировка нагрузки по нодам
- [Разработка веб-сайтов, Фриланс, DevOps, Удалённая работа] Будет ли оплата труда привязана к местоположению в будущем
- [Информационная безопасность, IT-инфраструктура] Зачем нужны сертифицированные средства защиты информации?
- [Учебный процесс в IT, Карьера в IT-индустрии, DevOps] Я занялся преподаванием и не бросил работу. Совмещать — офигенно
- [IT-инфраструктура, Хранение данных, DevOps, Облачные сервисы] Принципы организации объектных хранилищ (перевод)
Теги для поиска: #_sistemnoe_administrirovanie (Системное администрирование), #_itinfrastruktura (IT-инфраструктура), #_servernoe_administrirovanie (Серверное администрирование), #_devops, #_ansible, #_sistemnoe_administrirovanie (
Системное администрирование
), #_itinfrastruktura (
IT-инфраструктура
), #_servernoe_administrirovanie (
Серверное администрирование
), #_devops
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:33
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В посте рассматриваются следующие Ansible модули loop: with_items, with_nested, with_subelements, with_dict. Исходный код https://github.com/ctorgalson/ansible-loops Одна из моих ролей в Chromatic — член команды DevOps. Помимо прочего, это включает в себя работу с нашими серверами и серверами наших клиентов. Это, в свою очередь, означает, что я трачу много времени на работу с Ansible, популярным инструментом для инициализации, настройки и развертывания серверов и приложений. Проще говоря, машина, на которой запущен Ansible, запускает команды на другом компьютере через SSH. Эти команды указываются декларативно (не обязательно) с использованием небольших участков YAML, называемых задачами. Эти TASKS вызывают модули Ansible, которые специализируются на выполнении опций с определенными компонентами, такими как файлы, базы данных и т. д. Например, следующая задача использует модуль File (документация, код) для создания определенного каталога, если он еще не существует, и изменяет его атрибуты, если они еще не установлены правильно: - file:
path: /home/jenkins/.ssh state: directory owner: jenkins group: jenkins mode: 700 Несколько задач, относящихся к одной задаче, сгруппированы в роли, а несколько ролей могут быть сгруппированы в playbooks. Затем можно использовать playbook для выполнения точно таких же шагов конфигурации на любом количестве серверов одновременно. Ansible декларативный? TASKS Ansible записываются декларативно, то есть мы не указываем, какая базовая реализация должна использоваться для выполнения TASKS. Это полезно, поскольку обеспечивает высокий уровень абстракции, очень читаемый и относительно простой для написания кода, а в некоторых случаях позволяет нам использовать одну и ту же задачу на разных платформах. Например, есть модуль Ansible Copy, который используется для копирования файлов на конечный компьютер. В следующей задаче Ansible копирует файл конфигурации в правильный каталог на удаленном компьютере и устанавливает владельца, группу и права доступа к файлу: - name: Copy SSH config file into Alice’s .ssh directory.
copy: src: files/config dest: /home/alice/.ssh/config owner: alice group: alice mode: 0600 Для достижения того же результата мы могли бы, например, написать серию команд или функцию в bash, используя scp, chown и chmod. С Ansible мы можем сосредоточиться на желаемой конфигурации, не слишком заботясь о деталях. С другой стороны, это также означает, что доступные инструменты иногда кажутся странными или необычными — в основном потому, что разработчики обычно имеют доступ к императивным инструментам в тех случаях, когда декларативный вариант не подходит. Одно место, где я заметил это в Ansible, — это многократное выполнение одной и той же TASKS с набором разных элементов. В частности, я нашел инструменты циклов Ansible немного странными, не в последнюю очередь потому, что их шестнадцать — по сравнению с PHP, который имеет четыре вида циклов. На самом деле для этого есть причина, если вас интересует внутреннее устройство Ansible. На странице Loops в документации указано, что «loops на самом деле представляют собой комбинацию вещей с _ + lookup(), поэтому любой плагин поиска можно использовать в качестве источника для цикла». Поиск (Lookups) — это тип плагина Ansible, который используется для «доступа к данным в Ansible из внешних источников», и если вы сравните документацию Loops и каталог плагинов Ansible на Github, вы увидите многие из них с одинаковыми именами. Однако в документации Ansible поисковые запросы рассматриваются как «расширенная тема», и нет необходимости углубляться в исходный код, чтобы научиться использовать сами циклы. В оставшейся части этого поста описаны несколько наиболее часто используемых циклов Ansible, а также некоторые вещи, которые я узнал о том, как их использовать. ЦИКЛЫ ANSIBLE TASKS, выполняемые в следующих примерах, являются более или менее произвольными примерами, связанными с созданием пользователей и их каталогов, но они тесно связаны с реальными задачами, которые могут потребоваться на производственных серверах (но обратите внимание: данные, с которыми мы должны работать, и количество задач, которые мы используем для достижения правильной конфигурации, явно нереально!) Примеры основываются друг на друге для выполнения следующих простых задач на гипотетическом сервере:
/home/alice/
├── .ssh/ ├── bob/ ├── carol/ ├── dan/ └── loops/ ЦИКЛ 1. СОЗДАНИЕ ПОЛЬЗОВАТЕЛЕЙ WITH_ITEMS Типичная задача в Ansible может выглядеть примерно так, когда пользователь удаляет пользователя chuck из системы, в которой выполняется задача: - name: Remove user ‘Chuck’ from the system.
user: name: chuck state: absent remove: yes Чтобы повторить эту задачу для нескольких пользователей — скажем, нам нужно удалить пользователей Chuck и Craig — мы просто добавляем в задачу параметр with_items. with_items принимает либо список (показанный здесь), либо переменную (как в остальных следующих примерах): - name: Remove users ‘Chuck’ and ‘Craig’ from the system.
user: name: "{{ item }}" state: absent remove: yes with_items: - chuck - craig Возвращаясь к нашему первому примеру цикла, мы можем использовать with_items для создания первых пользователей в нашем списке, alice и bob: Переменные users_with_items:
- name: "alice" personal_directories: - "bob" - "carol" - "dan" - name: "bob" personal_directories: - "alice" - "carol" - "dan" TASKS - name: "Loop 1: create users using 'with_items'."
user: name: "{{ item.name }}" with_items: "{{ users_with_items }}" Здесь мы используем модуль Ansible User для перебора переменной с именем users_with_items. Эта переменная содержит имена и информацию о двух пользователях, но задача только гарантирует, что пользователи существуют в системе, она не создает каталоги, содержащиеся в списке personal_directories каждого пользователя (обратите внимание, что personal_directories — это просто произвольный ключ в массиве данных для нашего примера). Это примечательная особенность циклов Ansible (и Ansible в целом): поскольку TASKS вызывают определенные модули с определенной проблемной областью, обычно в задаче невозможно выполнять более одного вида вещей. В данном конкретном случае это означает, что мы не можем убедиться, что personal_directories пользователя существуют из этой TASKS (т. е. Потому что мы используем модуль User, а не модуль File). Цикл with_items работает примерно так же, как этот цикл PHP: <?php
foreach ($users_with_items as $user) { // Do something with $user... } Мы писали задачу как обычно, за исключением того, что: • Мы заменили имя переменной item.name на имя пользователя. • Мы добавили строку with_items, определяющую переменную для перебора. Также стоит отметить, что внутри цикла Ansible текущая итерация всегда является item, и доступ к любому заданному свойству осуществляется с помощью item.property. РЕЗУЛЬТАТЫ /home/
├── alice/ └── bob/ ЦИКЛ 2: СОЗДАВАЙТЕ КАТАЛОГИ ОБЩИХ ПОЛЬЗОВАТЕЛЕЙ, ИСПОЛЬЗУЯ WITH_NESTED Примечание: Для цикла 2 нужны созданные юзеры, например с помощью цикла 1. Иначе будет ошибка chown failed: failed to look up user В этом примере мы используем две переменные, users_with_items из цикла 1, и новую, common_directories, которая представляет собой список всех каталогов, которые должны присутствовать в каталоге каждого пользователя. Это означает, что (снова возвращаясь к PHP), нам нужно что-то, что работает примерно так: <?php
foreach ($users_with_items as $user) { foreach ($common_directories as $directory) { // Create $directory for $user... } } В Ansible мы можем использовать цикл with_nested. Циклы with_nested принимают два списка, второй из которых повторяется на каждой итерации первого: Переменные users_with_items:
- name: "alice" personal_directories: - "bob" - "carol" - "dan" - name: "bob" personal_directories: - "alice" - "carol" - "dan" common_directories: - ".ssh" - "loops TASKS # Note that this does not set correct permissions on /home/{{ item.x.name }}/.ssh!
- name: "Loop 2: create common users' directories using 'with_nested'." file: dest: "/home/{{ item.0.name }}/{{ item.1 }}" owner: "{{ item.0.name }}" group: "{{ item.0.name }}" state: directory with_nested: - "{{ users_with_items }}" - "{{ common_directories }}" Как показано в приведенной выше задаче, к двум спискам в with_nested можно получить доступ, используя item.0 (для users_with_items) и item.1 (для common_directories) соответственно. Это позволяет нам, например, создайте каталог /home/alice/.ssh на самой первой итерации. РЕЗУЛЬТАТЫ /home/
├── alice/ │ ├── .ssh/ │ └── loops/ └── bob/ ├── .ssh/ └── loops/ ЦИКЛ 3: СОЗДАВАЙТЕ ЛИЧНЫЕ КАТАЛОГИ ПОЛЬЗОВАТЕЛЕЙ, ИСПОЛЬЗУЯ WITH_SUBELEMENTS Примечание: Для цикла 3 нужны созданные юзеры, например с помощью цикла 1. Иначе будет ошибка chown failed: failed to look up user В этом примере мы используем другой вид вложенного цикла with_subelements для создания каталогов, перечисленных в переменной users_with_items из цикла 1. В PHP цикл может выглядеть примерно так: <?php
foreach ($users_with_items as $user) { foreach ($user['personal_directories'] as $directory) { // Create $directory for $user... } } Обратите внимание, что мы перебираем массив $users_with_items и $user['personal_directories'] для каждого пользователя. Переменные users_with_items:
- name: "alice" personal_directories: - "bob" - "carol" - "dan" - name: "bob" personal_directories: - "alice" - "carol" - "dan" TASKS - name: "Loop 3: create personal users' directories using 'with_subelements'."
file: dest: "/home/{{ item.0.name }}/{{ item.1 }}" owner: "{{ item.0.name }}" group: "{{ item.0.name }}" state: directory with_subelements: - "{{ users_with_items }}" - personal_directories Цикл with_subelements работает почти так же, как with_nested, за исключением того, что вместо второй переменной он принимает переменную и ключ другого списка, содержащегося в этой переменной — в данном случае personal_directories. Как и в цикле 2, первая итерация этого цикла создает (или проверяет существование) /home/alice/bob. РЕЗУЛЬТАТЫ /home/
├── alice/ │ ├── .ssh/ │ ├── bob/ │ ├── carol/ │ ├── dan/ │ └── loops/ └── bob/ ├── .ssh/ ├── alice/ ├── carol/ ├── dan/ └── loops/ ЦИКЛ 4: СОЗДАВАЙТЕ ПОЛЬЗОВАТЕЛЕЙ С ИСПОЛЬЗОВАНИЕМ WITH_DICT Цикл 3 завершил настройку домашних каталогов, принадлежащих alice и bob, но есть еще два выдающихся пользователя, которые нужно создать, carol и dan. В этом примере этих пользователей создаются с помощью новой переменной users_with_dict и цикла Ansible with_dict. Обратите внимание, что структура данных здесь содержит значимые ключи (dict или dictionary — это имя Python для ассоциативного массива); with_dict может быть лучшим вариантом, если вы вынуждены использовать данные с таким типом структуры. Цикл, который мы создаем здесь в Ansible, в PHP примерно такой: <?php
foreach ($users_with_dict as $user => $properties) { // Create a user named $user... } ПЕРЕМЕННЫЕ users_with_dict:
carol: common_directories: "{{ common_directories }}" dan: common_directories: "{{ common_directories }}" TASKS - name: "Loop 4: create users using 'with_dict'."
user: name: "{{ item.key }}" with_dict: "{{ users_with_dict }}" Тип цикла with_dict довольно краток и позволяет получить доступ к ключам переменной и соответствующим значениям. К сожалению, у него есть один практический недостаток, а именно то, что невозможно перебрать подэлементы dict с помощью with_dict (так, например, мы не можем использовать with_dict для создания общих каталогов каждого пользователя). РЕЗУЛЬТАТЫ /home/
├── alice/ │ ├── .ssh/ │ ├── bob/ │ ├── carol/ │ ├── dan/ │ └── loops/ ├── bob/ │ ├── .ssh/ │ ├── alice/ │ ├── carol/ │ ├── dan/ │ └── loops/ ├── carol/ └── dan/ ЦИКЛ 5: СОЗДАВАЙТЕ ЛИЧНЫЕ КАТАЛОГИ, ЕСЛИ ОНИ НЕ СУЩЕСТВУЮТ Поскольку мы не можем легко использовать users_with_dict, нам нужно использовать доступные инструменты Ansible, чтобы сделать это по-другому. Поскольку теперь мы создали необходимых пользователей alice, bob, carol и dan, мы можем повторно использовать цикл with_nested вместе с содержимым каталога /home/. В этом примере используется несколько новых функций, не связанных с циклами, чтобы показать, как циклы могут быть интегрированы в относительно сложные TASKS: • Регистрируемые переменные Ansible • Ansible условные выражения • Jinja2 (переменные) • Jinja2 (фильтры) ПЕРЕМЕННЫЕ common_directories:
- ".ssh" - "loops" TASKS - name: "Get list of extant users."
shell: "find * -type d -prune | sort" args: chdir: "/home" register: "home_directories" changed_when: false - name: "Loop 5: create personal user directories if they don't exist." file: dest: "/home/{{ item.0 }}/{{ item.1 }}" owner: "{{ item.0 }}" group: "{{ item.0 }}" state: directory with_nested: - "{{ home_directories.stdout_lines }}" - "{{ home_directories.stdout_lines | union(common_directories) }}" when: "'{{ item.0 }}' != '{{ item.1 }}'" Здесь у нас есть две TASKS: одна использует модуль shell для выполнения команды find на сервере, а другая использует file для создания каталогов. При выполнении в каталоге /home команда find \ -type d -prune | sort (выполняется модулем shell) вернет только имена каталогов, найденных внутри /home, другими словами, имена всех пользователей, каталоги которых необходимо подготовить. Вывод этой команды сохраняется в переменной home_directories строкой register: "home_directories" в задаче. Важная часть этой переменной, которую мы будем использовать в следующей задаче, выглядит так: "stdout_lines": [
"alice", "bob", "carol", "dan", ], Вторая задача в этом примере (фактический цикл) почти полностью совпадает с циклом with_nested во втором примере, но следует отметить два отличия:
- "{{ home_directories.stdout_lines | union(common_directories) }}"
Давайте пройдемся по ним по очереди. Нечетная строка под with_nested применяет фильтр Jinja2 к новому списку каталогов из первой TASKS выше (это часть home_directories.stdout_lines). Базовый синтаксис фильтров Jinja: • объект для фильтрации (home_directories.stdout_lines) • применить фильтр (|) • имя фильтра плюс аргументы, если есть (union (common_directories)) Другими словами, мы используем фильтр для объединения home_directories.stdout_lines и переменной common_directories из начала этого примера в единый массив: item:
- .ssh - alice - bob - carol - dan - loops Это означает, что наш цикл with_nested будет перебирать каждый из home_directories.stdout_lines (первая строка with_nested) и гарантировать, что каждый из каталогов во второй строке существует в домашнем каталоге каждого пользователя. К сожалению, это дало бы нам неверный результат — если бы мы полагались только на цикл, мы бы обнаружили, что домашний каталог каждого пользователя будет содержать каталог с тем же именем, что и домашний каталог! (например, /home/alice/alice, /home/bob/bob и т. д.) Вот где появляются условные выражения Ansible — when — приходят: when: "'{{ item.0 }}' != '{{ item.1 }}'"
Эта строка не позволяет задаче создать каталог, когда текущий элемент в home_directories.stdout_lines и текущий элемент в нашем объединении home_directories.stdout_lines идентичны (как указано в документации Ansible Loops, «… при объединении when с with_items (или любой другой оператор цикла), оператор when обрабатывается отдельно для каждого элемента »). В PHP то, что мы делаем во второй задаче, будет выглядеть примерно так: <?php
$users = ['alice', 'bob', 'carol', 'dan']; $common_directories = ['.ssh', 'loops']; $directories = $user + $common_directories; foreach ($users as $user) { foreach ($directories as $directory) { if ($directory != $user) { // Create the directory… } } } Это дает нам набор результатов, показанных ниже, и завершает подготовку нашего тестового примера. РЕЗУЛЬТАТЫ /home/
├── alice/ │ ├── .ssh/ │ ├── bob/ │ ├── carol/ │ ├── dan/ │ └── loops/ ├── bob/ │ ├── .ssh/ │ ├── alice/ │ ├── carol/ │ ├── dan/ │ └── loops/ ├── carol/ │ ├── .ssh/ │ ├── alice/ │ ├── bob/ │ ├── dan/ │ └── loops/ └── dan/ ├── .ssh/ ├── alice/ ├── bob/ ├── carol/ └── loops/ ВЫВОДЫ Циклы Ansible довольно странные. Они не только декларативны (как и все остальное в Ansible), но и имеют много разных типов, некоторые из имен которых (with_nested? with_subitems?) Трудно распутать. С другой стороны, они достаточно мощны, чтобы выполнять TASKS, хотя это может потребовать небольшого сдвига в мышлении (во многом подобно языковым функциям, таким как array_filter, array_reduce, array_map и другим подобным функциям, когда вы впервые сталкиваетесь с ними). Прошло некоторое время, прежде чем я действительно начал понимать, что необходимо присоединить цикл к задаче — даже если это иногда означает повторение одних и тех же данных более одного раза — вместо выполнения одной или нескольких задач внутри цикла. Надеюсь, этот пост поможет вам избавиться от моего первоначального затруднения. С этой целью я собрал виртуальную машину Vagrant (Vagrant изначально поддерживает использование Ansible для подготовки) и Ansible playbook, который я использовал для создания и тестирования этих примеров). Просто следуйте инструкциям в README, чтобы запустить примеры из этого сообщения или попробовать свои собственные. Если у вас есть какие-либо вопросы или комментарии, напишите нам в Twitter по адресу @chromaticHQ! =========== Источник: habr.com =========== =========== Автор оригинала: Christopher Torgalson ===========Похожие новости:
Системное администрирование ), #_itinfrastruktura ( IT-инфраструктура ), #_servernoe_administrirovanie ( Серверное администрирование ), #_devops |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:33
Часовой пояс: UTC + 5