Red Hat Certified Engineer (RHCE) #5 Playbook の基礎: task、handler、冪等性
#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 つ見ます。
---
- 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 が互いに異なるホストグループを対象にすることもできます。
---
- 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 へ移ってくると考えればよいです。
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 で呼び出されたときだけ 実行されます。
---
- 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)、notifyでrestart httpdhandler を呼び出します。- ファイルがすでに同じ内容で変わらない場合 (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 **********************************************************
server1 : ok=3 changed=0 unreachable=0 failed=02 回目の実行で changed=0 が出れば冪等性が保証されたという意味です。
command と shell は冪等性がない #
ほとんどのモジュールは「望む状態」を宣言するので自動的に冪等です。dnf はすでにインストール済みのパッケージを再インストールせず、service はすでに実行中のサービスを再起動しません。
一方 command と shell モジュールは 現在の状態を問わず毎回コマンドを実行 します。そのため冪等性がなく、2 回目の実行でも常に changed になります。
- name: 誤った例 - 毎回実行される
ansible.builtin.command: useradd appuser上の task は 2 回目の実行でユーザーがすでにいてエラーになるか、毎回 changed と表示されます。できる限り command・shell の代わりに専用モジュール (user、dnf、service など) を使うのが正解です。
creates と removes で補完 #
専用モジュールがなくやむを得ず command や shell を使うなら、creates と removes オプションで冪等性をまねできます。
- creates。指定したファイルがすでにあればコマンドを実行しません。
- removes。指定したファイルがなければコマンドを実行しません。
- 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 つの方式で実行できます。
# 従来の実行器
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 を前もって見つけたりするのに便利です。
ansible-playbook --check site.yml–diff で変更内容を確認 #
--diff はファイルがどう変わるかを行単位の差分で見せます。--check と一緒に使えば、適用しないままどの行が変わるかまで確認できます。
ansible-playbook --check --diff site.yml設定ファイルを扱う task が意図どおり動くかを確認するときに特に便利です。
総合例 #
ここまで扱った要素を集めた playbook を 1 つ見ます。パッケージをインストールし、設定ファイルをデプロイし、設定が変わった場合だけ handler でサービスを再起動します。
---
- 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.yml1 回目の実行ではパッケージのインストール・設定のデプロイ・サービスの起動が 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 は冪等性がありません。 専用モジュールに変えるのが優先で、やむを得なければ
creates・removesで補完します。 - すべての task に name を付け FQCN を使い、出力の可読性と正確さを確保します。
- 提出前に
--syntax-checkで文法を確認し、必要なら--check・--diffで変更内容を前もって点検します。
まとめ #
この記事で押さえたこと:
- playbook は play のリスト であり、play は
hosts・become・tasksでホスト集合に作業を適用します。 - task はモジュール 1 つの呼び出し であり、可読性と採点点検のために常に
nameを付け FQCN で書きます。 - handler と notify で、変更があるときだけ play の最後に一度サービスを再起動するパターンを構成します。
- 冪等性 は RHCE の核心的な基準であり、2 回実行して
changed=0を確認します。command・shell は冪等性がないので専用モジュールかcreates・removesで補完します。 - 実行は
ansible-playbookまたはansible-navigator run -m stdoutで行い、--syntax-check・--check・--diffで前もって点検します。
次へ — 変数と fact #
playbook の骨格は押さえました。ここから同じ playbook をホストごとに違う動作にする変数と、システムから自動で収集される fact へ進みます。
#6 変数と fact: 優先順位、magic vars、custom facts では、変数を定義する複数の場所とその優先順位、ansible_facts で収集されるシステム情報、hostvars・inventory_hostname のような magic variable、そして custom fact を自分で追加する方法まで整理します。