Ansible. Принятие решений в Ansible

В этой статье вы узнаете, как добавить навыки принятия решений в свои сценарии 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.
![]()
Редактор: AndreyEx