«Ansible playbook не выполняется и вместо этого запускает блок rescue»

Вопрос или проблема

У меня есть следующий Ansible playbook:

- hosts: <host>
  become: true
  vars:
    dest_dir: <dir>
  roles:
    - {role: '<role>', tags: '<tag>'}

Вызываемая <role> состоит из одного файла задачи:

- name: Копировать и проверить файл.
  block:
    - name: Копировать
      ansible.builtin.copy:
        src: <source file>
        dest: "{{dest_dir}}"
        mode: 0666
        owner: root
        group: root
        backup: true
      register: copy_result
    - name: Печать результата
      ansible.builtin.debug:
        var: copy_result
    - name: Валидация
      ansible.builtin.shell: <script> 1
      when: copy_result is changed
  rescue:
    - name: Восстановить
      ansible.builtin.copy:
        remote_src: true
        src: "{{copy_result.backup_file}}"
        dest: "{{dest_dir}}"
      when: copy_result.backup_file is defined

Вызываемый <script> является заглушкой, просто для выхода с кодом ошибки, соответствующим переданному аргументу:

exit $1

(Этот код основан на ответе в FAQ Ansible на https://docs.ansible.com/ansible/devel/reference_appendices/faq.html#the-validate-option-is-not-enough-for-my-needs-what-do-i-do.)

Если я запущу это, когда целевой файл уже существует, но его содержимое отличается от содержимого исходного файла, Ansible правильно скопирует исходный файл в целевой, и я ожидал, что блок ‘Валидация’ приведет к вызову блока ‘rescue’, который должен “откатить” копирование файла. Однако блок ‘rescue’ не вызывается; вместо этого playbook прекращает выполнение в блоке ‘Валидация’ с сообщением:

TASK [<task> : Валидация] ******************************************************
fatal: [<host>]: FAILED! => {"changed": true, "cmd": "<script> 1", "delta": "0:00:00.010525", "end": "2024-09-17 10:49:23.559180", "msg": "не нулевой код возврата", "rc": 1, "start": "2024-09-17 10:49:23.548655", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}

Затем он входит в отладчик.

Я задумывался, не в этом ли параметре ‘when’ блока ‘rescue’ заключается проблема, но комментирование его привело к такому же поведению.

Может кто-то выявить, что я делаю неправильно?

Во-первых, обратите внимание, что модуль ansible.builtin.copy имеет опцию validate. Используя ее, вы можете переписать свой playbook следующим образом:

- hosts: localhost
  gather_facts: false
  vars:
    dest_dir: /tmp
  tasks:
  - name: Копировать
    ansible.builtin.copy:
      src: testfile
      dest: "{{dest_dir}}/testfile"
      mode: 0666
      owner: root
      group: root
      backup: true
      validate: sh -c 'exit 1' %s

Вы получите фактически такое же поведение с значительно меньшим объемом кода.


Что касается вашего вопроса, я не могу воспроизвести ошибку, которую вы показали. Вы не предоставили ее в своем вопросе, поэтому я создал следующий минимальный, воспроизводимый пример:

- hosts: localhost
  gather_facts: false
  vars:
    dest_dir: /tmp
  tasks:
  - block:
      - name: Копировать
        ansible.builtin.copy:
          src: testfile
          dest: "{{dest_dir}}"
          mode: 0666
          owner: root
          group: root
          backup: true
        register: copy_result

      - name: Печать результата
        ansible.builtin.debug:
          var: copy_result

      - name: Валидация
        ansible.builtin.shell: "exit 1"
        when: copy_result is changed
    rescue:
      - name: Восстановить
        ansible.builtin.copy:
          remote_src: true
          src: "{{copy_result.backup_file}}"
          dest: "{{dest_dir}}"
        when: copy_result.backup_file is defined

Запуск этого кода производит (я использую unixy вывод для уменьшения объема):

- localhost on hosts: localhost -
Копировать...
  localhost done
Печать результата...
  localhost ok: {
    "changed": false,
    "copy_result": {
        "backup_file": "/tmp/testfile.893626.2024-09-17@07:11:55~",
        "changed": true,
        "checksum": "22596363b3de40b06f981fb85d82312e8c0ed511",
        "dest": "/tmp/testfile",
        "diff": [],
        "failed": false,
        "gid": 0,
        "group": "root",
        "md5sum": "6f5902ac237024bdd0c176cb93063dc4",
        "mode": "0666",
        "owner": "root",
        "secontext": "unconfined_u:object_r:user_tmp_t:s0",
        "size": 12,
        "src": "/home/lars/.ansible/tmp/ansible-tmp-1726571514.9080791-893587-119392786896945/.source",
        "state": "file",
        "uid": 0
    }
}
Валидация...
  localhost failed | msg: не нулевой код возврата
Восстановить...
  localhost ok

- Резюме выполнения -
  localhost                  : ok=3    changed=1    unreachable=0    failed=0    rescued=1    ignored=0

Мы можем четко видеть, что задача “Восстановить” выполняется. Однако обратите внимание, что задача “Восстановить” не делает того, что вам нужно. В приведенном выше примере нашей целевой файл является /tmp/testfile, что означает, что резервная копия файла будет называться, например, /tmp/testfile.893626.2024-09-17@07:11:55~. Это делает задачу “Восстановить” эквивалентной:

- name: Восстановить
  ansible.builtin.copy:
    remote_src: true
    src: "/tmp/testfile.893626.2024-09-17@07:11:55~"
    dest: "/tmp"
  when: copy_result.backup_file is defined

И это не операция; вы копируете резервный файл на сам себя. Если вы хотите заменить целевой файл, вам нужно будет указать явное имя файла в параметре dest вместо директории; это сделает playbook выглядеть следующим образом:

- hosts: localhost
  gather_facts: false
  vars:
    dest_dir: /tmp
  tasks:
  - block:
      - name: Копировать
        ansible.builtin.copy:
          src: testfile
          dest: "{{dest_dir}}/testfile"
          mode: 0666
          owner: root
          group: root
          backup: true
        register: copy_result

      - name: Печать результата
        ansible.builtin.debug:
          var: copy_result

      - name: Валидация
        ansible.builtin.shell: "exit 1"
        when: copy_result is changed
    rescue:
      - name: Восстановить
        ansible.builtin.copy:
          remote_src: true
          src: "{{copy_result.backup_file}}"
          dest: "{{dest_dir}}/testfile"
        when: copy_result.backup_file is defined

Запуск этого успешно откатит файл, когда задача “Валидация” завершится неудачно.

Ответ или решение

Ваша проблема связана с тем, как механизм блоков и блоков-rescue работает в Ansible, когда вы выполняете задачи, которые могут завершаться с ошибкой. Давайте разберем вашу ситуацию и предложим правильное решение.

Проблема:
В вашем плейбуке блок rescue не срабатывает, когда задача Validate завершает выполнение с ошибкой. Вместо этого выполнение плейбука прерывается, и вы видите сообщение о сбое с кодом возврата 1. Это может произойти из-за того, что задача Validate завершает выполнение не совсем так, как ожидается, и Ansible не понимает, что это ошибка, требующая обработки через rescue.

Решение:
Для того чтобы блок rescue сработал, нужно убедиться, что ошибка действительно обрабатывается. Один из подходов — создать задачу в блоке, которая вызывает проверку с помощью модуля ansible.builtin.shell, и обязательно указывать, что это может закончиться с ошибкой.

Вот как можно переписать ваш плейбук с учетом всех замечаний:

- hosts: <host>
  become: true
  vars:
    dest_dir: <dir>
  roles:
    - { role: '<role>', tags: '<tag>' }

  # В роли создаем задачу
  tasks:
    - block:
        - name: Копирование файла
          ansible.builtin.copy:
            src: <source file>
            dest: "{{ dest_dir }}"
            mode: '0666'
            owner: root
            group: root
            backup: true
          register: copy_result

        - name: Печать результата
          ansible.builtin.debug:
            var: copy_result

        - name: Валидация
          ansible.builtin.shell: <script> 1
          register: validation_result
          failed_when: validation_result.rc != 0  # Обрабатываем ошибки

    rescue:
      - name: Откат изменений
        ansible.builtin.copy:
          remote_src: true
          src: "{{ copy_result.backup_file }}"
          dest: "{{ dest_dir }}"
        when: copy_result.backup_file is defined

Объяснение изменений:

  1. failed_when: В задаче Validate я добавил параметр failed_when, который явным образом указывает, что задача считается неуспешной, когда код возврата (rc) отличается от 0. Это позволяет Ansible корректно обрабатывать ошибку и не прерывать выполнение плейбука.

  2. Названия задач: Я обновил названия задач на русском, чтобы они были более понятными и отражали суть выполнения.

  3. Логика отката: Теперь rescue блок будет корректно выполняться, если задача Validate завершилась с ошибкой, и откат произойдет, как ожидается.

Дополнительные рекомендации:

  • Убедитесь, что все пути к файлам и скриптам указаны правильно, чтобы избежать ошибок в процессе выполнения.
  • Если предусмотрена возможность, что задачи могут завершаться с ошибкой, рассмотрите возможность использования модуля ansible.builtin.command вместо shell для повышения безопасности, если нет необходимости в использовании оболочки.

С вышеуказанными исправлениями ваш плейбук должен корректно работать и выполнять задачу отката в случае, если валидация не проходит.

Оцените материал
Добавить комментарий

Капча загружается...