Иногда вам может потребоваться повторить задание несколько раз. Например, вы можете создать несколько пользователей, запустить/остановить несколько служб или сменить владельца для нескольких файлов на управляемых хостах.
В этой статье вы узнаете, как использовать циклы Ansible для многократного повторения задачи без необходимости переписывать всю задачу снова и снова.
Вы должны знать концепцию плейбуков Ansible, знать специальные команды и знать основную терминологию, связанную с Ansible, например, список, словари и т . д.
Также приветствуется знание основ YAML.
Цикл по спискам
Ansible использует цикл ключевых слов для перебора элементов списка. Для демонстрации давайте создадим очень простую книгу с именем print-list.yml, которая покажет вам, как распечатать элементы в списке:
[destroyer@andreyex]$ cat print-list.yml --- - name: print list hosts: node1 vars: prime: [2,3,5,7,11] tasks: - name: Show first five prime numbers debug: msg: "{{ item }}" loop: "{{ prime }}"
Обратите внимание, что мы используем переменную item с циклами Ansible. Задача будет выполняться пять раз, что равно количеству элементов в простом списке.
При первом запуске переменная элемента будет установлена на первый элемент в массиве простых чисел (2). При втором запуске переменная item будет установлена на второй элемент в массиве простых чисел (3) и так далее.
Следуйте дальше и запустите playbook, чтобы увидеть все отображаемые элементы основного списка:
[destroyer@andreyex]$ ansible-playbook print-list.yml PLAY [print list] ************************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Show first five prime numbers] ******************************************* ok: [node1] => (item=2) => { "msg": 2 } ok: [node1] => (item=3) => { "msg": 3 } ok: [node1] => (item=5) => { "msg": 5 } ok: [node1] => (item=7) => { "msg": 7 } ok: [node1] => (item=11) => { "msg": 11 } PLAY RECAP ********************************************************************* node1 : ok=2 changed=0 unreachable=0 failed=0
Теперь вы применяете циклы к реальному приложению. Например, вы можете создать playbook add-users.yml, который добавит нескольких пользователей на все хосты в группе dbservers:
[destroyer@andreyex]$ cat add-users.yml --- - name: Add multiple users hosts: dbservers vars: dbusers: - username: brad pass: pass1 - username: david pass: pass2 - username: jason pass: pass3 tasks: - name: Add users user: name: "{{ item.username }}" password: "{{ item.pass | password_hash('sha512') }}" loop: "{{ dbusers }}"
Сначала мы создали список dbusers, который в основном представляет собой список хешей/словарей. Затем мы использовали пользовательский модуль вместе с циклом, чтобы добавить пользователей и установить пароли для всех пользователей в списке dbusers.
Обратите внимание, что мы также использовали точечную нотацию item.username и item.pass для доступа к значениям ключей внутри хешей/словарей списка dbusers.
Также стоит отметить, что мы использовали фильтр password_hash (‘sha512’) для шифрования паролей пользователей с помощью алгоритма хеширования sha512, поскольку пользовательский модуль не позволял устанавливать незашифрованные пароли пользователей.
Теперь запустим playbook add-users.yml :
[destroyer@andreyex]$ ansible-playbook add-users.yml PLAY [Add multiple users] ****************************************************** TASK [Gathering Facts] ********************************************************* ok: [node4] TASK [Add users] *************************************************************** changed: [node4] => (item={'username': 'brad', 'pass': 'pass1'}) changed: [node4] => (item={'username': 'david', 'pass': 'pass2'}) changed: [node4] => (item={'username': 'jason', 'pass': 'pass3'}) PLAY RECAP ********************************************************************* node4 : ok=2 changed=1 unreachable=0 failed=0 skipped=0
Вы можете убедиться, что три пользователя добавлены, выполнив специальную команду Ansible:
[destroyer@andreyex]$ ansible dbservers -m command -a "tail -3 /etc/passwd" node4 | CHANGED | rc=0 >> brad:x:1001:1004::/home/brad:/bin/bash david:x:1002:1005::/home/david:/bin/bash jason:x:1003:1006::/home/jason:/bin/bash
Перебирать словари
Вы можете использовать цикл только со списками. Вы получите сообщение об ошибке, если попытаетесь перебрать словарь.
Например, если вы запустите следующую книгу воспроизведения print-dict.yml:
[destroyer@andreyex]$ cat print-dict.yml --- - name: Print Dictionary hosts: node1 vars: employee: name: "destroyer Alderson" title: "Penetration Tester" company: "Linux AndreyEx" tasks: - name: Print employee dictionary debug: msg: "{{ item }}" loop: "{{ employee }}"
Вы получите следующую ошибку:
[destroyer@andreyex]$ ansible-playbook print-dict.yml PLAY [Print Dictionary] ******************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Print employee dictionary] *********************************************** fatal: [node1]: FAILED! => {"msg": "Invalid data passed to 'loop', it requires a list, got this instead: {'name': 'destroyer Alderson', 'title': 'Penetration Tester', 'company': 'Linux AndreyEx'}. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."}
Как видите, ошибка явно говорит о том, что требуется список.
Чтобы исправить эту ошибку; вы можете использовать фильтр dict2items для преобразования словаря в список. Итак, в playbook print-dict.yml, отредактируйте строку:
loop: "{{ employee }}"
и примените фильтр dict2items следующим образом:
loop: "{{ employee | dict2items }}"
Затем снова запустите playbook:
[destroyer@andreyex]$ ansible-playbook print-dict.yml PLAY [Print Dictionary] ******************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Print employee dictionary] *********************************************** ok: [node1] => (item={'key': 'name', 'value': 'destroyer Alderson'}) => { "msg": { "key": "name", "value": "destroyer Alderson" } } ok: [node1] => (item={'key': 'title', 'value': 'Penetration Tester'}) => { "msg": { "key": "title", "value": "Penetration Tester" } } ok: [node1] => (item={'key': 'company', 'value': 'Linux AndreyEx'}) => { "msg": { "key": "company", "value": "Linux AndreyEx" } } PLAY RECAP ********************************************************************* node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
Успех! Были отображены пары ключ/значение словаря сотрудников.
Цикл по диапазону чисел
Вы можете использовать функцию range() вместе с фильтром списка для циклического перебора диапазона чисел.
Например, следующая задача распечатает все числа от 0 до 9:
- name: Range Loop debug: msg: "{{ item }}" loop: "{{ range(10) | list }}"
Вы также можете начать свой диапазон с числа, отличного от нуля. Например, следующая задача напечатает все числа от 5 до 14:
- name: Range Loop debug: msg: "{{ item }}" loop: "{{ range(5,15) | list }}"
По умолчанию шаг установлен на 1. Однако вы можете установить другой шаг.
Например, следующая задача распечатает все четные IP-адреса в подсети 192.168.1.x:
- name: Range Loop debug: msg: 192.168.1.{{ item }} loop: "{{ range(0,256,2) | list }}"
Где начало = 0, конец <256 и шаг = 2.
Зацикливание на запасах
Вы можете использовать встроенную переменную групп Ansible, чтобы перебрать все ваши хосты инвентаризации или только их подмножество. Например, чтобы перебрать все ваши инвентарные хосты; ты можешь использовать:
loop: "{{ groups['all'] }}"
Если вы хотите перебрать все хосты в группе веб-серверов, вы можете использовать:
loop: "{{ groups['webservers'] }}"
Чтобы увидеть, как это работает в playbook; взгляните на следующую книгу loop-inventory.yml:
[destroyer@andreyex]$ cat loop-inventory.yml --- - name: Loop over Inventory hosts: node1 tasks: - name: Ping all hosts command: ping -c 1 "{{ item }}" loop: "{{ groups['all'] }}"
Этот сценарий проверяет, может ли node1 пинговать все остальные хосты в вашем инвентаре. Идите вперед и запустите playbook:
[destroyer@andreyex]$ ansible-playbook loop-inventory.yml PLAY [Loop over Inventory] ***************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Ping all hosts] ********************************************************** changed: [node1] => (item=node1) changed: [node1] => (item=node2) changed: [node1] => (item=node3) changed: [node1] => (item=node4) PLAY RECAP ********************************************************************* node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0
Если вы получите какие-либо ошибки; это будет означать, что ваши управляемые хосты не могут пинговать (достигать) друг друга.
Пауза в циклах
Вы можете сделать паузу на определенное время между каждой итерацией цикла. Для этого вы можете использовать директиву pause вместе с ключевым словом loop_control.
Чтобы продемонстрировать это, давайте напишем playbook countdown.yml, который будет просто делать десятисекундный обратный отсчет перед отображением сообщения «С днем рождения!» на экране:
[destroyer@andreyex]$ cat countdown.yml --- - name: Happy Birthday Playbook hosts: node1 tasks: - name: Ten seconds countdown debug: msg: "{{ 10 - item }} осталось несколько секунд ..." loop: "{{ range(10) | list }}" loop_control: pause: 1 - name: Display Happy Birthday debug: msg: "С днем рождения!"
Идите вперед и запустите playbook:
[destroyer@andreyex]$ ansible-playbook countdown.yml PLAY [С днем рождения Playbook] ************************************************* TASK [Сбор Фактов] ********************************************************* ok: [node1] TASK [Подождите десять секунд] **************************************************** ok: [node1] => (item=0) => { "msg": "10 осталось несколько секунд ..." } ok: [node1] => (item=1) => { "msg": "9 осталось несколько секунд ..." } ok: [node1] => (item=2) => { "msg": "8 осталось несколько секунд ..." } ok: [node1] => (item=3) => { "msg": "7 осталось несколько секунд ..." } ok: [node1] => (item=4) => { "msg": "6 осталось несколько секунд ..." } ok: [node1] => (item=5) => { "msg": "5 осталось несколько секунд ..." } ok: [node1] => (item=6) => { "msg": "4 осталось несколько секунд ..." } ok: [node1] => (item=7) => { "msg": "3 осталось несколько секунд ..." } ok: [node1] => (item=8) => { "msg": "2 осталось несколько секунд ..." } ok: [node1] => (item=9) => { "msg": "1 осталось несколько секунд ..." } TASK [Дисплей: с днем рождения] ************************************************** ok: [node1] => { "msg": "С днем рождения!" } PLAY RECAP ********************************************************************* node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0
Итак, вы познакомились с циклами в Ansible.