Безопасное управление конфигурациями в Ansible: Полное руководство по использованию rescue и always

Безопасное управление конфигурациями в Ansible: Полное руководство по использованию rescue и always
Photo by İsmail Enes Ayhan / Unsplash

Введение: Почему это важно

В мире DevOps и системного администрирования существует простое правило: всё ломается. Особенно в самый неподходящий момент. Когда вы изменяете конфигурацию критического сервиса (например, Nginx), цена ошибки может быть очень высока — от простого даунтайма до потери данных.

Ansible предлагает элегантное решение для безопасного внесения изменений через механизм блоков с обработкой ошибок. Понимание и правильное использование конструкций rescue и always — это навык, который отделяет начинающего администратора от профессионала.

Глубже в механизм обработки ошибок

Анатомия блока обработки

Базовый блок в Ansible состоит из трёх частей:

  1. Основной блок (block) — здесь выполняются основные задачи
  2. Блок восстановления (rescue) — выполняется только при ошибке в основном блоке
  3. Блок завершения (always) — выполняется в любом случае
- name: Пример структуры
  block:
    - name: Основная задача
      command: make_changes
  rescue:
    - name: Действия при ошибке
      command: rollback_changes
  always:
    - name: Финальные действия
      command: cleanup

Как это работает на практике

Представьте, что вы врач, выполняющий операцию:

  • Block — это сама операция
  • Rescue — действия, если что-то пошло не так
  • Always — наложение повязки и послеоперационный уход в любом случае

Реальный пример: безопасное обновление Nginx

Проблема

При обновлении конфигурации Nginx могут возникнуть:

  1. Синтаксические ошибки в новом конфиге
  2. Проблемы с правами доступа
  3. Конфликты конфигурации
  4. Ошибки при перезагрузке сервиса

Решение

Разберём полноценное решение с пояснениями каждого этапа:

- name: Безопасное обновление Nginx
  hosts: webservers
  become: yes
  vars:
    nginx_conf_path: "/etc/nginx/nginx.conf"
    nginx_conf_backup: "/etc/nginx/nginx.conf.bak"
  
  tasks:
    - name: Блок управления конфигурацией
      block:
        # 1. Сначала создаём бэкап текущей рабочей конфигурации
        - name: Создание бэкапа
          ansible.builtin.copy:
            src: "{{ nginx_conf_path }}"
            dest: "{{ nginx_conf_backup }}"
            remote_src: yes
          register: backup_result
          changed_when: false  # Помечаем как неизменяемую задачу
        
        # 2. Развёртываем новый конфигурационный файл
        - name: Развертывание нового конфига
          ansible.builtin.template:
            src: templates/nginx.conf.j2
            dest: "{{ nginx_conf_path }}"
            mode: '0644'
        
        # 3. Проверяем синтаксис перед применением
        - name: Проверка синтаксиса
          ansible.builtin.command: nginx -t
          register: nginx_test
          changed_when: false
        
        # 4. Если проверка прошла — применяем изменения
        - name: Применение изменений
          ansible.builtin.service:
            name: nginx
            state: reloaded
      
      rescue:
        # Этот блок выполняется ТОЛЬКО если что-то пошло не так в основном блоке
        
        # 1. Оповещаем администратора об ошибке
        - name: Оповещение об ошибке
          ansible.builtin.slack:
            token: "{{ slack_token }}"
            msg: "Ошибка в Nginx на {{ inventory_hostname }}: {{ nginx_test.stderr }}"
          when: nginx_test.failed
        
        # 2. Восстанавливаем рабочую конфигурацию из бэкапа
        - name: Восстановление бэкапа
          ansible.builtin.copy:
            src: "{{ nginx_conf_backup }}"
            dest: "{{ nginx_conf_path }}"
            remote_src: yes
          when: backup_result is defined and backup_result.changed == false
        
        # 3. Перезагружаем Nginx с рабочей конфигурацией
        - name: Восстановление сервиса
          ansible.builtin.service:
            name: nginx
            state: reloaded
          when: backup_result is defined and backup_result.changed == false
        
        # 4. Если бэкап не был создан — это критическая ошибка
        - name: Обработка неудачного бэкапа
          ansible.builtin.fail:
            msg: "Бэкап не создан, откат невозможен!"
          when: backup_result is undefined or backup_result.changed == true
      
      always:
        # Этот блок выполняется В ЛЮБОМ СЛУЧАЕ — была ошибка или нет
        
        # 1. Логируем результат операции
        - name: Логирование результата
          ansible.builtin.lineinfile:
            path: /var/log/nginx_updates.log
            line: "{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - {% if ansible_failed_result %}FAIL{% else %}OK{% endif %}"
            create: yes
        
        # 2. Отправляем отчёт по email
        - name: Отправка отчёта
          ansible.builtin.mail:
            host: smtp.example.com
            port: 25
            subject: "Обновление Nginx на {{ inventory_hostname }}"
            body: "Статус: {% if ansible_failed_result %}ОШИБКА{% else %}УСПЕХ{% endif %}"
            to: "[email protected]"

Почему это работает?

  1. Бэкап перед изменениями — гарантия, что мы сможем откатиться
  2. Проверка синтаксиса перед применением изменений
  3. Автоматический откат при любой ошибке
  4. Уведомления — вы сразу узнаете о проблеме
  5. Логирование — полная история изменений
  6. Гарантированная очистка — блок always выполнится в любом случае

Альтернативные подходы

Метод временного файла

В некоторых случаях лучше использовать временный файл для новой конфигурации:

- name: Обновление через временный файл
  block:
    # 1. Генерируем новый конфиг во временный файл
    - name: Генерация нового конфига
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: "{{ nginx_conf_path }}.new"
    
    # 2. Проверяем его валидность
    - name: Валидация конфига
      ansible.builtin.command: nginx -t -c "{{ nginx_conf_path }}.new"
      register: test_result
    
    # 3. Если проверка прошла — заменяем основной файл
    - name: Применение конфига
      ansible.builtin.command: mv "{{ nginx_conf_path }}.new" "{{ nginx_conf_path }}"
      notify: reload nginx
  
  rescue:
    # Если что-то пошло не так — удаляем временный файл
    - name: Очистка временного файла
      ansible.builtin.file:
        path: "{{ nginx_conf_path }}.new"
        state: absent
    
    # И сообщаем об ошибке
    - name: Логирование ошибки
      ansible.builtin.debug:
        msg: "Ошибка конфигурации: {{ test_result.stderr }}"
  
  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Преимущества метода:

  • Нет периода, когда основной конфиг повреждён
  • Более чистая реализация
  • Легче отслеживать состояние

Продвинутые техники

Вложенные блоки

Для сложных сценариев можно вкладывать блоки друг в друга:

- name: Вложенная структура
  block:
    - name: Внешний блок
      block:
        - name: Внутренняя операция
          command: /bin/fail_command
      rescue:
        - debug:
            msg: "Внутренний обработчик ошибки"
  
  rescue:
    - debug:
        msg: "Внешний обработчик ошибки"

Условное выполнение

Блоки rescue и always поддерживают условие when:

rescue:
  - name: Условное восстановление
    command: /bin/recovery
    when: recovery_needed | bool

Лучшие практики

  1. Всегда делайте бэкап перед изменением конфигурации
  2. Проверяйте синтаксис перед применением изменений
  3. Используйте уведомления — вы должны знать о проблемах
  4. Логируйте всё — это поможет при разборе инцидентов
  5. Тестируйте сценарии отката — они должны работать
  6. Документируйте свои playbook для коллег

Заключение

Использование rescue и always в Ansible — это не просто хороший тон, а необходимость при работе с production-окружением. Представленные подходы позволяют:

  • Минимизировать downtime при обновлениях
  • Обеспечить автоматический откат при ошибках
  • Сохранять полную историю изменений
  • Оперативно реагировать на проблемы

Помните: хороший системный администратор не тот, кто не делает ошибок, а тот, кто предусмотрел их последствия. Используйте механизмы обработки ошибок в Ansible, и ваша инфраструктура станет значительно более устойчивой и предсказуемой.

Read more

Использование ~/.ssh/authorized_keys для управления входящими SSH-соединениями

Использование ~/.ssh/authorized_keys для управления входящими SSH-соединениями

Файл ~/.ssh/authorized_keys позволяет настроить команды, которые будут выполняться при входящих SSH-соединениях. Это полезный инструмент для управления доступом и обеспечения безопасности, особенно при работе с резервным копированием данных. Настройка резервного копирования с использованием authorized_keys В данном примере рассматривается использование authorized_keys для настройки резервного копирования базы данных Bacula

Смазывать или не смазывать? Вот в чём вопрос (спойлер: смазывать)

Смазывать или не смазывать? Вот в чём вопрос (спойлер: смазывать)

Если вы когда-нибудь слушали, как ваш 3D-принтер поёт на Z-оси вместо того, чтобы плавно ехать, как по маслу (буквально), — скорее всего, вы уже задавались этим вопросом: чем смазывать кинематику и когда вообще это делать? Спокойно. Без фанатизма и бутылки литола. Сейчас разберёмся, что мажем, чем мажем и зачем. Для валов

Добавление SWAP в Ubuntu

Добавление SWAP в Ubuntu

Что такое Swap и зачем он нужен? Swap (подкачка) — это пространство на диске, которое используется, когда физической оперативной памяти (RAM) становится недостаточно. Когда система Linux исчерпывает доступную RAM, неактивные страницы памяти перемещаются в swap-пространство, освобождая место для активных процессов. Swap может быть организован двумя способами: * Swap-раздел — выделенный раздел на диске

Cold pull — как вытащить засор из сопла и не сломать мозг

Cold pull — как вытащить засор из сопла и не сломать мозг

Знаете, что общего у принтера Flashforge 5M Pro и кофемашины, которая внезапно перестала варить кофе? Правильно — если в узком месте что-то застряло, всё остальное тут же идёт по одному месту. В 3D-принтерах это место — сопло. И когда оно забивается, без паники. Есть старый добрый способ — cold pull. А теперь по-человечески: