Иногда вам может потребоваться повторить задание несколько раз. Например, вы можете создать несколько пользователей, запустить/остановить несколько служб или сменить владельца для нескольких файлов на управляемых хостах.
В этой статье вы узнаете, как использовать циклы 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.