В этой статье вы узнаете, как добавить навыки принятия решений в свои сценарии Ansible.
Вы научитесь:
- Используйте операторы when для условного выполнения задач.
- Используйте инструкции блока для реализации обработки исключений.
- Используйте обработчики Ansible для запуска задач при изменении.
Излишне говорить, что вы должны быть знакомы со статьями Ansible, специальными командами и другими основами Ansible, чтобы понять эту статью.
В этой статье используется та же настройка, которая была упомянута в первой статье: 1 элемент управления Red Hat, 3 узла CentOS и 1 узел Ubuntu.
Выбор того, когда запускать задачи
Давайте начнем ставить условия, когда запускать определенную задачу с помощью Ansible.
Использование когда с фактами
Вы можете использовать условные выражения when для запуска задачи, только если выполняется определенное условие. Для демонстрации создайте новую книгу воспроизведения с именем ubuntu-server.yml со следующим содержимым:
[destroyer@andreyex]$ cat ubuntu-server.yml --- - name: Using when with facts hosts: all tasks: - name: Detect Ubuntu Servers debug: msg: "This is an Ubuntu Server." when: ansible_facts['distribution'] == "Ubuntu"
Теперь запустите playbook:
[destroyer@andreyex]$ ansible-playbook ubuntu-servers.yml PLAY [Using when with facts] ******************************************* TASK [Gathering Facts] ********************************************************* ok: [node4] ok: [node1] ok: [node3] ok: [node2] TASK [Detect Ubuntu Servers] *************************************************** skipping: [node1] skipping: [node2] skipping: [node3] ok: [node4] => { "msg": "Это сервер Ubuntu." } PLAY RECAP ********************************************************************* node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 node2 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 node3 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 node4 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
Обратите внимание, как мы использовали факт Ansible ansible_facts[‘distribution’] в условии when, чтобы проверить, на каких узлах работает Ubuntu. Также обратите внимание, что вам не нужно заключать переменные в фигурные скобки при использовании условных выражений when.
В выходных данных playbook обратите внимание, как TASK [Detect Ubuntu Servers] пропущены первые три узла, поскольку все они работают под CentOS и работают только на node4, поскольку он работает под управлением Ubuntu.
Использование when с регистрами
Вы также можете использовать условные выражения с зарегистрированными переменными. Например, следующая книга centos-servers.yml покажет, на каких узлах работает CentOS:
[destroyer@andreyex]$ cat centos-servers.yml --- - name: Using when with registers hosts: all tasks: - name: Save the contents of /etc/os-release command: cat /etc/os-release register: os_release - name: Detect CentOS Servers debug: msg: "Запуск CentOS..." when: os_release.stdout.find('CentOS') != -1
Плейбук сначала начинается с сохранения содержимого файла /etc/os-release в регистр os_release . Затем вторая задача отображает сообщение «Запуск CentOS…», только если слово «CentOS» встречается в стандартном выводе os_release .
Идите вперед и запустите playbook:
[destroyer@andreyex]$ ansible-playbook centos-servers.yml PLAY [Using when with registers] *********************************************** TASK [Gathering Facts] ********************************************************* ok: [node4] ok: [node1] ok: [node3] ok: [node2] TASK [Save the contents of /etc/os-release] ************************************ changed: [node4] changed: [node1] changed: [node2] changed: [node3] TASK [Detect CentOS Servers] *************************************************** ok: [node1] => { "msg": "Запуск CentOS..." } ok: [node2] => { "msg": "Запуск CentOS..." } ok: [node3] => { "msg": "Запуск CentOS..." } skipping: [node4] PLAY RECAP ********************************************************************* node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 node2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 node3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 node4 : ok=2 changed=1 unreachable=0 failed=0 skipped=1
Обратите внимание, как работает TASK [Detect CentOS Servers] только на первых трех узлах и пропускается node4 (Ubuntu).
Тестирование нескольких условий с помощью when
Вы также можете проверить несколько условий одновременно, используя логические операторы. Например, следующая перезагрузка-centos8.yml Playbook использует логический и оператора для перезагрузки серверов, работающих под управлением CentOS версии 8:
[destroyer@andreyex]$ cat reboot-centos8.yml --- - name: Reboot Servers hosts: all tasks: - name: Reboot CentOS 8 servers reboot: msg: "Сервер перезагружается..." when: ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "8"
Вы также можете использовать логический оператор или для запуска задачи, если выполняется какое-либо из условий. Например, следующая задача будет перезагружать серверы , на которых работает CentOS или RedHat :
tasks: - name: Reboot CentOS and RedHat Servers reboot: msg: "Сервер перезагружается..." when: ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "RedHat"
Использование when с петлями
Если вы объедините условный оператор when с циклом, Ansible проверит условие для каждого элемента в цикле отдельно.
Например, следующий сценарий print-even.yml напечатает все четные числа в диапазоне (1,11):
[destroyer@andreyex]$ cat print-even.yml --- - name: Print Some Numbers hosts: node1 tasks: - name: Print Even Numbers debug: msg: Number {{ item }} is Even. loop: "{{ range(1,11) | list }}" when: item % 2 == 0
Продолжайте и запустите playbook, чтобы увидеть список всех четных чисел в диапазоне (1,11):
[destroyer@andreyex]$ ansible-playbook print-even.yml PLAY [Print Some Numbers] ********************************** TASK [Gathering Facts] **************************** ok: [node1] TASK [Print Even Numbers] ****************************** skipping: [node1] => (item=1) ok: [node1] => (item=2) => { "msg": "Число 2 четное." } skipping: [node1] => (item=3) ok: [node1] => (item=4) => { "msg": "Число 4 четное." } skipping: [node1] => (item=5) ok: [node1] => (item=6) => { "msg": "Число 6 четное." } skipping: [node1] => (item=7) ok: [node1] => (item=8) => { "msg": "Число 8 четное." } skipping: [node1] => (item=9) ok: [node1] => (item=10) => { "msg": "Число 10 четное." } PLAY RECAP *********************************** node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
Использование when с переменными
Вы также можете использовать условные операторы when с вашими собственными определенными переменными. Помните, что условные выражения требуют логических входных данных; то есть, для срабатывания условия тест должен быть истинным, поэтому вам нужно использовать фильтр bool с небулевыми переменными.
Чтобы продемонстрировать это, взгляните на следующий сборник сценариев isfree.yml :
[destroyer@andreyex]$ cat isfree.yml --- - name: hosts: node1 vars: weekend: true on_call: "no" tasks: - name: Run if "weekend" is true and "on_call" is false debug: msg: "Вы свободны!" when: weekend and not on_call | bool
Обратите внимание, что здесь я использовал фильтр bool для преобразования значения on_call в его логический эквивалент (no -> false).
Кроме того, вы должны знать, что not false – это истина, и поэтому в этом случае все условие будет оцениваться как true; ты свободен!
Вы также можете проверить, установлена ли переменная или нет; например, следующая задача будет выполняться, только если определена переменная car :
tasks: - name: Run only if you got a car debug: msg: "Давай отправимся в путешествие..." when: car is defined
Следующая задача использует сбой модуля потерпеть неудачу , если ключи переменная не определена:
tasks: - name: Fail if you got no keys fail: msg: "Эта игра требует некоторых ключей" when: keys is undefined
Обработка исключений с помощью блоков
Поговорим об обработке исключений.
Группировка задач с блоками
Вы можете использовать блоки для группировки связанных задач. Для демонстрации взгляните на следующую книгу install-apache.yml :
[destroyer@andreyex]$ cat install-apache.yml --- - name: Install and start Apache Play hosts: webservers tasks: - name: Install and start Apache block: - name: Install httpd yum: name: httpd state: latest - name: Start and enable httpd service: name: httpd state: started enabled: yes - name: This task is outside the block debug: msg: "Сейчас я нахожусь за пределами блока..."
Playbook запускается на хостах группы веб-серверов и имеет один блок с именем Установить и запустить Apache, который включает в себя две задачи:
- Установить httpd
- Запустить и включить httpd
Первая задача Install httpd использует модуль yum для установки пакета httpd apache. Вторая задача использует служебный модуль для запуска и разрешает запуск httpd при загрузке.
Обратите внимание, что в playbook есть третья задача, которая не принадлежит блоку Install and start Apache.
Теперь запустите playbook, чтобы установить и запустить httpd на узлах веб-серверов:
[destroyer@andreyex]$ ansible-playbook install-apache.yml PLAY [Install and start Apache Play] ******************************************* TASK [Gathering Facts] ********************************************************* ok: [node3] ok: [node2] TASK [Install httpd] *********************************************************** changed: [node2] changed: [node3] TASK [Start and enable httpd] ************************************************** changed: [node3] changed: [node2] TASK [This task is outside the block] ****************************************** ok: [node2] => { "msg": "Сейчас я нахожусь за пределами квартала..." } ok: [node3] => { "msg": "Сейчас я нахожусь за пределами квартала..." } PLAY RECAP ********************************************************************* node2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 node3 : ok=4 changed=2 unreachable=0 failed=0 skipped=0
Вы также можете выполнить специальную команду, чтобы убедиться, что httpd действительно запущен и работает:
[destroyer@andreyex]$ ansible webservers -m command -a "systemctl status httpd" node3 | CHANGED | rc=0 >> ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled) Active: active (running) since Tue 2020-11-03 19:35:13 UTC; 1min 37s ago Docs: man:httpd.service(8) Main PID: 47122 (httpd) Status: "Running, listening on: port 80" Tasks: 213 (limit: 11935) Memory: 25.1M CGroup: /system.slice/httpd.service ├─47122 /usr/sbin/httpd -DFOREGROUND ├─47123 /usr/sbin/httpd -DFOREGROUND ├─47124 /usr/sbin/httpd -DFOREGROUND ├─47125 /usr/sbin/httpd -DFOREGROUND └─47126 /usr/sbin/httpd -DFOREGROUND Nov 03 19:35:13 node3 systemd[1]: Starting The Apache HTTP Server... Nov 03 19:35:13 node3 systemd[1]: Started The Apache HTTP Server. Nov 03 19:35:13 node3 httpd[47122]: Server configured, listening on: port 80 node2 | CHANGED | rc=0 >> ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled) Active: active (running) since Tue 2020-11-03 19:35:13 UTC; 1min 37s ago Docs: man:httpd.service(8) Main PID: 43695 (httpd) Status: "Running, listening on: port 80" Tasks: 213 (limit: 11935) Memory: 25.1M CGroup: /system.slice/httpd.service ├─43695 /usr/sbin/httpd -DFOREGROUND ├─43696 /usr/sbin/httpd -DFOREGROUND ├─43697 /usr/sbin/httpd -DFOREGROUND ├─43698 /usr/sbin/httpd -DFOREGROUND └─43699 /usr/sbin/httpd -DFOREGROUND Nov 03 19:35:13 node2 systemd[1]: Starting The Apache HTTP Server... Nov 03 19:35:13 node2 systemd[1]: Started The Apache HTTP Server. Nov 03 19:35:13 node2 httpd[43695]: Server configured, listening on: port 80
Обработка сбоев с помощью блоков
Вы также можете использовать блоки для обработки ошибок задач, используя разделы rescue и always. Это очень похоже на обработку исключений в языках программирования, таких как try-catch в Java или try-except в Python.
Вы можете использовать раздел восстановления, чтобы включить все задачи, которые вы хотите запустить в случае сбоя одной или нескольких задач в блоке.
Для демонстрации давайте взглянем на следующий пример:
tasks: - name: Handling error example block: - name: run a command command: uptime - name: run a bad command command: blabla - name: This task will not run debug: msg: "Я не запускаю потому что вышеприведенная задача провалилась." rescue: - name: Runs when the block failed debug: msg: "Блок не удался; давайте попробуем исправить это здесь..."
Обратите внимание, как вторая задача в блоке run a bad command генерирует ошибку, и, в свою очередь, третья задача в блоке никогда не получает возможности запустить. Задачи внутри спасательной секции будут работать , потому что вторая задача в блоке не удалась.
Вы также можете использовать ignore_errors: yes, чтобы гарантировать, что Ansible продолжит выполнение задач в playbook, даже если задача не удалась:
tasks: - name: Handling error example block: - name: run a command command: uptime - name: run a bad command command: blabla ignore_errors: yes - name: This task will run debug: msg: "Запуск потому что вышеуказанные ошибки задачи были проигнорированы." rescue: - name: This will not run debug: msg: "Ошибки были проигнорированы! ... не собирается запускаться."
Обратите внимание, что в этом примере вы проигнорировали ошибки во второй задаче в блоке run a bad command, и поэтому третья задача была запущена. Кроме того, спасательный раздел не будет работать , как вы игнорировали ошибку во второй задаче в блоке.
Вы также можете добавить в блок раздел всегда. Задачи в разделе всегда будут выполняться всегда, независимо от того, произошел сбой блока или нет.
Чтобы продемонстрировать это, взгляните на следующую книгу воспроизведения handle-errors.yml, в которой есть все три раздела блока (block, rescue, always):
[destroyer@andreyex]$ cat handle-errors.yml --- - name: Handling Errors with Blocks hosts: node1 tasks: - name: Handling Errors Example block: - name: run a command command: uptime - name: run a bad command command: blabla - name: This task will not run debug: msg: "Я не запускаюсь потому что вышеприведенная задача провалилась!" rescue: - name: Runs when the block fails debug: msg: "Блок провалился! давайте попробуем исправить это здесь..." always: - name: This will always run debug: msg: "Независимо от того, провалился блок или нет ... Я всегда буду запускаться.!"
Идите вперед и запустите playbook:
[destroyer@andreyex]$ ansible-playbook handle-errors.yml PLAY [Handling Errors with Blocks] ********************************************* TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [run a command] *********************************************************** changed: [node1] TASK [run a bad command] ******************************************************* fatal: [node1]: FAILED! => {"changed": false, "cmd": "blabla", "msg": "[Errno 2] No such file or directory: b'blabla': b'blabla'", "rc": 2} TASK [Runs when the block fails] *********************************************** ok: [node1] => { "msg": "Block failed! let's try to fix it here ..." } TASK [This will always run] **************************************************** ok: [node1] => { "msg": "Whether the block has failed or not ... I will always run!" } PLAY RECAP ********************************************************************* node1 : ok=4 changed=1 unreachable=0 failed=0 skipped=0
Как вы видете; спасательный раздел ли работать как 2 – й задача в блоке произошел сбой, и вы не игнорировать ошибки. Кроме того, всегда выполнялся (и будет всегда) раздел всегда.
Запуск задач при изменении с помощью обработчиков
Посмотрим, как менять обработчики и запускать задачи.
Запуск вашего первого обработчика
Вы можете использовать обработчики для запуска задач при изменении на ваших управляемых узлах. Чтобы продемонстрировать это, взгляните на следующий сборник handler-example.yml:
[destroyer@andreyex]$ cat handler-example.yml --- - name: Simple Handler Example hosts: node1 tasks: - name: Create engineers group group: name: engineers notify: add destroyer - name: Another task in the play debug: msg: "I am just another task." handlers: - name: add destroyer user: name: destroyer groups: engineers append: yes
Первая задача Create engineers groupсоздает группу инженеров, а также уведомляет обработчика add destroyer.
Давайте запустим сценарий, чтобы увидеть, что произойдет:
[destroyer@andreyex]$ ansible-playbook handler-example.yml PLAY [Simple Handler Example] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Create engineers group] ************************************************** changed: [node1] TASK [Another task in the play] ************************************************ ok: [node1] => { "msg": "I am just another task." } RUNNING HANDLER [add destroyer] *************************************************** changed: [node1] PLAY RECAP ********************************************************************* node1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0
Обратите внимание, что создание инженеров вызвало изменение на node1 и в результате запустило add destroyerобработчик.
Вы также можете запустить быструю специальную команду, чтобы убедиться, что пользователь destroyer действительно является членом группы инженеров:
[destroyer@andreyex]$ ansible node1 -m command -a "id destroyer" node1 | CHANGED | rc=0 >> uid=1000(destroyer) gid=1000(destroyer) groups=1000(destroyer),4(adm),190(systemd-journal),1004(engineers)
Плейбуки и модули Ansible являются идемпотентными, что означает, что если на управляемых узлах произошло изменение конфигурации; он больше не повторится!
Чтобы полностью понять концепцию идемпотентности Ansible; запустите playbook handler-example.yml еще раз:
[destroyer@andreyex]$ ansible-playbook handler-example.yml PLAY [Simple Handler Example] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Create engineers group] ************************************************** ok: [node1] TASK [Another task in the play] ************************************************ ok: [node1] => { "msg": "I am just another task." } PLAY RECAP ********************************************************************* node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0
Как вы видете; на этот раз Create engineers group задача не вызвала и не сообщила об изменении, потому что группа инженеров уже существует на node1 и, как результат; обработчик add destroyer не запустился.
Контроль, когда сообщать об изменении
Вы можете использовать ключевое слово changed_when, чтобы указать, когда задача должна сообщать об изменении. Чтобы продемонстрировать это, взгляните на следующий сценарий control-change.yml:
[destroyer@andreyex]$ cat control-change.yml --- - name: Control Change hosts: node1 tasks: - name: Run the date command command: date notify: handler1 - name: Run the uptime command command: uptime handlers: - name: handler1 debug: msg: "I can handle dates"
Обратите внимание, как первая задача Run the date commandзапускает handler1. А теперь запустите playbook:
[destroyer@andreyex]$ ansible-playbook control-change.yml PLAY [Control Change] ********************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Run the date command] **************************************************** changed: [node1] TASK [Run the uptime command] ************************************************** changed: [node1] RUNNING HANDLER [handler1] ***************************************************** ok: [node1] => { "msg": "I can handle dates" } PLAY RECAP ********************************************************************* node1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0
Обе задачи Run the date command и Run the uptime command зарегистрированные изменения, и обработчик handler1 был запущен. Вы можете утверждать, что выполнение команд date и uptime на самом деле ничего не меняет на управляемом узле, и вы совершенно правы!
Теперь давайте отредактируем playbook, чтобы задача не сообщала об изменениях:
[destroyer@andreyex]$ cat control-change.yml --- - name: Control Change hosts: node1 tasks: - name: Run the date command command: date notify: handler1 changed_when: false - name: Run the uptime command command: uptime handlers: - name: handler1 debug: msg: "I can handle dates"
Теперь снова запустите playbook:
[destroyer@andreyex]$ ansible-playbook control-change.yml PLAY [Control Change] ********************************************************** TASK [Gathering Facts] ********************************************************* ok: [node1] TASK [Run the date command] **************************************************** ok: [node1] TASK [Run the uptime command] ************************************************** changed: [node1] PLAY RECAP ********************************************************************* node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
Как видите, на этот раз задача не сообщила об изменении, и в результате handler1 не был запущен.
Настройка сервисов с обработчиками
Обработчики особенно полезны, когда вы редактируете конфигурации служб с помощью Ansible. Это потому, что вы хотите перезапустить службу только при изменении ее конфигурации службы.
Чтобы продемонстрировать это, взгляните на следующую инструкцию configure-ssh.yml :
[destroyer@andreyex]$ cat configure-ssh.yml --- - name: Configure SSH hosts: all tasks: - name: Edit SSH Configuration blockinfile: path: /etc/ssh/sshd_config block: | MaxAuthTries 4 Banner /etc/motd X11Forwarding no notify: restart ssh handlers: - name: restart ssh service: name: sshd state: restarted
Обратите внимание, что мы использовали модуль blockinfile для вставки нескольких строк текста в файл конфигурации /etc/ssh/sshd_config. Задача также вызывает обработчик restart ssh по изменению.
Идите вперед и запустите playbook:
[destroyer@andreyex]$ ansible-playbook configure-ssh.yml PLAY [Configure SSH] *********************************************************** TASK [Gathering Facts] ********************************************************* ok: [node4] ok: [node3] ok: [node1] ok: [node2] TASK [Edit SSH Configuration] ************************************************** changed: [node4] changed: [node2] changed: [node3] changed: [node1] RUNNING HANDLER [restart ssh] ************************************************** changed: [node4] changed: [node3] changed: [node2] changed: [node1] PLAY RECAP ********************************************************************* node1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 node2 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 node3 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 node4 : ok=3 changed=2 unreachable=0 failed=0 skipped=0
Все хорошо! Теперь давайте быстро взглянем на последние несколько строк в файле/etc/ssh/sshd_config:
[destroyer@andreyex]$ ansible node1 -m command -a "tail -5 /etc/ssh/sshd_config" node1 | CHANGED | rc=0 >> # BEGIN ANSIBLE MANAGED BLOCK MaxAuthTries 4 Banner /etc/motd X11Forwarding no # END ANSIBLE MANAGED BLOCK
Удивительно! Точно так, как вы ожидали. Имейте в виду, что если вы повторно запустите playbook configure-ssh.yml, Ansible не будет редактировать (или добавлять) файл /etc/ssh/sshd_config. Вы можете попробовать это сами.
Мы также рекомендуем вам взглянуть на страницы документации blockinfile и lineinfile, чтобы понять различия и использование каждого модуля:
[destroyer@andreyex]$ ansible-doc blockinfile [destroyer@andreyex]$ ansible-doc lineinfile
Хорошо! На этом мы подошли к концу нашей статьи по принятию решений в Ansible.