Ansible #9: Условия, циклы и обработка ошибок
Введение
До этого момента наши Playbook выполняли задачи последовательно: установить пакет, создать файл, запустить службу и так далее. На практике этого часто недостаточно. Например, Ansible может работать сразу с разными операционными системами, устанавливать несколько пакетов одной задачей, выполнять действие только при определённых условиях или продолжать выполнение даже после ожидаемой ошибки.
Для этого в Ansible используются условия, циклы и механизмы управления результатами выполнения задач.
В этой статье рассмотрим:
-
when- выполнение задач по условию; -
loop- повторение одной задачи для нескольких элементов; -
register- сохранение результата выполнения задачи; -
failed_when- определение условий, при которых задача считается ошибочной; -
changed_when- управление определением изменения; -
сочетание этих возможностей в одном Playbook.
Условия с when
Условие when позволяет выполнить задачу только тогда, когда заданное выражение возвращает true.
Например, посмотрим версию операционной системы:
- name: Установка Nginx на Debian
ansible.builtin.apt:
name: nginx
state: present
when: ansible_facts['distribution'] == "Debian"
В этом случае задача выполнится только на Debian.
На Ubuntu условие будет ложным, поэтому Ansible пропустит задачу.
В выводе появится:
skipping: [server1]
Несколько условий
Условия можно объединять. Например:
- name: Установка Nginx
ansible.builtin.apt:
name: nginx
state: present
when:
- ansible_facts['distribution'] in ["Debian", "Ubuntu"]
- ansible_facts['architecture'] == "x86_64"
Теперь задача выполнится только если:
-
операционная система - Debian или Ubuntu;
-
архитектура -
x86_64.
Также можно использовать логические операторы:
when: ansible_facts['distribution'] == "Debian" or ansible_facts['distribution'] == "Ubuntu"
Условие по переменной
when можно использовать не только с фактами Ansible, но и с обычными переменными.
Например:
---
- name: Настройка сервера
hosts: all
vars:
install_nginx: true
tasks:
- name: Установка Nginx
ansible.builtin.apt:
name: nginx
state: present
when: install_nginx
Если:
install_nginx: false
задача будет пропущена.
Циклы с loop
Циклы позволяют выполнять одну задачу несколько раз. Например, вместо нескольких одинаковых задач:
- name: Установка curl
ansible.builtin.apt:
name: curl
state: present
- name: Установка wget
ansible.builtin.apt:
name: wget
state: present
- name: Установка git
ansible.builtin.apt:
name: git
state: present
можно использовать loop:
- name: Установка необходимых пакетов
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- curl
- wget
- git
Ansible выполнит одну задачу для каждого элемента списка.
В loop текущий элемент доступен через переменную item.
Цикл с переменной
Список можно вынести в переменную:
vars:
packages:
- curl
- wget
- git
- htop
А затем:
- name: Установка пакетов
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: "{{ packages }}"
Такой вариант удобнее, если список пакетов должен изменяться в зависимости от конкретного сервера.
Циклы с файлами
loop можно использовать не только для пакетов.
Например, создадим несколько каталогов:
- name: Создание каталогов
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/app
- /opt/app/config
- /opt/app/data
- /opt/app/logs
Одна задача создаст сразу несколько каталогов.
Сохранение результата с помощью register
Иногда нужно не просто выполнить задачу, а использовать её результат в следующих задачах. Для этого используется register.
Например:
- name: Проверка версии Nginx
ansible.builtin.command:
cmd: nginx -v
register: nginx_version
changed_when: false
Результат выполнения будет сохранён в переменную:
nginx_version
В ней Ansible сохраняет информацию о выполнении команды: код возврата, stdout, stderr и другие данные.
Например, можно вывести результат:
- name: Вывод версии Nginx
ansible.builtin.debug:
var: nginx_version.stderr
У nginx -v версия обычно выводится в stderr, поэтому здесь используется именно nginx_version.stderr.
Использование register вместе с when
Главное преимущество register появляется, когда результат одной задачи влияет на следующую.
Например, проверим наличие файла:
- name: Проверка конфигурации приложения
ansible.builtin.stat:
path: /opt/myapp/config.yml
register: config_file
Теперь можно выполнить задачу только при наличии файла:
- name: Резервное копирование конфигурации
ansible.builtin.copy:
src: /opt/myapp/config.yml
dest: /opt/myapp/config.yml.backup
remote_src: true
when: config_file.stat.exists
Если файла нет, задача будет пропущена.
Обработка ошибок с failed_when
По умолчанию Ansible считает задачу неудачной, если модуль возвращает ошибку. Иногда стандартного поведения недостаточно. Например, команда может возвращать ненулевой код завершения, который в конкретном сценарии не означает настоящую ошибку. Для этого используется failed_when.
Рассмотрим проверку:
- name: Проверка доступности порта
ansible.builtin.command:
cmd: nc -z 127.0.0.1 80
register: port_check
failed_when: false
В этом примере задача никогда не будет считаться ошибочной, даже если команда завершится с ненулевым кодом.
Но обычно лучше использовать failed_when не для полного отключения ошибок, а для определения конкретного условия.
Например:
- name: Проверка результата
ansible.builtin.command:
cmd: /opt/myapp/check.sh
register: check_result
failed_when: "'CRITICAL' in check_result.stdout"
Теперь задача считается ошибочной только в том случае, если результат содержит CRITICAL.
changed_when
По умолчанию Ansible определяет, изменила ли задача состояние системы. Для команд это может быть неочевидно.
Например:
- name: Проверка версии Nginx
ansible.builtin.command:
cmd: nginx -v
Команда только получает информацию и ничего не изменяет. Но Ansible не всегда может определить это самостоятельно.
Поэтому можно явно указать:
- name: Проверка версии Nginx
ansible.builtin.command:
cmd: nginx -v
changed_when: false
Теперь Ansible будет считать задачу выполненной без изменений. Это особенно полезно для диагностических команд.
failed_when и changed_when вместе
Оба параметра можно использовать в одной задаче.
Например:
- name: Проверка приложения
ansible.builtin.command:
cmd: /opt/myapp/check.sh
register: check_result
changed_when: false
failed_when: "'CRITICAL' in check_result.stdout"
Здесь:
-
registerсохраняет результат; -
changed_when: falseговорит, что проверка ничего не изменяет; -
failed_whenопределяет, что считать ошибкой.
Практический пример
Теперь объединим несколько возможностей в одном Playbook.
Создадим:
nano nine.yml
Добавим:
---
- name: Проверка и настройка сервера
hosts: all
vars:
packages:
- curl
- wget
- htop
tasks:
- name: Установка необходимых пакетов
ansible.builtin.apt:
name: "{{ item }}"
state: present
update_cache: true
loop: "{{ packages }}"
- name: Проверка наличия Nginx
ansible.builtin.command:
cmd: nginx -v
register: nginx_version
changed_when: false
failed_when: false
- name: Вывод версии Nginx
ansible.builtin.debug:
var: nginx_version.stderr
when: nginx_version.rc == 0
- name: Установка Nginx, если он отсутствует
ansible.builtin.apt:
name: nginx
state: present
when: nginx_version.rc != 0
- name: Создание каталогов приложения
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/myapp
- /opt/myapp/config
- /opt/myapp/data
- /opt/myapp/logs
В этом примере используется сразу несколько механизмов.
loop устанавливает несколько пакетов:
loop: "{{ packages }}"
register сохраняет результат проверки Nginx:
register: nginx_version
changed_when говорит, что проверка версии не изменяет систему:
changed_when: false
failed_when отключает ошибку, если Nginx ещё не установлен:
failed_when: false
А when решает, нужно ли устанавливать Nginx:
when: nginx_version.rc != 0
Запуск Playbook
Проверим синтаксис:
ansible-playbook -i inventory nine.yml --syntax-check
Запустим:
ansible-playbook -i inventory nine.yml
При повторном запуске Ansible не будет повторно устанавливать уже установленные пакеты и создавать существующие каталоги.
Что важно запомнить
Основные инструменты из этой статьи решают разные задачи:
| Инструмент | Назначение |
|---|---|
when |
Выполнить задачу только при определённом условии |
loop |
Повторить задачу для нескольких элементов |
register |
Сохранить результат выполнения задачи |
failed_when |
Определить, когда задача считается ошибочной |
changed_when |
Определить, считается ли задача изменившей систему |
На практике они часто используются вместе. Например, register получает результат команды, when анализирует этот результат, а failed_when и changed_when позволяют настроить поведение самой задачи.
Заключение
Условия, циклы и управление результатами позволяют сделать Playbook значительно гибче. С помощью when Ansible может принимать решения в зависимости от операционной системы, переменных и результатов предыдущих задач. loop позволяет обрабатывать списки без дублирования одинаковых задач. register сохраняет результаты выполнения, которые затем можно использовать в других задачах. failed_when и changed_when дают дополнительный контроль над тем, как Ansible воспринимает результат выполнения команд и модулей.
Эти возможности особенно важны при создании сложных Playbook и ролей, где сценарий выполнения зависит от текущего состояния сервера.
В следующей статье можно перейти к защите чувствительных данных и разобрать Ansible Vault - хранение паролей, токенов и других секретов в зашифрованном виде.
Полезные ссылки
-
Ansible - Conditionals - условия и
when. -
Ansible - Loops - циклы и
loop. -
Ansible - Error handling - обработка ошибок.
-
Ansible - Registering variables - сохранение результатов выполнения.



