Управление выполнением плейбуков Ansible в RHEL

Red Hat Enterprise LinuxBeginner
Практиковаться сейчас

Введение

В этой лабораторной работе вы научитесь управлять потоком выполнения плейбуков Ansible в системе Red Hat Enterprise Linux (RHEL). Вы начнете с написания плейбука, который использует основные управляющие структуры, включая циклы для эффективного повторения задач и условные операторы для выполнения задач только при выполнении определенных критериев. Вы также реализуете обработчики (handlers) для запуска действий, таких как перезапуск служб, только при возникновении изменений, что сделает вашу автоматизацию более интеллектуальной и эффективной.

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

Написание плейбука с циклами и условными операторами

На этом шаге вы изучите две фундаментальные концепции Ansible для управления выполнением задач: циклы и условия. Циклы позволяют повторять задачу несколько раз с различными значениями, что очень эффективно для таких задач, как установка нескольких пакетов или создание нескольких пользователей. Условия, использующие ключевое слово when, позволяют выполнять задачу только при выполнении определенных критериев, например, если операционная система имеет определенную версию или если файл уже существует.

Сначала убедимся, что Ansible установлен на вашей виртуальной машине LabEx. Для этого мы будем использовать менеджер пакетов DNF.

sudo dnf install -y ansible-core

Вы должны увидеть вывод, указывающий на то, что ansible-core и его зависимости устанавливаются.

...
Installed:
  ansible-core-2.x.x-1.el9.x86_64
  ...
Complete!

Теперь давайте настроим каталог нашего проекта. Вся наша работа для этой лабораторной работы будет находиться внутри выделенного каталога, чтобы все было организовано.

cd ~/project
mkdir control-flow-lab
cd control-flow-lab

Проекту Ansible нужен файл инвентаризации (inventory file), который определяет хосты, которыми вы хотите управлять. Для этой лабораторной работы мы будем управлять локальной машиной, localhost.

Создайте файл инвентаризации с именем inventory с помощью редактора nano:

nano inventory

Добавьте следующую строку в файл. Это говорит Ansible запускать плейбук на localhost и подключаться к нему напрямую, а не через SSH.

localhost ansible_connection=local

Сохраните файл и выйдите из nano, нажав Ctrl+X, затем Y и Enter.

Далее мы создадим наш первый плейбук, playbook.yml, чтобы продемонстрировать цикл. Этот плейбук установит список полезных инструментов командной строки.

nano playbook.yml

Введите следующее содержимое YAML в редактор. Этот плейбук определяет одну задачу, которая использует модуль ansible.builtin.dnf для установки пакетов. Директива become: yes указывает Ansible выполнять задачи с привилегиями sudo, что необходимо для установки пакетов. Ключевое слово loop предоставляет список имен пакетов. Ansible выполнит эту задачу один раз для каждого элемента в списке, подставляя заполнитель {{ item }} текущим именем пакета.

---
- name: Install common tools
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

Сохраните и выйдите из редактора. Теперь запустите плейбук с помощью команды ansible-playbook, указав ваш файл инвентаризации с помощью флага -i.

ansible-playbook -i inventory playbook.yml

Вывод покажет выполнение плейбука. Ansible проверит каждый пакет и установит его, если он еще не установлен. PLAY RECAP в конце суммирует результаты.

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
changed: [localhost] => (item=git)
changed: [localhost] => (item=tree)
changed: [localhost] => (item=wget)

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Теперь давайте модифицируем плейбук, чтобы включить условную задачу. Мы добавим задачу, которая выводит сообщение, но только если операционная система является Red Hat Enterprise Linux. Это распространенный сценарий для адаптации автоматизации к конкретным средам.

Снова откройте файл playbook.yml:

nano playbook.yml

Добавьте следующие задачи в конец файла. Ключевое слово when оценивает данное выражение. ansible_facts['distribution'] — это переменная, которую Ansible автоматически обнаруживает о управляемом хосте. Первая задача будет выполнена, потому что наша среда — RHEL, а вторая задача будет пропущена.

---
- name: Install tools and run conditional tasks
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

    - name: Show message on Red Hat systems
      ansible.builtin.debug:
        msg: "This system is a Red Hat family distribution."
      when: ansible_facts['distribution'] == "RedHat"

    - name: Show message on other systems
      ansible.builtin.debug:
        msg: "This system is NOT a Red Hat family distribution."
      when: ansible_facts['distribution'] != "RedHat"

Сохраните и выйдите из редактора. Запустите обновленный плейбук:

ansible-playbook -i inventory playbook.yml

Внимательно наблюдайте за выводом. Задача установки пакетов, скорее всего, сообщит ok для всех элементов, поскольку они уже установлены. Что более важно, вы увидите первое отладочное сообщение, выведенное, в то время как второе будет помечено как skipping.

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
ok: [localhost] => (item=git)
ok: [localhost] => (item=tree)
ok: [localhost] => (item=wget)

TASK [Show message on Red Hat systems] *****************************************
ok: [localhost] => {
    "msg": "This system is a Red Hat family distribution."
}

TASK [Show message on other systems] *******************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

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

Реализация обработчиков для перезапуска служб

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

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

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

cd ~/project
mkdir control-handlers-lab
cd control-handlers-lab

Как и раньше, нам нужен файл инвентаризации, чтобы сообщить Ansible, где запускать плейбук.

nano inventory

Добавьте следующую строку, чтобы указать локальную машину.

localhost ansible_connection=local

Сохраните и выйдите из редактора (Ctrl+X, Y, Enter).

Далее нам понадобится файл, который будет служить домашней страницей нашего веб-сервера. Мы создадим каталог files для его хранения.

mkdir files

Теперь создайте простой файл index.html внутри каталога files.

nano files/index.html

Добавьте следующее содержимое HTML:

<h1>Welcome to the Ansible Handler Lab!</h1>

Сохраните и выйдите из редактора.

Теперь вы создадите плейбук deploy_nginx.yml. Этот плейбук выполнит три основных действия: установит Nginx, скопирует файл index.html и определит обработчик для перезагрузки Nginx.

nano deploy_nginx.yml

Введите следующее содержимое. Обратите особое внимание на ключевое слово notify в задаче "Copy homepage" и соответствующий раздел handlers в конце. Директива become: yes указывает Ansible выполнять задачи с привилегиями sudo, что необходимо для установки пакетов и управления службами.

---
- name: Deploy Nginx with a handler
  hosts: localhost
  become: yes
  tasks:
    - name: Ensure Nginx is installed
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: Start and enable Nginx service
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Copy homepage
      ansible.builtin.copy:
        src: files/index.html
        dest: /usr/share/nginx/html/index.html
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded

Сохраните и выйдите из редактора.

Теперь запустите плейбук в первый раз.

ansible-playbook -i inventory deploy_nginx.yml

Вы увидите вывод, показывающий, что Nginx был установлен (или уже присутствовал), служба Nginx была запущена и включена, файл index.html был скопирован (статус changed), и, что важно, обработчик был уведомлен и выполнен в конце выполнения.

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Вы можете проверить, что веб-сервер запущен и обслуживает вашу пользовательскую страницу, используя curl.

curl http://localhost

Вывод должен содержать содержимое вашего файла index.html.

<h1>Welcome to the Ansible Handler Lab!</h1>

Теперь запустите тот же самый плейбук снова без внесения каких-либо изменений.

ansible-playbook -i inventory deploy_nginx.yml

На этот раз обратите внимание на вывод. Задача "Copy homepage" сообщит ok вместо changed, потому что файл на целевом хосте уже соответствует источнику. Задача "Start and enable Nginx service" также сообщит ok, поскольку служба уже запущена и включена. Поскольку ни одна задача не уведомила обработчик, обработчик не был запущен.

...
TASK [Copy homepage] ***********************************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Чтобы снова увидеть обработчик в действии, давайте изменим исходный файл index.html.

nano files/index.html

Измените содержимое на следующее:

<h1>The Handler Ran Again!</h1>

Сохраните и выйдите. Теперь запустите плейбук еще раз.

ansible-playbook -i inventory deploy_nginx.yml

Поскольку исходный файл изменился, задача "Copy homepage" снова сообщит changed, что, в свою очередь, уведомляет и запускает обработчик reload nginx.

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Проверьте изменение с помощью curl в последний раз.

curl http://localhost

Вы должны увидеть обновленное сообщение.

<h1>The Handler Ran Again!</h1>

Это упражнение демонстрирует мощь и эффективность обработчиков для управления состоянием служб в ответ на изменения конфигурации.

Управление сбоями задач с помощью Block и Rescue

На этом шаге вы узнаете, как корректно обрабатывать ошибки в ваших плейбуках Ansible. По умолчанию, если какая-либо задача завершается с ошибкой, Ansible прекращает выполнение всего плейбука на этом хосте. Хотя это безопасное поведение по умолчанию, иногда вам нужен больший контроль. Вы изучите два метода обработки ошибок: простую директиву ignore_errors и более мощную структуру block, rescue и always, которая предоставляет способ попытаться выполнить задачи и определить действия по восстановлению в случае их сбоя.

Сначала создадим новый каталог для этого упражнения.

cd ~/project
mkdir control-errors-lab
cd control-errors-lab

Создайте стандартный файл inventory для localhost.

nano inventory

Добавьте следующее содержимое:

localhost ansible_connection=local

Сохраните и выйдите из редактора (Ctrl+X, Y, Enter).

Теперь создадим плейбук с именем playbook.yml, который спроектирован так, чтобы завершаться с ошибкой. Первая задача попытается установить несуществующий пакет.

nano playbook.yml

Введите следующее содержимое. Этот плейбук пытается установить фиктивный пакет httpd-fake, а затем реальный пакет mariadb-server.

---
- name: Demonstrate Task Failure
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt to install a non-existent package
      ansible.builtin.dnf:
        name: httpd-fake
        state: present

    - name: Install MariaDB server
      ansible.builtin.dnf:
        name: mariadb-server
        state: present

Сохраните и выйдите из редактора. Теперь запустите плейбук.

ansible-playbook -i inventory playbook.yml

Вы увидите, что первая задача завершается с ошибкой, поскольку пакет httpd-fake не найден. Важно отметить, что Ansible остановится, и вторая задача, "Install MariaDB server", не будет выполнена.

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "No match for argument: httpd-fake", "rc": 1, "results": []}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Теперь давайте используем block и rescue для более элегантной обработки этой ошибки. Ключевое слово block группирует набор задач. Если какая-либо задача в block завершается с ошибкой, Ansible пропускает остальные задачи в block и выполняет задачи в разделе rescue. Раздел always будет выполнен независимо от того, успешно ли завершились разделы block или rescue.

Измените playbook.yml, чтобы использовать эту структуру.

nano playbook.yml

Замените все содержимое следующим. Здесь мы пытаемся установить фиктивный пакет в block. Когда он завершается с ошибкой, выполняется раздел rescue, который устанавливает mariadb-server в качестве шага восстановления. Раздел always выведет сообщение в конце.

---
- name: Handle Task Failure with Block and Rescue
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt primary task, with recovery
      block:
        - name: Attempt to install a non-existent package
          ansible.builtin.dnf:
            name: httpd-fake
            state: present
        - name: This task will be skipped
          ansible.builtin.debug:
            msg: "This message will not appear because the previous task fails."
      rescue:
        - name: Install MariaDB server on failure
          ansible.builtin.dnf:
            name: mariadb-server
            state: present
      always:
        - name: This always runs
          ansible.builtin.debug:
            msg: "The block has completed, either by success or rescue."

Сохраните и выйдите. Запустите плейбук снова.

ansible-playbook -i inventory playbook.yml

Обратите внимание на вывод. Первая задача в block завершается ошибкой, как и ожидалось. Вторая задача в block пропускается. Затем Ansible переходит к разделу rescue и успешно устанавливает mariadb-server. Наконец, выполняется раздел always.

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => ...

TASK [This task will be skipped] ***********************************************
skipping: [localhost]

RESCUE START *******************************************************************

TASK [Install MariaDB server on failure] ***************************************
changed: [localhost]

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=1    ignored=0

Теперь давайте посмотрим, что произойдет, когда block завершится успешно. Отредактируйте плейбук и исправьте имя пакета.

nano playbook.yml

Измените httpd-fake на реальный пакет httpd.

## ... (rest of the playbook)
block:
  - name: Attempt to install a valid package
    ansible.builtin.dnf:
      name: httpd ## Corrected from httpd-fake
      state: present
  - name: This task will now run
    ansible.builtin.debug:
      msg: "This message will now appear because the previous task succeeds."
## ... (rest of the playbook)

Сохраните и выйдите. Запустите плейбук в последний раз.

ansible-playbook -i inventory playbook.yml

На этот раз обе задачи в block завершаются успешно. Поскольку block завершился без ошибок, раздел rescue полностью пропускается. Раздел always по-прежнему выполняется, как следует из его названия.

...
TASK [Attempt to install a valid package] **************************************
changed: [localhost]

TASK [This task will now run] **************************************************
ok: [localhost] => {
    "msg": "This message will now appear because the previous task succeeds."
}

RESCUE START *******************************************************************
skipping rescue

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Теперь вы успешно использовали структуру block/rescue/always для создания надежного плейбука, который может обрабатывать сбои и выполнять действия по восстановлению.

Управление состоянием задач с помощью changed_when и failed_when

На этом шаге вы получите более точный контроль над тем, как Ansible интерпретирует результаты ваших задач. Вы узнаете о двух мощных директивах: changed_when и failed_when.

  • changed_when: По умолчанию модули, такие как ansible.builtin.command или ansible.builtin.shell, почти всегда сообщают о состоянии "changed" (изменено), даже если выполненная ими команда не внесла изменений в систему. changed_when позволяет определить пользовательское условие, которое определяет, должна ли задача быть помечена как "changed". Это крайне важно для написания идемпотентных плейбуков и для точного запуска обработчиков.
  • failed_when: Иногда команда может завершиться с ненулевым кодом выхода (что Ansible считает ошибкой), даже если результат является приемлемым. failed_when позволяет переопределить условия ошибки по умолчанию, позволяя вашему плейбуку продолжать работу на основе более интеллектуальных критериев, таких как вывод команды или конкретный код выхода.

Начнем с настройки нового каталога проекта.

cd ~/project
mkdir control-state-lab
cd control-state-lab

Создайте стандартный файл inventory для localhost.

nano inventory

Добавьте следующее содержимое:

localhost ansible_connection=local

Сохраните и выйдите из редактора (Ctrl+X, Y, Enter).

Использование changed_when

Сначала посмотрим, как задача команды ведет себя по умолчанию. Мы создадим плейбук, который выполняет команду date. Эта команда просто выводит дату и не изменяет систему, но модуль command сообщит о ней как об изменении.

Создайте новый плейбук с именем playbook.yml.

nano playbook.yml

Введите следующее содержимое:

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (default behavior)
      ansible.builtin.command: date

Сохраните и выйдите. Теперь запустите плейбук.

ansible-playbook -i inventory playbook.yml

Обратите внимание в выводе, что задача помечена как changed=1, хотя ничего в системе не было изменено.

...
TASK [Check local time (default behavior)] *************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    ...

Теперь давайте используем changed_when, чтобы сообщить Ansible, что эта команда никогда не изменяет систему. Измените playbook.yml.

nano playbook.yml

Добавьте changed_when: false к задаче.

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (with changed_when)
      ansible.builtin.command: date
      changed_when: false

Сохраните и выйдите. Запустите плейбук снова.

ansible-playbook -i inventory playbook.yml

На этот раз задача сообщает ok, а в итоговом сводке показано changed=0. Вы успешно переопределили поведение по умолчанию.

...
TASK [Check local time (with changed_when)] ************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

Использование failed_when

Далее давайте изучим failed_when. Мы создадим задачу, которая проверяет наличие файла, которого нет. Команда по умолчанию "завершится с ошибкой".

Сначала создайте фиктивный файл для поиска.

echo "System is running" > status.txt

Теперь измените playbook.yml, чтобы искать слово "ERROR" в этом файле. Команда grep завершится с кодом выхода 1, поскольку слово не найдено, что Ansible интерпретирует как ошибку.

nano playbook.yml

Замените содержимое следующим:

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (will fail)
      ansible.builtin.command: grep ERROR status.txt

Сохраните и выйдите. Запустите плейбук.

ansible-playbook -i inventory playbook.yml

Как и ожидалось, выполнение плейбука останавливается с сообщением FAILED!.

...
TASK [Check for ERROR in status file (will fail)] ******************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["grep", "ERROR", "status.txt"], "delta": "...", "end": "...", "msg": "non-zero return code", "rc": 1, ...}
...

Это не то, чего мы хотим. Отсутствие "ERROR" является для нас условием успеха. Мы можем использовать failed_when, чтобы переопределить, что считается ошибкой. Мы сообщим Ansible, что ошибка возникает только в том случае, если код возврата команды больше 1. Код возврата 1 (шаблон не найден) теперь будет считаться успехом. Нам также нужно register результат задачи, чтобы проверить ее код возврата (rc).

Измените playbook.yml в последний раз.

nano playbook.yml

Обновите плейбук с помощью register и failed_when.

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (with failed_when)
      ansible.builtin.command: grep ERROR status.txt
      register: grep_result
      failed_when: grep_result.rc > 1
      changed_when: false

Мы также добавили changed_when: false, потому что grep является операцией только для чтения и не изменяет систему.

Сохраните и выйдите. Запустите финальный плейбук.

ansible-playbook -i inventory playbook.yml

Успех! Задача теперь сообщает ok, потому что ее код возврата был 1, что не соответствует нашему новому условию ошибки (rc > 1). Плейбук успешно завершается.

...
TASK [Check for ERROR in status file (with failed_when)] ***********************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

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

Развертывание безопасного веб-сервера с помощью управления задачами

На этом заключительном шаге вы объедините все изученные концепции — циклы, условные операторы, обработчики и обработку ошибок — для создания единого, надежного плейбука. Цель состоит в том, чтобы развернуть веб-сервер Apache (httpd), обезопасить его с помощью mod_ssl, сгенерировать самоподписанный SSL-сертификат и развернуть пользовательскую домашнюю страницу. Это практическое упражнение имитирует реальную задачу автоматизации.

Сначала настроим каталог проекта для этого заключительного упражнения.

cd ~/project
mkdir control-review-lab
cd control-review-lab

Как всегда, создайте файл inventory для определения целевого хоста.

nano inventory

Добавьте запись localhost:

localhost ansible_connection=local

Сохраните и выйдите из редактора (Ctrl+X, Y, Enter).

Далее нам понадобится каталог для хранения файлов, которые будет развертывать наш плейбук.

mkdir files

Теперь создайте пользовательскую домашнюю страницу index.html в каталоге files.

nano files/index.html

Добавьте следующее HTML-содержимое. Это будет страница, обслуживаемая нашим безопасным веб-сервером.

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

Сохраните и выйдите из редактора.

Теперь пришло время создать основной плейбук deploy_secure_web.yml. Этот плейбук будет сложнее предыдущих, интегрируя несколько концепций.

nano deploy_secure_web.yml

Введите следующий полный плейбук. Прочитайте комментарии в коде, чтобы понять, как каждая часть способствует достижению общей цели.

---
- name: Deploy a Secure Apache Web Server
  hosts: localhost
  become: yes
  vars:
    packages_to_install:
      - httpd
      - mod_ssl
    ssl_cert_path: /etc/pki/tls/certs/localhost.crt
    ssl_key_path: /etc/pki/tls/private/localhost.key

  tasks:
    - name: Stop nginx to free port 80
      ansible.builtin.systemd:
        name: nginx
        state: stopped
      ignore_errors: yes

    - name: Install httpd and mod_ssl packages
      ansible.builtin.dnf:
        name: "{{ packages_to_install }}"
        state: present

    - name: Generate self-signed SSL certificate if it does not exist
      ansible.builtin.command: >
        openssl req -new -nodes -x509
        -subj "/C=US/ST=None/L/O=LabEx/CN=localhost"
        -keyout {{ ssl_key_path }}
        -out {{ ssl_cert_path }}
      args:
        creates: "{{ ssl_cert_path }}"

    - name: Deploy custom index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      notify: restart httpd

    - name: Start and enable httpd service
      ansible.builtin.systemd:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: restart httpd
      ansible.builtin.systemd:
        name: httpd
        state: restarted

Разберем, что делает этот плейбук:

  • vars: Определяет переменные для устанавливаемых пакетов и пути к SSL-сертификату и ключу, что делает плейбук более читаемым и удобным в сопровождении.
  • Задача остановки Nginx: Останавливает службу nginx из предыдущего шага лаборатории, чтобы освободить порт 80 для Apache. Использует ignore_errors: yes на случай, если nginx не запущен.
  • Задача установки: Использует переменную packages_to_install для установки как httpd, так и mod_ssl.
  • Задача генерации сертификата: Это ключевая задача. Она использует команду openssl для создания самоподписанного сертификата. Директива args: { creates: ... } делает эту задачу идемпотентной. Команда будет выполнена только в том случае, если файл сертификата (/etc/pki/tls/certs/localhost.crt) еще не существует.
  • Задача развертывания домашней страницы: Копирует ваш пользовательский index.html. Важно, что она использует notify: restart httpd для запуска обработчика, если файл изменен.
  • Задача запуска службы: Использует модуль systemd для запуска и включения службы httpd после завершения всей конфигурации, гарантируя ее запуск при загрузке.
  • Обработчик: Обработчик restart httpd выполняет перезапуск Apache с использованием systemd, который запускается только при изменении файла конфигурации или содержимого.

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

ansible-playbook -i inventory deploy_secure_web.yml

При первом запуске вы должны увидеть, что несколько задач сообщают об изменении (changed), включая остановку nginx, установку пакетов, генерацию сертификата, копирование файла и запуск службы.

...
TASK [Start and enable httpd service] ******************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=6    changed=5    unreachable=0    failed=0    ...

Наконец, убедитесь, что ваш безопасный веб-сервер работает. Сначала протестируйте HTTP-версию, затем HTTPS-версию с флагом -k для игнорирования предупреждений о самоподписанном сертификате.

curl http://localhost

Вы должны увидеть содержимое вашей пользовательской домашней страницы.

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

Вы также можете протестировать HTTPS-версию:

curl -k https://localhost

Если вы запустите плейбук снова, вы увидите, что ни одна задача не сообщает об изменении (changed), и обработчик не запускается, что доказывает идемпотентность вашего плейбука.

Поздравляем! Вы успешно создали практичный, надежный плейбук Ansible, который объединяет циклы, переменные, идемпотентное выполнение команд и обработчики для развертывания безопасного приложения.

Резюме

В этой лаборатории вы научились управлять выполнением плейбуков Ansible в системе RHEL. Вы начали с настройки базовой среды проекта, включая установку Ansible и создание файла инвентаризации. Затем вы изучили фундаментальные структуры управления потоком, используя циклы для эффективного повторения задач с различными входными данными и условные операторы с инструкцией when для выполнения задач только при определенных обстоятельствах. Основываясь на этом, вы реализовали обработчики для создания отзывчивых автоматизаций, таких как запуск перезапуска службы только при изменении ее конфигурационного файла.

Лаборатория также охватила продвинутые методы управления выполнением плейбуков. Вы узнали, как создавать более надежные плейбуки, используя блоки block и rescue для корректной обработки сбоев задач. Кроме того, вы получили детальный контроль над результатами задач, используя changed_when и failed_when для определения пользовательских условий успеха и сбоя. Наконец, вы объединили все эти навыки, применив их к практическому сценарию: развертыванию безопасного веб-сервера, демонстрируя, как эффективно сочетать циклы, условные операторы, обработчики и обработку ошибок в рабочем процессе автоматизации реального мира.