Red Hat Certified Engineer (RHCE) #6: 変数と fact: 優先順位、magic vars、custom facts

読了 10分

#5 Playbook 基礎 で、task と handler により冪等性のあるプレイブックを書く感覚をつかみました。ただし、それまでのプレイブックは値がコードに埋め込まれていました。ホストごとに異なるパッケージ名、ポート、パスを 1 つのプレイブックで扱うには 変数 が必要です。そして、プレイブックが条件分岐するには、ホスト自身の情報 (IP、メモリ、OS バージョン) を知っている必要があります。その情報を Ansible が自動で集めてくれるものが fact です。

この記事では、変数を定義するいくつもの場所とその優先順位、fact の収集と活用、task の結果を入れる register、インベントリ全体を覗き込む magic 変数、そして自分で作る custom facts まで、実技の観点で整理します。

変数が必要な理由 #

同じプレイブックを web グループと db グループに回すとき、インストールするパッケージや開けるポートが異なります。値を task の中に直接書くと、グループごとにプレイブックを別々に作らなければなりません。変数は 値を 1 か所にまとめ、task では名前で参照 させることで、同じプレイブックがホストごとに異なる値で動くようにします。

変数名は英字・数字・アンダースコアだけを使い、数字で始めません。http-port のようにハイフンを使ってはならず、http_port のようにアンダースコアを使います。試験では変数名を自由に決めてもよいですが、問題に変数名が指定されている場合は、その名前をそのまま使わないと採点されません。

変数を定義する場所 #

Ansible は変数を複数の場所で定義できます。実技でよく使う場所を整理します。

1) play の vars #

プレイブックの中に直接書く、最も単純な方法です。

playのvars
---
- name: 変数を play に直接定義
  hosts: web
  vars:
    http_port: 8080
    web_package: httpd
  tasks:
    - name: パッケージのインストール
      ansible.builtin.dnf:
        name: "{{ web_package }}"
        state: present

2) vars_files で外部ファイルを参照 #

変数を別ファイルに切り出し、プレイブックから読み込みます。変数が多くなったときに整理しやすくなります。

vars_filesでロード
- name: 変数を外部ファイルから読み込む
  hosts: web
  vars_files:
    - vars/web_vars.yml
  tasks:
    - name: パッケージのインストール
      ansible.builtin.dnf:
        name: "{{ web_package }}"
        state: present
vars/web_vars.yml
# vars/web_vars.yml
http_port: 8080
web_package: httpd

3) group_vars と host_vars #

#2 で扱ったインベントリベースの変数です。プレイブックの隣にディレクトリを置くと、Ansible が自動で読みます。

プロジェクト構成
project/
├── inventory
├── playbook.yml
├── group_vars/
│   ├── all.yml      # すべてのホスト共通
│   └── web.yml      # web グループ専用
└── host_vars/
    └── node1.yml    # node1 ホスト専用
group_vars/web.yml
# group_vars/web.yml
http_port: 8080
web_package: httpd

ホストごとに値を変えなければならない場合、host_vars が group_vars より優先されます。web グループ全体は 8080 を使いつつ node1 だけ 8443 を使うなら、host_vars/node1.yml に http_port: 8443 を書きます。

4) コマンドラインの extra-vars #

ansible-playbook の実行時に -e (または --extra-vars) で渡す変数です。ほかのすべての定義を上書きする最優先の変数です。

extra-varsで上書き
ansible-playbook playbook.yml -e "http_port=9090"
ansible-playbook playbook.yml -e "@vars/override.yml"

5) register で task の結果を保存 #

task の実行結果を変数に入れる方法です。あとで別に扱います。

変数の参照構文 #

定義した変数は {{ 変数名 }} の形で参照します。二重中括弧は Jinja2 の式であり、詳しい活用は #7 で扱います。

変数の参照
tasks:
  - name: 変数の参照
    ansible.builtin.debug:
      msg: "ポートは {{ http_port }} です"

値が変数だけで始まると、YAML がその行をオブジェクトと誤解するので、引用符で囲む必要があります。

引用符で囲む
# 誤った例: コロンの直後が中括弧で始まる
- name: エラー
  ansible.builtin.debug:
    msg: {{ http_port }}

# 正しい例: 引用符で囲む
- name: 正常
  ansible.builtin.debug:
    msg: "{{ http_port }}"

ディクショナリの値とリストの項目は、ドット記法またはブラケット記法でアクセスします。

辞書とリストへのアクセス
vars:
  users:
    admin:
      shell: /bin/bash
  packages:
    - httpd
    - mariadb-server
tasks:
  - name: ディクショナリとリストへのアクセス
    ansible.builtin.debug:
      msg: "{{ users.admin.shell }} / {{ packages[0] }}"

変数の優先順位 #

同じ名前の変数が複数の場所に定義されると、優先順位が高いほうが勝ちます。Ansible の優先順位ルールは長いですが、実技で覚えるべき核心は extra-vars (-e) が常に最優先 だという点です。よく出くわす場所だけを、おおまかな順序で整理します。

順位変数の場所備考
extra-vars (-e)何でも上書き。最優先
task の varsその task だけ
block の varsblock の範囲
role と include の vars役割の呼び出し時に渡す
play の vars / vars_filesプレイ全体
host_varsホスト専用
group_vars (特定のグループ)グループ専用
group_vars/allすべてのホスト共通
role defaults最も弱い。上書き前提

すべてのルールを暗記する必要はありません。試験では 「この値を強制的に適用せよ」が出たら extra-vars を思い出す、host_vars が group_vars に勝つ、role defaults が最も弱い という 3 つだけ覚えれば十分です。

fact: ホストの情報を収集 #

fact は、Ansible が managed node に接続して自動で集めるシステム情報です。IP、OS バージョン、メモリ、CPU、ディスク、ネットワークインターフェースなどが入っています。ホストごとに異なるこの値で、条件分岐とテンプレートを作ります。

gather_facts #

play が始まるとき、Ansible はデフォルトで fact を収集します。出力に見える Gathering Facts の段階がそれです。収集を止めるには gather_facts: false を置きます。fact が不要な短いプレイブックは、止めると実行が速くなります。

fact収集を無効化
- name: fact 収集を止める
  hosts: web
  gather_facts: false
  tasks:
    - name: fact を使わない作業
      ansible.builtin.debug:
        msg: "速く実行"

setup モジュールで fact を確認 #

どんな fact があるかを確認するには、ad-hoc で setup モジュールを実行します。fact の収集は内部的に setup モジュールが担当します。

setupモジュールでfact確認
ansible node1 -m setup
ansible node1 -m setup -a "filter=ansible_default_ipv4"
ansible node1 -m setup -a "filter=ansible_memory_mb"

filter で欲しい fact だけを絞って見ると、変数名を正確に確認できるので、実技で時間を節約できます。

ansible_facts でアクセス #

収集された fact は ansible_facts ディクショナリに入ります。推奨される形式は ansible_facts['キー'] です。よく使う fact を整理します。

fact 参照意味
ansible_facts['hostname']ホスト名
ansible_facts['default_ipv4']['address']デフォルトの IPv4 アドレス
ansible_facts['memtotal_mb']総メモリ (MB)
ansible_facts['distribution']ディストリビューション名 (RedHat など)
ansible_facts['distribution_major_version']メジャーバージョン (9 など)
ansible_facts['processor_vcpus']仮想 CPU 数
factの活用
- name: fact の活用
  hosts: web
  tasks:
    - name: ホスト情報の出力
      ansible.builtin.debug:
        msg: >-
          {{ ansible_facts['hostname'] }} /
          {{ ansible_facts['default_ipv4']['address'] }} /
          memory {{ ansible_facts['memtotal_mb'] }}MB

旧表記と新表記 #

以前の表記は、ansible_hostnameansible_default_ipv4.addressansible_memtotal_mb のように ansible_ 接頭辞を付けたフラットな変数でした。最新の推奨は ansible_facts['hostname'] の形です。どちらも動くので、試験では慣れたほうを使いつつ、ドキュメントに出た表記と統一するほうが安全です。

register: task の結果を変数に保存 #

register は、task の実行結果 (戻り値、stdout、変更の有無など) を変数に入れて後続の task で使うことです。コマンド出力で分岐したり、次の作業の入力として渡したりするときに使います。

registerの活用
- name: register の活用
  hosts: web
  tasks:
    - name: サービス状態の照会
      ansible.builtin.command: systemctl is-active httpd
      register: httpd_status
      ignore_errors: true
      changed_when: false

    - name: 結果の出力
      ansible.builtin.debug:
        msg: "httpd の結果コードは {{ httpd_status.rc }} です"

    - name: stdout で分岐
      ansible.builtin.debug:
        msg: "httpd が実行中です"
      when: httpd_status.stdout == "active"

register で入った変数には複数のキーが入っています。よく見るキーを整理します。

キー意味
.rcコマンドの戻りコード
.stdout標準出力全体 (文字列)
.stdout_lines標準出力を行単位のリストで
.changed変更の有無 (true/false)
.failed失敗の有無

どんなキーがあるか分からないときは、register した変数を debug で丸ごと出力して構造を確認します。

register変数の構造確認
- name: register 変数の構造を確認
  ansible.builtin.debug:
    var: httpd_status

条件分岐の詳しい内容 (when、loop) は #9 で扱います。

magic 変数 #

magic 変数は、ユーザーが定義しなくても Ansible が常に提供する特殊な変数です。インベントリ全体と現在の実行コンテキストを覗き込むときに使います。試験で「ほかのホストの IP を取得して設定に入れよ」というタイプに直結します。

magic 変数意味
inventory_hostname現在処理中のホストのインベントリ名
hostvarsすべてのホストの変数と fact にアクセスするディクショナリ
groupsグループ名からホスト一覧へのマッピング
group_names現在のホストが属するグループ一覧
ansible_play_hosts今回の play の対象ホスト一覧
magic変数の活用
- name: magic 変数の活用
  hosts: web
  tasks:
    - name: 自分のインベントリ名
      ansible.builtin.debug:
        msg: "私は {{ inventory_hostname }} です"

    - name: ほかのホストの fact を取得
      ansible.builtin.debug:
        msg: "node1 の IP は {{ hostvars['node1']['ansible_facts']['default_ipv4']['address'] }} です"

    - name: db グループのホスト一覧
      ansible.builtin.debug:
        msg: "db メンバー: {{ groups['db'] }}"

hostvars は、ほかのホストがすでに fact を収集したあとでなければ、その値を読めません。1 つの play ですべてのホストの fact を先に収集するように対象グループを取るのが安全です。

custom facts #

基本の fact のほかに、ホストに直接埋め込んでおくユーザー定義の fact が custom facts です。managed node の /etc/ansible/facts.d/ の下に .fact 拡張子のファイルを置くと、fact 収集時に自動で読まれて ansible_local に入ります。

.fact ファイルは INI 形式または JSON 形式で書きます。INI の例は次のとおりです。

/etc/ansible/facts.d/custom.fact
# /etc/ansible/facts.d/custom.fact
[web]
package = httpd
port = 8080

[role]
tier = frontend

このファイルが node1 にあると、収集された値は ansible_local を通じてアクセスします。構造は ansible_local['ファイル名']['セクション']['キー'] です。

custom factへのアクセス
- name: custom fact へのアクセス
  hosts: web
  tasks:
    - name: ローカル fact の出力
      ansible.builtin.debug:
        msg: >-
          package {{ ansible_local['custom']['web']['package'] }} /
          tier {{ ansible_local['custom']['role']['tier'] }}

実技では、custom fact ファイルをプレイブックで配布する作業が一緒に出ることもあります。copy または template モジュールで /etc/ansible/facts.d/ にファイルを置き、同じプレイブックの後ろでその値を使うには、setup モジュールで fact を再収集して、その時点で ansible_local に反映させる必要があります。

custom fact配布と再収集
- name: custom fact 配布後の再収集
  hosts: web
  tasks:
    - name: facts.d ディレクトリの作成
      ansible.builtin.file:
        path: /etc/ansible/facts.d
        state: directory
        mode: '0755'

    - name: custom fact ファイルの配布
      ansible.builtin.copy:
        src: files/custom.fact
        dest: /etc/ansible/facts.d/custom.fact
        mode: '0644'

    - name: fact の再収集
      ansible.builtin.setup:

    - name: 配布した fact の確認
      ansible.builtin.debug:
        var: ansible_local['custom']

試験ポイント #

  • extra-vars (-e) が最優先 の変数です。「この値を強制的に適用せよ」が出たら思い出します。
  • host_vars が group_vars より優先し、group_vars/all が最も広く、role defaults が最も弱いです。
  • 変数の参照は {{ }} で行い、値が変数で始まる場合は引用符で囲みます。
  • fact は ansible_facts['キー'] でアクセスし、どんな fact があるかは ansible node -m setup -a "filter=..." で確認します。
  • よく使う fact は hostname、default_ipv4.address、memtotal_mb、distribution_major_version です。
  • register で task の結果を入れ、.rc.stdout.changed で次の task を分岐します。構造が紛らわしければ debug で丸ごと出力します。
  • ほかのホストの値は hostvars['ホスト']['...'] で取得し、グループのメンバーは groups['グループ名'] で得ます。
  • custom facts は /etc/ansible/facts.d/*.fact に置き、ansible_local でアクセスします。配布直後に使うには setup モジュールで再収集します。

まとめ #

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

  • 変数の定義場所。play vars、vars_files、group_vars/host_vars、extra-vars、register の 5 つの枝
  • 優先順位。extra-vars が最優先、host_vars が group_vars より優先、role defaults が最も弱い
  • fact。gather_facts で自動収集、ansible_facts でアクセス、setup モジュールで確認
  • register。task の結果を変数に入れて .rc.stdout.changed で分岐
  • magic 変数。inventory_hostname、hostvars、groups でインベントリ全体を覗き込む
  • custom facts。facts.d の .fact ファイルを ansible_local で読み、配布後は再収集

変数と fact で、ホストごとの値を扱う土台を押さえました。ここからは、その値を加工して設定ファイルを動的に生成する段階へ進みます。

次へ: Jinja2 テンプレート #

変数と fact を集めたので、次はこれらの値を組み合わせて、ホストごとに異なる設定ファイルを作る番です。

#7 Jinja2 テンプレート: フィルター、制御フロー、lookup では、template モジュールと .j2 ファイル、変数の出力とフィルター (default、upper、join など)、for・if の制御フローで繰り返しブロックを生成する方法、そして lookup で外部データを引いてくる方法まで、実技の例で整理します。

X