#ansible
#ansible
Вопрос:
Использование Ansible версии v2.9.12
Вопрос: Я бы хотел, чтобы Ansible завершал работу / останавливал воспроизведение при сбое задачи, когда несколько хостов выполняют задачу. В этом смысле Ansible должен прервать дальнейшее выполнение задачи. Конфигурация должна работать в роли, поэтому использование serial
или использование разных игр невозможно.
Пример ;
- hosts:
- host1
- host2
- host3
any_errors_fatal: true
tasks:
- name: always fail
shell: /bin/false
throttle: 1
Обеспечивает ;
===== task | always fail =======
host1: fail
host2: fail
host3: fail
Это означает, что задача все еще выполняется на втором хосте и третьем хосте. Я бы хотел, чтобы вся игра завершилась неудачно / остановилась, как только задача завершится неудачно на хосте. При сбое задачи на последнем хосте Ansible также должен прерваться.
Желаемый результат ;
===== task | always fail =======
host1: fail
host2: not executed/skipped, cause host1 failed
host3: not executed/skipped, cause host1 failed
Как вы можете видеть, я повозился с обработкой ошибок, но без превалирования.
Справочная информация: я разрабатывал идемпотентную роль Ansible для mysql. Можно настроить кластер с несколькими хостами. Роль также поддерживает добавление арбитра.
У арбитра не установлено приложение mysql, но хост по-прежнему требуется для воспроизведения.
Теперь представьте три хоста. Host1 является арбитром, у host2 и host3 установлен mysql, настроенный в кластере. Приложения настраиваются ролью Ansible. Теперь Ansible выполняет роль во второй / третий / четвертый / любой другой раз и изменяет настройку конфигурации mysql. Mysql требуется непрерывный перезапуск. Обычно кто-то пишет что-то вроде:
- template:
src: mysql.j2
dest: /etc/mysql
register: mysql_config
when: mysql.role != 'arbiter'
- service:
name: mysql
state: restarted
throttle: 1
when:
- mysql_config.changed
- mysql.role != 'arbiter'
Недостатком этой конфигурации Ansible является то, что если mysql не запускается на host2 по какой-либо причине, Ansible также перезапустит mysql на host3. И это нежелательно, потому что если mysql также выходит из строя на host3, то кластер теряется. Итак, для этой конкретной задачи я бы хотел, чтобы Ansible остановил / прервал / пропустил другие задачи, если mysql не удалось запустить на одном хосте в игре.
Ответ №1:
Хорошо, это работает:
# note that test-multi-01 set host_which_is_skipped: true
---
- hosts:
- test-multi-01
- test-multi-02
- test-multi-03
tasks:
- set_fact:
host_which_is_skipped: "{{ inventory_hostname }}"
when: host_which_is_skipped
- shell: /bin/false
run_once: yes
delegate_to: "{{ item }}"
loop: "{{ ansible_play_hosts }}"
when:
- item != host_which_is_skipped
- result is undefined or result is not failed
register: result
- meta: end_play
when: result is failed
- debug:
msg: Will not happen
Когда для команды оболочки установлено значение /bin/true
, команда выполняется на хостах 2 и host3.
Ответ №2:
Одним из способов решить эту проблему было бы запустить playbook с serial: 1
. Таким образом, задачи выполняются последовательно на хостах, и как только одна задача завершается неудачей, playbook завершается:
- name: My playbook
hosts: all
serial: 1
any_errors_fatal: true
tasks:
- name: Always fail
shell: /bin/false
В этом случае это приводит к тому, что задача выполняется только на первом хосте. Обратите внимание, что существует также order
предложение, с помощью которого вы также можете управлять порядком запуска хостов: https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#hosts-and-users
Комментарии:
1. Для меня это невозможно. Конфигурация должна быть учтена в роли. И опять же, использование serial довольно схематично. И заказ воспроизведения бесполезен…
Ответ №3:
Отказ от ответственности: большая часть полностью рабочей части этого ответа относится к ответу @ Tomasz Klosinski на ошибку сервера.
Вот частично работающая идея, которая не дотягивает только до одного хоста.
Для демонстрации я намеренно увеличил количество своих хостов до 5 хостов.
Идея основана на специальных переменных ansible_play_batch
и ansible_play_hosts_all
, которые описаны на вышеупомянутой странице документа как:
-
ansible_play_hosts_all
Список всех хостов, на которые была нацелена игра -
ansible_play_batch Список активных хостов в текущем прогоне воспроизведения ограничен серийным номером, он же ‘пакет’. Сбойные / недоступные хосты не считаются ‘активными’.
Идея в сочетании с вашей пробной версией использования throttle: 1
должна сработать, но потерпит неудачу на одном хосте, выполняясь на host2
, когда он должен ее пропустить.
Учитывая сценарий:
- hosts: all
gather_facts: no
tasks:
- shell: /bin/false
when: "ansible_play_batch | length == ansible_play_hosts_all | length"
throttle: 1
Это приводит к повторению:
PLAY [all] ***********************************************************************************************************
TASK [shell] *********************************************************************************************************
fatal: [host1]: FAILED! => {"changed": true, "cmd": "/bin/false", "delta": "0:00:00.003915", "end": "2020-09-06 22:09:16.550406", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:09:16.546491", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
fatal: [host2]: FAILED! => {"changed": true, "cmd": "/bin/false", "delta": "0:00:00.004736", "end": "2020-09-06 22:09:16.844296", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:09:16.839560", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
skipping: [host3]
skipping: [host4]
skipping: [host5]
PLAY RECAP ***********************************************************************************************************
host1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
host2 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
host3 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
host4 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
host5 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Рассматривая это далее, я остановился на этом ответе на ошибку сервера, и это, похоже, правильная идея для разработки вашего решения.
Вместо того, чтобы идти обычным путем, идея состоит в том, чтобы делегировать все с первого хоста с циклом на всех целевых хостах воспроизведения, потому что в цикле вы тогда сможете легко получить доступ к зарегистрированному факту предыдущего хоста, пока ваш register
это.
Итак, вот план действий:
- hosts: all
gather_facts: no
tasks:
- shell: /bin/false
loop: "{{ ansible_play_hosts }}"
register: failing_task
when: "failing_task | default({}) is not failed"
delegate_to: "{{ item }}"
run_once: true
Это привело бы к повторению:
PLAY [all] ***********************************************************************************************************
TASK [shell] *********************************************************************************************************
failed: [host1 -> host1] (item=host1) => {"ansible_loop_var": "item", "changed": true, "cmd": "/bin/false", "delta": "0:00:00.003706", "end": "2020-09-06 22:18:23.822608", "item": "host1", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:18:23.818902", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
skipping: [host1] => (item=host2)
skipping: [host1] => (item=host3)
skipping: [host1] => (item=host4)
skipping: [host1] => (item=host5)
NO MORE HOSTS LEFT ***************************************************************************************************
PLAY RECAP ***********************************************************************************************************
host1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
И просто ради доказательства того, что это работает по назначению, изменив его, чтобы сделать host2
сбой конкретно, с помощью failed_when
:
- hosts: all
gather_facts: no
tasks:
- shell: /bin/false
loop: "{{ ansible_play_hosts }}"
register: failing_task
when: "failing_task | default({}) is not failed"
delegate_to: "{{ item }}"
run_once: true
failed_when: "item == 'host2'"
Выдает резюме:
PLAY [all] ***********************************************************************************************************
TASK [shell] *********************************************************************************************************
changed: [host1 -> host1] => (item=host1)
failed: [host1 -> host2] (item=host2) => {"ansible_loop_var": "item", "changed": true, "cmd": "/bin/false", "delta": "0:00:00.004226", "end": "2020-09-06 22:20:38.038546", "failed_when_result": true, "item": "host2", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:20:38.034320", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
skipping: [host1] => (item=host3)
skipping: [host1] => (item=host4)
skipping: [host1] => (item=host5)
NO MORE HOSTS LEFT ***************************************************************************************************
PLAY RECAP ***********************************************************************************************************
host1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Комментарии:
1. При использовании есть большой недостаток
run_once
. Если для этой задачи помещено другое предложение when, и первый хост, выполняющий эту задачу, должен пропустить эту задачу, сама задача пропускается. Пример: pastebin.com/knAknpU6 . Не могли бы вы придумать что-нибудь, что заставило бы это работать?2. Mhm здесь это связано не с
run_once
, а с тем фактом, что вы используетеwhen: ... and ...
, который не ограничен конкретным хостом. Возможно, предоставление большего контекста, чем теоретическийshell: /bin/false
, приведет к тому, что кто-то даст вам ответ, которого вы, возможно, не ожидаете, но который будет работать в вашем случае использования.3. Хорошо, я обновил свой вопрос некоторой справочной информацией. Я надеюсь, что это прояснит ситуацию.