ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE)

Ansible. Циклы

Краткое руководство: Как установить и настроить ansible в Linux для автоматизации

Иногда вам может потребоваться повторить задание несколько раз. Например, вы можете создать несколько пользователей, запустить/остановить несколько служб или сменить владельца для нескольких файлов на управляемых хостах.

В этой статье вы узнаете, как использовать циклы 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.

Exit mobile version