Red Hat Certified Engineer (RHCE) #5 Playbook の基礎: task、handler、冪等性

読了 10分

#4 ad-hoc コマンド でモジュール 1 つをその場で実行してホストの状態を変えてみたなら、今回はその作業をファイルにまとめて宣言的に構成する playbook へ進みます。RHCE 実技で採点される成果物は、結局のところ playbook です。一度書いておけば同じ結果を何度でも再現できなければならず、その再現性を保証する性質がまさに 冪等性 です。今回の記事では playbook の構造と task の作成、handler と notify、そして冪等性の検証まで実技の観点で整理します。

playbook とは #

playbook は 複数の task を順番に定義した YAML ファイル です。ad-hoc コマンドが 1 つのモジュールを 1 回実行する一回限りのツールだとすれば、playbook は同じ作業をファイルに保存して繰り返し実行し、バージョン管理できる形です。採点者は試験会場で皆さんが書いた playbook を実行して結果を確認するので、RHCE 実技のほとんどすべての答案は playbook で提出します。

playbook は 1 つ以上の play で構成され、各 play は どのホストにどの task を適用するか を定義します。play の中には適用対象 (hosts)、権限昇格 (become)、そして実際に行う作業のリスト (tasks) が入ります。

playbook の YAML 構造 #

最も単純な playbook を 1 つ見ます。

基本の playbook
---
- name: Web サーバーの基本構成
  hosts: webservers
  become: true
  tasks:
    - name: httpd パッケージのインストール
      ansible.builtin.dnf:
        name: httpd
        state: present

    - name: httpd サービスの起動と起動時の有効化
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: true

このファイルの構造を分解すると次のとおりです。

  • ファイル冒頭の --- は YAML ドキュメントの開始を示します。
  • 最上位は play のリスト です。上の例は play が 1 つだけなので、- で始まる項目が 1 つです。
  • 各 play は適用対象 hosts、権限昇格の有無 become、作業リスト tasks を持ちます。
  • tasks の下の各項目が 1 つの task であり、task ごとにモジュール 1 つを呼び出します。

play と task の関係 #

play は ホスト集合に適用される作業のまとまり です。1 つの playbook の中に play を複数置くこともでき、各 play が互いに異なるホストグループを対象にすることもできます。

play を 2 つ含む playbook
---
- name: Web サーバーの構成
  hosts: webservers
  become: true
  tasks:
    - name: httpd のインストール
      ansible.builtin.dnf:
        name: httpd
        state: present

- name: データベースサーバーの構成
  hosts: dbservers
  become: true
  tasks:
    - name: mariadb のインストール
      ansible.builtin.dnf:
        name: mariadb-server
        state: present

上の playbook は play を 2 つ含んでいます。最初の play は webservers グループに、2 つ目の play は dbservers グループに適用されます。play は定義された順に上から下へ実行されます。

YAML インデントの注意 #

YAML はインデントで構造を表現するので、空白の個数がずれるとファイル全体が壊れます。次のルールを守れば試験会場で些細な文法エラーで時間を失いません。

  • インデントは スペースのみ を使い、タブは使いません。
  • 同じ水準の項目はインデントを正確にそろえます。
  • コロンの後ろには空白を 1 つ置きます (name: httpd)。
  • リスト項目は - の後ろに空白を 1 つ置きます。

task の作成 #

task は モジュール 1 つの呼び出し です。各 task はどのモジュールを、どの引数で実行するかを定義します。ad-hoc コマンドで使っていたモジュールとオプションがそのまま task へ移ってくると考えればよいです。

task の例
tasks:
  - name: chrony パッケージのインストール
    ansible.builtin.dnf:
      name: chrony
      state: present

上の task は ansible.builtin.dnf モジュールを呼び出して chrony パッケージを present の状態にします。name は task の説明であり、その下がモジュール名と引数です。

name を付ける習慣 #

task に name は任意ですが、実技では常に付けるほうが有利です。実行出力に name がそのまま表示されるので、どの task で何が変わったのかを一目で把握できます。デバッグ時間と採点前の自己点検時間の両方を減らせます。

実行結果
TASK [chrony パッケージのインストール] ********************************************
ok: [server1]

name を省略すると出力にモジュール名だけが出て task を識別しにくくなります。すべての task に意味のある name を付ける習慣を身につけます。

FQCN 推奨 #

ansible.builtin.dnf のように ネームスペース.コレクション.モジュール の形でモジュールを書く表記を FQCN (Fully Qualified Collection Name) と言います。短く dnf とだけ書いても動きますが、どのコレクションのモジュールなのかが明確になり名前の衝突を避けられるので FQCN を推奨します。試験会場でも FQCN で書けば安全です。

handler と notify #

サービスの設定ファイルを変えた後はサービスを再起動しないと変更が反映されません。ところが設定が変わっていなければわざわざ再起動する必要はありません。この「変更が起きたときだけ何かを実行する」パターンのために Ansible は handler を提供します。

handler は通常の task と同じ形ですが、play の handlers 節に別途定義し、notify で呼び出されたときだけ 実行されます。

handler と notify の構成
---
- name: Web サーバーの構成
  hosts: webservers
  become: true
  tasks:
    - name: httpd のインストール
      ansible.builtin.dnf:
        name: httpd
        state: present

    - name: httpd 設定のデプロイ
      ansible.builtin.copy:
        src: files/httpd.conf
        dest: /etc/httpd/conf/httpd.conf
        owner: root
        group: root
        mode: '0644'
      notify: restart httpd

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

上の playbook の動作を押さえます。

  • httpd 設定のデプロイ task が実際にファイルを変えると (changed)、notifyrestart httpd handler を呼び出します。
  • ファイルがすでに同じ内容で変わらない場合 (ok)、handler は呼び出されません。
  • 呼び出された handler はすぐには実行されず、play のすべての task が終わった後に一度だけ 実行されます。

handler の 2 つの核心的な性質 #

handler を正確に理解するには 2 つを覚えればよいです。

  • 変更があるときだけ実行されます。 notify をかける task が changed にならないと handler は起きません。同じ設定をそのまま置く 2 回目の実行では task が ok になるので handler も回りません。
  • 最後に一度だけ実行されます。 複数の task が同じ handler を notify しても、その handler は play の最後に 1 回だけ実行されます。設定ファイルを 3 か所変えてもサービス再起動は 1 回で十分だという実際の運用感覚と合致します。

このパターンのおかげで「設定が変わった場合だけサービスを再起動する」動作を冪等性を壊さずに表現できます。

冪等性 #

冪等性 (idempotency) は同じ playbook を何度実行しても結果が同じであることを保証する性質です。1 回目の実行でシステムを望む状態に合わせ、2 回目の実行からはすでにその状態なので何も変えません。RHCE 採点の核心的な基準がまさにこの冪等性です。

changed と ok #

playbook 実行の出力には各 task が何をしたかが色と単語で表示されます。

  • ok。すでに望む状態なので変えるものがなかったことを意味します。
  • changed。task が実際にシステムの状態を変更したことを意味します。
  • failed。task が失敗したことを意味します。

よく書かれた playbook は 1 回目の実行で changed が出て、2 回目の実行では changed が 0 になりすべて ok になります。2 回目の実行でも changed が出続けるなら、その task は冪等性が壊れているという合図です。

実行が終わると最後に PLAY RECAP が要約を見せます。

PLAY RECAP
PLAY RECAP **********************************************************
server1 : ok=3  changed=0  unreachable=0  failed=0

2 回目の実行で changed=0 が出れば冪等性が保証されたという意味です。

command と shell は冪等性がない #

ほとんどのモジュールは「望む状態」を宣言するので自動的に冪等です。dnf はすでにインストール済みのパッケージを再インストールせず、service はすでに実行中のサービスを再起動しません。

一方 commandshell モジュールは 現在の状態を問わず毎回コマンドを実行 します。そのため冪等性がなく、2 回目の実行でも常に changed になります。

冪等性のない task
- name: 誤った例 - 毎回実行される
  ansible.builtin.command: useradd appuser

上の task は 2 回目の実行でユーザーがすでにいてエラーになるか、毎回 changed と表示されます。できる限り commandshell の代わりに専用モジュール (userdnfservice など) を使うのが正解です。

creates と removes で補完 #

専用モジュールがなくやむを得ず commandshell を使うなら、createsremoves オプションで冪等性をまねできます。

  • creates。指定したファイルがすでにあればコマンドを実行しません。
  • removes。指定したファイルがなければコマンドを実行しません。
creates で補完
- name: 初期化スクリプトの実行 (一度だけ)
  ansible.builtin.command: /usr/local/bin/init-db.sh
  args:
    creates: /var/lib/myapp/initialized

上の task は /var/lib/myapp/initialized ファイルがないときだけスクリプトを実行し、ファイルができた後はスキップします。スクリプトがそのファイルを作るように書いておけば、2 回目の実行からは ok と表示されて冪等性が保たれます。

playbook の実行 #

書いた playbook は 2 つの方式で実行できます。

playbook の実行
# 従来の実行器
ansible-playbook site.yml

# execution environment ベースの実行器 (出力を従来と似た形に)
ansible-navigator run site.yml -m stdout

#1 で見たとおり試験環境によって 2 つのどちらでも使えるので、慣れたほうを決めておきつつ ansible-navigator run -m stdout オプションも習得しておきます。

–syntax-check で文法を先に #

実行前に YAML 文法と playbook 構造を素早く検査すると些細なエラーを前もって捉えられます。

構文チェック
ansible-playbook --syntax-check site.yml

エラーがなければ playbook 名だけが出力されます。実技で答案を提出する前の軽い確認手順として推奨します。

–check で dry run #

--check は実際にはシステムを変えずに 何が変わるかを前もって見せてくれる dry run モードです。危険な変更を適用前に点検したり、2 回目の実行で changed が出る task を前もって見つけたりするのに便利です。

dry run 実行
ansible-playbook --check site.yml

–diff で変更内容を確認 #

--diff はファイルがどう変わるかを行単位の差分で見せます。--check と一緒に使えば、適用しないままどの行が変わるかまで確認できます。

変更内容の事前確認
ansible-playbook --check --diff site.yml

設定ファイルを扱う task が意図どおり動くかを確認するときに特に便利です。

総合例 #

ここまで扱った要素を集めた playbook を 1 つ見ます。パッケージをインストールし、設定ファイルをデプロイし、設定が変わった場合だけ handler でサービスを再起動します。

site.yml
---
- name: chrony 時刻同期の構成
  hosts: all
  become: true
  tasks:
    - name: chrony パッケージのインストール
      ansible.builtin.dnf:
        name: chrony
        state: present

    - name: chrony 設定のデプロイ
      ansible.builtin.copy:
        src: files/chrony.conf
        dest: /etc/chrony.conf
        owner: root
        group: root
        mode: '0644'
      notify: restart chronyd

    - name: chronyd サービスの起動と有効化
      ansible.builtin.service:
        name: chronyd
        state: started
        enabled: true

  handlers:
    - name: restart chronyd
      ansible.builtin.service:
        name: chronyd
        state: restarted

この playbook を 2 回実行して冪等性を検証する流れは次のとおりです。

実行と冪等性確認
# 1) 文法確認
ansible-playbook --syntax-check site.yml

# 2) 1 回目の実行 - changed が出る
ansible-playbook site.yml

# 3) 2 回目の実行 - changed=0 が正常
ansible-playbook site.yml

1 回目の実行ではパッケージのインストール・設定のデプロイ・サービスの起動が changed と表示され、設定が変わったので restart chronyd handler が play の最後に一度実行されます。2 回目の実行ではすべての task が ok になり handler は呼び出されず、PLAY RECAP に changed=0 が出ます。この 2 回実行の検証をすべての答案に適用する習慣が RHCE 合格の土台です。

試験ポイント #

  • handler は notify でだけ起き、変更があるときだけ、play の最後に一度 実行されます。設定デプロイ task に notify をかけ handler でサービスを再起動するパターンを手に馴染ませます。
  • 冪等性は 2 回の実行で検証 します。2 回目の実行で changed=0 が出るかを PLAY RECAP で確認する手順を答案ごとに踏みます。
  • command・shell は冪等性がありません。 専用モジュールに変えるのが優先で、やむを得なければ createsremoves で補完します。
  • すべての task に name を付け FQCN を使い、出力の可読性と正確さを確保します。
  • 提出前に --syntax-check で文法を確認し、必要なら --check--diff で変更内容を前もって点検します。

まとめ #

この記事で押さえたこと:

  • playbook は play のリスト であり、play は hostsbecometasks でホスト集合に作業を適用します。
  • task はモジュール 1 つの呼び出し であり、可読性と採点点検のために常に name を付け FQCN で書きます。
  • handler と notify で、変更があるときだけ play の最後に一度サービスを再起動するパターンを構成します。
  • 冪等性 は RHCE の核心的な基準であり、2 回実行して changed=0 を確認します。command・shell は冪等性がないので専用モジュールか createsremoves で補完します。
  • 実行は ansible-playbook または ansible-navigator run -m stdout で行い、--syntax-check--check--diff で前もって点検します。

次へ — 変数と fact #

playbook の骨格は押さえました。ここから同じ playbook をホストごとに違う動作にする変数と、システムから自動で収集される fact へ進みます。

#6 変数と fact: 優先順位、magic vars、custom facts では、変数を定義する複数の場所とその優先順位、ansible_facts で収集されるシステム情報、hostvarsinventory_hostname のような magic variable、そして custom fact を自分で追加する方法まで整理します。

X