Pull to refresh

Как настроить автоматический откат в Ansible

Level of difficultyMedium
Reading time3 min
Views6.1K

Петя запускает плейбук, чтобы обновить конфигурацию Nginx — но сервис не стартует. В конфиге ошибка.

Он ищет причину, правит файл, запускает плейбук заново — потому что Ansible сам по себе откатывать изменения не умеет. Сайт заработал, но Петя потратил время, а пользователи видели простой.

В Ansible для этого есть один приём. Блоки задач: blockrescue и always. Это как try-catch-finally в программировании: сначала block, при ошибке — rescue, затем — always, независимо от результата.

Вот как выглядит плейбук до улучшений:

- name: Update nginx config
  hosts: web
  tasks:
    - name: Copy config
      ansible.builtin.copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
      notify:
        - Restart nginx

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Первое, что можно сделать — добавить backup: true, чтобы Ansible сохранял старую конфигурацию рядом. Бэкап создаётся только если файл изменился:

- name: Update nginx config
  hosts: web
  tasks:
    - name: Copy config
      ansible.builtin.copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        backup: true
      notify:
        - Restart nginx

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Теперь есть резервная копия, но восстанавливать её всё ещё придётся вручную.

Настраиваем автоматический откат

Чтобы Ansible сам справлялся с ошибками, задачу нужно обернуть в block, а в rescue — прописать действия на случай ошибки.

По умолчанию Ansible запускает хендлеры в конце плейбука. Чтобы перезапустить сервис сразу после изменения, нужно добавить meta: flush_handlers.

Если Nginx не запустится, Ansible восстановит старый конфиг и перезапустит сервис:

- name: Update nginx config with rollback
  hosts: web
  tasks:
    - block:
        - name: Copy config
          ansible.builtin.copy:
            src: nginx.conf
            dest: /etc/nginx/nginx.conf
            backup: true
          register: copy_result
          notify:
            - Restart nginx

        - name: Flush handlers
          meta: flush_handlers

      rescue:
        - name: Restore backup
          ansible.builtin.copy:
            src: "{{ copy_result.backup_file }}"
            dest: /etc/nginx/nginx.conf
          when: copy_result.backup_file is defined
          notify:
            - Restart nginx

        - name: Flush handlers after restore
          meta: flush_handlers

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Теперь, если Nginx не запустится, Ansible сам вернёт старую конфигурацию и перезапустит сервис.

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

Чистим бэкапы

После каждого запуска плейбука остаётся файл бэкапа. Если их не удалять, со временем они будут копиться на сервере. Чтобы этого избежать, лучше сразу очищать их в секции always:

- name: Update nginx config with rollback and cleanup
  hosts: web
  tasks:
    - block:
        - name: Copy config
          ansible.builtin.copy:
            src: nginx.conf
            dest: /etc/nginx/nginx.conf
            backup: true
          register: copy_result
          notify:
            - Restart nginx

        - name: Flush handlers
          meta: flush_handlers

      rescue:
        - name: Restore backup
          ansible.builtin.copy:
            src: "{{ copy_result.backup_file }}"
            dest: /etc/nginx/nginx.conf
          when: copy_result.backup_file is defined
          notify:
            - Restart nginx

        - name: Flush handlers after restore
          meta: flush_handlers

      always:
        - name: Remove backup
          ansible.builtin.file:
            path: "{{ copy_result.backup_file }}"
            state: absent
          when: copy_result.backup_file is defined

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Есть важный нюанс: задачи внутри always выполняются всегда — даже если раньше произошла ошибка. Но если сама задача внутри always завершится с ошибкой, Ansible сразу остановит плейбук. Поэтому в always лучше использовать только простые и надёжные операции.

Когда откат не спасёт

Например, Nginx не запускается, потому что закончилась память, умер процесс или сломалась файловая система. Ansible вернёт старый конфиг, перезапустит сервис — но ошибка останется. Потому что дело было не в конфигурации. Такое бывает — и об этом важно помнить.

Пример с Петей и Nginx — просто иллюстрация. Вместо него мог быть любой другой сервис — ситуации бывают разные.

Главное — понимать, как работает blockrescue и always, и использовать их по делу.

Запомнить

  1. Ansible не умеет откатывать изменения сам.

  2. rescue ловит только ошибки задач.

  3. always выполняется всегда, но если задача в нём упадёт — плейбук остановится.

  4. Откат может не помочь — если причина в чем-то другом.

  5. Блоки полезны не только для отката.

Tags:
Hubs:
Total votes 14: ↑12 and ↓2+10
Comments14

Articles