Planetahost VPS
Перейти к основному контенту

Ansible #9: Условия, циклы и обработка ошибок

intro-Ansible-9-Conditionals-Loops-and-Error-Handling-planetahost.png

Введение

До этого момента наши 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"

Теперь задача выполнится только если:

  1. операционная система - Debian или Ubuntu;

  2. архитектура - 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 определяет, что считать ошибкой.

Нужен стенд для экспериментов с Ansible?
Проверяйте условия, циклы и обработку ошибок на отдельных Linux-серверах. Экспериментируйте с Playbook, тестируйте разные сценарии выполнения и отрабатывайте обработку сбоев без риска для рабочей инфраструктуры.
Арендовать VDS KVM

Практический пример

Теперь объединим несколько возможностей в одном 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

изображение.png

изображение.png

изображение.png

При повторном запуске 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 - хранение паролей, токенов и других секретов в зашифрованном виде.

Полезные ссылки