RHEL で Ansible Playbook の実行を制御する

Red Hat Enterprise LinuxBeginner
オンラインで実践に進む

はじめに

この実験では、Red Hat Enterprise Linux (RHEL) システム上で Ansible playbook の実行フローを制御する方法を学びます。まず、ループを使用してタスクを効率的に繰り返し実行し、条件分岐を使用して特定の条件が満たされた場合にのみタスクを実行する、基本的な制御構造を利用した playbook を作成します。また、変更が発生した場合にのみサービス再起動などのアクションをトリガーするハンドラーを実装し、自動化をよりインテリジェントで効率的にします。

これらの基本的なスキルを基盤として、playbook の実行を管理するためのより高度なテクニックを探求します。これには、タスクの失敗を適切に処理するためのブロックとレスキュー ステートメントの使用、およびタスクの状態をきめ細かく制御するための changed_whenfailed_when の使用が含まれます。実験の最後に、これらのすべての概念を実践的な演習に適用して、セキュアな Web サーバーをデプロイし、堅牢で信頼性の高い Ansible 自動化を作成する能力を確固たるものにします。

ループと条件分岐を含む Playbook を作成する

このステップでは、Ansible でタスクの実行を制御するための 2 つの基本的な概念、ループと条件分岐について学びます。ループを使用すると、異なる値でタスクを複数回繰り返すことができ、複数のパッケージのインストールや複数のユーザーの作成などのタスクに非常に効率的です。条件分岐は when キーワードを使用して、オペレーティング システムが特定のバージョンである場合やファイルが既に存在する場合など、特定の条件が満たされた場合にのみタスクを実行できます。

まず、LabEx VM に Ansible がインストールされていることを確認します。これには 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 プロジェクトにはインベントリ ファイルが必要です。これは、管理したいホストを定義します。この実験では、ローカル マシンである localhost を管理します。

nano エディターを使用して inventory という名前のインベントリ ファイルを作成します。

nano inventory

ファイルに次の行を追加します。これにより、Ansible は playbook を localhost で実行し、SSH を使用するのではなく直接接続するように指示されます。

localhost ansible_connection=local

ファイルを保存し、Ctrl+X、次に Y、そして Enter を押して nano を終了します。

次に、ループを実証するために最初の playbook、playbook.yml を作成します。この playbook は、便利なコマンドライン ツールのリストをインストールします。

nano playbook.yml

エディターに次の YAML コンテンツを入力します。この playbook は、パッケージをインストールするために ansible.builtin.dnf モジュールを使用する 1 つのタスクを定義します。become: yes ディレクティブは、Ansible に sudo 権限でタスクを実行するように指示します。これはパッケージのインストールに必要です。loop キーワードはパッケージ名のリストを提供します。Ansible は、リスト内の各項目に対してこのタスクを 1 回実行し、{{ 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

エディターを保存して終了します。次に、-i フラグを使用してインベントリ ファイルを指定して、ansible-playbook コマンドで playbook を実行します。

ansible-playbook -i inventory playbook.yml

出力には playbook の実行が表示されます。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

次に、条件付きタスクを含めるように playbook を変更します。オペレーティング システムが Red Hat Enterprise Linux の場合にのみメッセージを表示するタスクを追加します。これは、特定の環境に合わせて自動化を調整するための一般的なユースケースです。

playbook.yml ファイルを再度開きます。

nano playbook.yml

次のタスクをファイルに追加します。when キーワードは、指定された式を評価します。ansible_facts['distribution'] は、Ansible が管理対象ホストについて自動的に検出する変数です。環境が RHEL であるため最初のタスクが実行され、2 番目のタスクはスキップされます。

---
- 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"

エディターを保存して終了します。更新された playbook を実行します。

ansible-playbook -i inventory playbook.yml

出力を注意深く観察してください。パッケージは既にインストールされているため、パッケージ インストール タスクはすべての項目に対して ok を報告する可能性が高いです。さらに重要なことは、最初のデバッグ メッセージが表示され、2 番目のメッセージは 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 playbook を正常に作成および実行しました。

ハンドラーを実装してサービス再起動をトリガーする

このステップでは、Ansible のハンドラーについて学びます。ハンドラーは、別のタスクから「通知」された場合にのみ実行される特別なタスクです。通常、設定ファイルが更新された後に Nginx を再起動するなど、変更が行われた場合にのみ発生するアクションに使用されます。このアプローチは、playbook の実行ごとにサービスを再起動するよりも効率的です。なぜなら、必要な場合にのみアクションが実行されることが保証されるからです。

Nginx Web サーバーをインストールし、カスタムホームページをデプロイし、ホームページの内容が変更された場合にのみ Nginx をリロードするハンドラーを使用する playbook を作成します。

まず、この演習のために新しいディレクトリを作成し、プロジェクトを整理します。

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

前回と同様に、Ansible に playbook をどこで実行するかを伝えるインベントリ ファイルが必要です。

nano inventory

ローカル マシンを指定するために次の行を追加します。

localhost ansible_connection=local

エディターを保存して終了します (Ctrl+XYEnter)。

次に、Web サーバーのホームページとして機能するファイルが必要です。ファイルを格納するために files ディレクトリを作成します。

mkdir files

次に、files ディレクトリ内に簡単な index.html ファイルを作成します。

nano files/index.html

次の HTML コンテンツを追加します。

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

エディターを保存して終了します。

次に、playbook deploy_nginx.yml を作成します。この playbook は、Nginx のインストール、index.html ファイルのコピー、および Nginx のリロード ハンドラーの定義という 3 つの主なアクションを実行します。

nano deploy_nginx.yml

次のコンテンツを入力します。"Copy homepage" タスクの notify キーワードと、末尾の対応する 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

エディターを保存して終了します。

次に、playbook を初めて実行します。

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 を使用して Web サーバーが実行されており、カスタムページを提供していることを確認できます。

curl http://localhost

出力は index.html ファイルの内容であるはずです。

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

ここで、まったく同じ playbook を変更せずに再度実行します。

ansible-playbook -i inventory deploy_nginx.yml

今回は、出力を観察してください。宛先のファイルが既にソースと一致しているため、"Copy homepage" タスクは changed の代わりに ok を報告します。サービスは既に実行および有効化されているため、"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>

保存して終了します。次に、playbook をもう一度実行します。

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 playbook でエラーを適切に処理する方法を学びます。デフォルトでは、タスクが失敗した場合、Ansible はそのホストでの playbook 全体の実行を停止します。これは安全なデフォルトですが、場合によってはより多くの制御が必要になります。ここでは、エラー処理の 2 つの方法を探ります。単純な ignore_errors ディレクティブと、より強力な blockrescuealways 構造です。これにより、タスクを試行し、失敗した場合の回復アクションを定義する方法が提供されます。

まず、この演習のために新しいディレクトリを作成します。

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

localhost 用の標準的な inventory ファイルを作成します。

nano inventory

次のコンテンツを追加します。

localhost ansible_connection=local

エディターを保存して終了します (Ctrl+XYEnter)。

次に、失敗するように設計された playbook.yml という名前の playbook を作成します。最初のタスクは、存在しないパッケージをインストールしようとします。

nano playbook.yml

次のコンテンツを入力します。この playbook は、偽のパッケージ 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

エディターを保存して終了します。次に、playbook を実行します。

ansible-playbook -i inventory playbook.yml

httpd-fake パッケージが見つからないため、最初のタスクがエラー メッセージで失敗することがわかります。重要なのは、Ansible が停止し、2 番目のタスクである "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

次に、blockrescue を使用して、この失敗をよりエレガントに処理しましょう。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."

保存して終了します。playbook を再度実行します。

ansible-playbook -i inventory playbook.yml

出力に注目してください。block の最初のタスクは予想どおり失敗します。block の 2 番目のタスクはスキップされます。次に 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 が成功した場合に何が起こるかを見てみましょう。playbook を編集してパッケージ名を修正します。

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)

保存して終了します。最後に 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 構造を正常に使用して、障害を処理し回復アクションを実行できる堅牢な playbook を作成しました。

changed_when と failed_when によるタスク状態の制御

このステップでは、Ansible がタスクの結果をどのように解釈するかをより細かく制御する方法を学びます。ここでは、changed_whenfailed_when という 2 つの強力なディレクティブについて説明します。

  • changed_when: デフォルトでは、ansible.builtin.commandansible.builtin.shell のようなモジュールは、実行したコマンドがシステムを変更しなかった場合でも、ほぼ常に「changed」状態を報告します。changed_when を使用すると、タスクが「changed」として報告されるかどうかを決定するカスタム条件を定義できます。これは、冪等性のある playbook を記述し、ハンドラーを正確にトリガーするために重要です。
  • failed_when: 場合によっては、コマンドの終了ステータスコードがゼロ以外であっても (Ansible はこれを失敗と見なします)、その結果が許容できることがあります。failed_when を使用すると、デフォルトの失敗条件を上書きでき、コマンドの出力や特定の終了コードなど、よりインテリジェントな基準に基づいて playbook を続行できます。

まず、新しいプロジェクト ディレクトリを設定しましょう。

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

localhost 用の標準的な inventory ファイルを作成します。

nano inventory

次のコンテンツを追加します。

localhost ansible_connection=local

エディターを保存して終了します (Ctrl+XYEnter)。

changed_when の使用

まず、コマンド タスクがデフォルトでどのように動作するかを見てみましょう。date コマンドを実行する playbook を作成します。このコマンドは日付を出力するだけでシステムを変更しませんが、command モジュールはこれを変更として報告します。

新しい playbook playbook.yml を作成します。

nano playbook.yml

次のコンテンツを入力します。

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

保存して終了します。次に、playbook を実行します。

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

保存して終了します。playbook を再度実行します。

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

保存して終了します。playbook を実行します。

ansible-playbook -i inventory playbook.yml

予想どおり、playbook の実行は 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 を使用して、何が失敗を構成するかを再定義できます。コマンドの戻りコードが 1 より大きい場合にのみ失敗するように Ansible に指示します。戻りコード 1 (パターンが見つからない) は、成功と見なされるようになります。また、戻りコード (rc) を検査するためにタスクの結果を register する必要があります。

最後に playbook.yml を変更します。

nano playbook.yml

registerfailed_when で playbook を更新します。

---
- 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

grep は読み取り専用操作であり、システムを変更しないため、changed_when: false も追加しました。

保存して終了します。最後の playbook を実行します。

ansible-playbook -i inventory playbook.yml

成功しました!タスクの戻りコードは 1 であり、新しい失敗条件 (rc > 1) を満たさないため、タスクは ok を報告します。playbook は正常に完了します。

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

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

これで、changed_whenfailed_when を使用してタスクの成功、変更、および失敗の状態を正確に定義する方法を学びました。これにより、より堅牢でインテリジェントな自動化が可能になります。

タスク制御を使用したセキュアな Web サーバーのデプロイ

この最終ステップでは、これまでに学んだループ、条件分岐、ハンドラー、エラー処理のすべての概念を組み合わせて、単一の堅牢な playbook を構築します。目標は、Apache Web サーバー (httpd) をデプロイし、mod_ssl で保護し、自己署名 SSL 証明書を生成し、カスタムホームページをデプロイすることです。この実践的な演習は、実際の自動化タスクを反映しています。

まず、この集大成の演習のためにプロジェクト ディレクトリを設定しましょう。

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

いつものように、ターゲット ホストを定義する inventory ファイルを作成します。

nano inventory

localhost エントリを追加します。

localhost ansible_connection=local

エディターを保存して終了します (Ctrl+XYEnter)。

次に、playbook がデプロイするファイルを格納するディレクトリが必要です。

mkdir files

次に、files ディレクトリ内にカスタムホームページ index.html を作成します。

nano files/index.html

次の HTML コンテンツを追加します。これは、セキュアな Web サーバーによって提供されるページになります。

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

エディターを保存して終了します。

次に、メインの playbook deploy_secure_web.yml を構築します。この playbook は以前のものよりも複雑で、複数の概念を統合します。

nano deploy_secure_web.yml

以下の完全な playbook を入力します。コード内のコメントを読んで、各部分が全体目標にどのように貢献しているかを理解してください。

---
- 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=None/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

この playbook が何をするかを分解しましょう。

  • vars: インストールするパッケージと SSL 証明書およびキーのパスを変数として定義し、playbook を読みやすく保守しやすくします。
  • Nginx 停止タスク: 前の実験ステップの nginx サービスを停止し、ポート 80 を Apache 用に解放します。nginx が実行されていない場合に備えて ignore_errors: yes を使用します。
  • インストール タスク: packages_to_install 変数を使用して、httpdmod_ssl の両方をインストールします。
  • 証明書生成タスク: これは重要なタスクです。openssl コマンドを使用して自己署名証明書を作成します。args: { creates: ... } ディレクティブにより、このタスクは冪等性を持ちます。コマンドは、証明書ファイル (/etc/pki/tls/certs/localhost.crt) がまだ存在しない場合にのみ実行されます。
  • ホームページ デプロイ タスク: カスタム index.html をコピーします。重要なのは、ファイルが変更された場合にハンドラーをトリガーするために notify: restart httpd を使用することです。
  • サービス開始タスク: systemd モジュールを使用して、すべての構成が完了した後で httpd サービスを開始および有効化し、起動時に開始されるようにします。
  • ハンドラー: restart httpd ハンドラーは、systemd を使用して Apache を再起動します。これは、構成ファイルまたはコンテンツ ファイルが変更された場合にのみトリガーされます。

エディターを保存して終了します。次に、包括的な playbook を実行します。

ansible-playbook -i inventory deploy_secure_web.yml

最初の実行では、nginx の停止、パッケージのインストール、証明書の生成、ファイルのコピー、サービスの開始など、いくつかのタスクが changed を報告しているはずです。

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

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

最後に、セキュアな Web サーバーが機能していることを確認します。まず HTTP バージョンをテストし、次に自己署名証明書の警告を無視するための -k フラグを使用して HTTPS バージョンをテストします。

curl http://localhost

カスタムホームページのコンテンツが表示されるはずです。

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

HTTPS バージョンもテストできます。

curl -k https://localhost

playbook を再度実行すると、どのタスクも changed を報告せず、ハンドラーも実行されないことがわかります。これにより、playbook が冪等であることが証明されます。

おめでとうございます!ループ、変数、冪等なコマンド実行、ハンドラーを組み合わせて、セキュアなアプリケーションをデプロイする実践的で堅牢な Ansible playbook を正常に構築しました。

まとめ

この実験では、RHEL システムで Ansible playbook の実行を制御する方法を学びました。まず、Ansible のインストールやインベントリ ファイルの作成を含む、基本的なプロジェクト環境のセットアップから始めました。次に、ループを使用して異なる入力でタスクを効率的に繰り返し実行し、when ステートメントを使用した条件分岐で特定の状況下でのみタスクを実行することで、基本的な制御フロー構造を探索しました。これを基盤として、設定ファイルが変更された場合にのみサービス再起動をトリガーするなど、応答性の高い自動化を作成するためにハンドラーを実装しました。

この実験では、playbook 実行を管理するための高度なテクニックもカバーしました。block および rescue 句を使用してタスクの失敗を適切に処理することで、より堅牢な playbook を構築する方法を学びました。さらに、changed_when および failed_when を使用してカスタムの成功および失敗条件を定義することにより、タスクの結果を細かく制御できるようになりました。最後に、これらのすべてのスキルを実践的なシナリオに適用することで統合しました。具体的には、セキュアな Web サーバーをデプロイし、ループ、条件分岐、ハンドラー、エラー処理を実際の自動化ワークフローで効果的に組み合わせる方法を示しました。