Ansibleで複数ホストを管理するとき、ホスト名でファイル名を変えたい、他のサーバーのIPをconfigに埋め込みたい、特定グループにだけ条件分岐したい。こうした要求に応えるのが Magic Variables(特殊変数)です。
この記事では、RHEL 9系/ansible-core 2.17を想定環境として、AnsibleのMagic Variablesの概念から
hostvars・groups・inventory_hostname を使った実践的な設計パターンを体系的に解説します。さらに map・selectattr フィルタとの組み合わせ、playbook_dir・inventory_dir によるパス管理、ansible_play_hosts を使った実行台数制御、ansible_check_mode によるドライラン制御も取り上げ、「なんとなく動いている」Playbookを「意図を持って設計した」Playbookに変えるための設計入門として読んでください。この記事のポイント
・Magic VariablesはAnsibleが自動設定する組み込み変数で上書きすると予期しない動作になる
・inventory_hostnameでInventory上の論理名を、hostvarsで他ホストの変数をクロスホスト参照できる
・groups・group_namesでグループ条件分岐やHAProxy設定の自動生成ができる
・map/selectattrフィルタとhostvarsを組み合わせるとIPリスト生成がフィルタ1行で書ける
・ansible_check_modeでドライラン時だけ処理をスキップする安全設計も実現できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜMagic Variablesが必要なのか|静的なPlaybookの限界
Ansibleを使い始めると、まずシンプルなPlaybookを書きます。しかしホスト数が増え、環境が複雑になるにつれ、次のような壁にぶつかります。・ホストごとに異なるファイル名を生成したい
・WebサーバーのconfigにデータベースサーバーのIPを自動的に埋め込みたい
・「本番グループのホストだけ」にある処理を実行したい
・「このPlaybookが実行されている台数」をログに残したい
こうした要求に答えるために
vars でいちいち変数を定義していると、「変数の定義がPlaybookとInventoryの両方に散らばる」「ホストを追加するたびに複数箇所を書き換える」という問題が起きます。手動管理の変数はInventoryとの乖離が発生しやすく、本番環境でのトラブル原因にもなります。Magic Variablesを使えば、Ansibleが実行時に自動で設定する情報をそのまま活用できます。ホスト名・グループ情報・他ホストの変数といった「Ansibleがすでに知っていること」を、Playbookの中で再利用する設計が可能になります。Inventoryが「唯一の真実の情報源(Single Source of Truth)」として機能し、ホストを追加・削除してもPlaybookを書き換えなくて済む状態が理想形です。
Magic Variablesの全体マップ|種類と役割の整理
Magic Variablesは大きく4種類に分類できます。| 変数名 | 種別 | 内容 |
|---|---|---|
inventory_hostname |
ホスト識別 | Inventoryに記述したホスト名(FQDNまたは短縮名) |
inventory_hostname_short |
ホスト識別 | 最初のドットより前の短縮ホスト名 |
ansible_host |
ホスト識別 | 実際の接続先IP/ホスト名(ansible_host変数で指定した値) |
hostvars |
クロスホスト参照 | 全ホストの変数を辞書形式で参照できる |
groups |
グループ情報 | グループ名→ホスト名リストの辞書 |
group_names |
グループ情報 | 現在のホストが属するグループ名のリスト |
ansible_play_hosts |
実行状態 | 現在のPlayで対象となるアクティブなホスト名のリスト(失敗したホストは除外される) |
ansible_play_hosts_all |
実行状態 | 失敗したホストも含む、Play開始時点の全対象ホストのリスト |
ansible_play_name |
実行状態 | 現在実行中のPlay名(nameで指定した文字列) |
ansible_check_mode |
実行状態 | --checkフラグで実行中はTrue。ドライランと本番適用の処理を切り替えられる |
ansible_verbosity |
実行状態 | ansible-playbookの -v オプションで指定された詳細度(0~5の整数) |
ansible_version |
実行環境 | Ansibleのバージョン情報の辞書 |
playbook_dir |
パス | Playbookファイルが置かれているディレクトリの絶対パス |
inventory_dir |
パス | Inventoryファイルが置かれているディレクトリの絶対パス |
vars セクションやInventoryで同名の変数を定義すると予期しない動作の原因になるため、上書きは厳禁です。なお、旧バージョンで使われていた
play_hosts は非推奨になっています。現在は ansible_play_hosts(アクティブ分のみ)と ansible_play_hosts_all(全体)を使い分けるのが正しい設計です。inventory_hostnameとansible_hostで自ホスト情報を扱う
Magic Variablesの中で最もよく使うのがinventory_hostname です。Inventoryに記述したホスト名がそのまま入っています。以下のInventoryがある場合を例に説明します。
# hosts.ini [webservers] web01.example.com web02.example.com [dbservers] db01.example.com ansible_host=192.168.1.101
web01.example.com で実行されるタスクでは、inventory_hostname は web01.example.com、inventory_hostname_short は web01 になります。1. ホスト名を使ったファイル生成パターン
ホストごとにユニークなファイル名を生成したい場合、inventory_hostname を直接使えます。- name: ホスト固有の設定ファイルを配置する template: src: nginx.conf.j2 dest: /etc/nginx/conf.d/{{ inventory_hostname_short }}.conf owner: root mode: '0644'
web01.conf / web02.conf のようなホスト固有ファイルが生成されます。vars に hostname: web01 と手書きする必要はありません。2. ansible_hostとinventory_hostnameの違い
混乱しやすいポイントです。inventory_hostname はInventoryの記述名、ansible_host は実際のSSH接続先です。たとえば
db01.example.com ansible_host=192.168.1.101 と定義されている場合:・
inventory_hostname → db01.example.com(Inventoryでの論理名)・
ansible_host → 192.168.1.101(SSHの実際の接続先)ログファイル名にはInventoryの論理名、実際の疎通確認にはansible_hostを使うというように、用途に合わせて使い分けましょう。
3. ansible_verbosityで詳細度に応じたデバッグ出力を切り替える
実運用では、デバッグ情報を常に出力すると運用ログが読みにくくなります。ansible_verbosity を使うと、-v オプションの指定有無に応じて詳細出力を動的に切り替えられます。- name: Magic Variablesの内容を詳細モード時だけ表示する debug: msg: "ホスト={{ inventory_hostname }} グループ={{ group_names }}" when: ansible_verbosity >= 1 # 通常実行(-v なし): このタスクはスキップされる # ansible-playbook -v site.yml 実行時: 全ホストで詳細が表示される
hostvarsで他ホストの変数を参照する設計パターン
hostvars は、Ansible実行中のすべてのホストの変数を辞書で持ちます。これを使うと、WebサーバーのPlaybookにDBサーバーのIPを自動埋め込みといったクロスホスト参照が実現できます。1. 他ホストのIPアドレスをconfigに埋め込む
Webサーバーのconfigにデータベースサーバーの接続先を設定する典型的なユースケースです。# templates/app.conf.j2 [database] host = {{ hostvars['db01.example.com']['ansible_host'] }} port = 3306
db01.example.com ansible_host=192.168.1.101 と定義されていれば、host = 192.168.1.101 が自動生成されます。DBサーバーを移設してInventoryを変えるだけで全Webサーバーの設定が更新されます。2. gather_factsとhostvarsの関係
重要な設計上の注意点があります。hostvars に他ホストのfacts(ansible_default_ipv4 等)を含めるには、そのホストでfactsが収集済みである必要があります。# Play 1: DBサーバーのfactsを先に収集する(tasks不要・gather_factsだけ実行) - hosts: dbservers gather_facts: yes tasks: [] # Play 2: WebサーバーでDBのfactsを参照する - hosts: webservers tasks: - name: DBサーバーのIPをconfigに埋め込む template: src: app.conf.j2 dest: /etc/app/app.conf vars: db_ip: "{{ hostvars[groups['dbservers'][0]]['ansible_default_ipv4']['address'] }}"
facts収集は全ホストに対して実行するとPlaybook開始の待ち時間が長くなります。必要なサブセットだけを収集する
gather_subset を指定するか、ホスト変数(ansible_host)だけを参照する場合は gather_facts: no とすることで実行時間を大幅に短縮できます。3. mapフィルタとhostvarsを組み合わせてIPリストを一括取得する
groups で得たホスト名のリストに map('extract', hostvars, '変数名') を使うと、グループ内の全ホストから特定の変数値をまとめて抽出できます。ループを書かずに一行で完結するのが利点です。# webserversグループの全ホストのansible_hostを一括取得する - name: Webサーバー群のIPリストをset_factに格納する set_fact: webserver_ips: "{{ groups['webservers'] | map('extract', hostvars, 'ansible_host') | list }}" # 出力例(環境により異なります): webserver_ips = ['192.168.1.10', '192.168.1.11', '192.168.1.12'] # Inventoryにホストを追加するだけでリストが自動更新される
map('extract', hostvars, 'ansible_host') は、リスト内の各ホスト名を辞書 hostvars のキーとして使い、指定した変数名の値を取り出す操作です。hostvars[host]['ansible_host'] を全ホストに対して適用するのと同じ結果が、フィルタチェーン一行で書けます。このIPリストは後続タスクの
loop に渡したり、テンプレート内で直接展開したりと応用範囲が広く、Inventoryを「唯一の情報源」として機能させる設計の核になります。この設計パターンをAnsibleの実機環境で体験したい方は、>> Ansibleハンズオン講座の詳細を見る をご覧ください。
4. delegate_toとhostvarsを組み合わせたクロスホスト操作
delegate_to を使うと、タスクの実行対象を別ホストに切り替えられます。このとき inventory_hostname は委任元のホスト名のままなので、hostvars と組み合わせて「委任先ホストの変数」と「委任元ホストの変数」を両方参照する設計が可能です。# 各Webサーバーの処理を、そのWebサーバーに対応するLBホストに委任する - name: 各WebサーバーをLBのドレインリストに追加する command: > lb-manage drain add --host {{ inventory_hostname }} --lbip {{ hostvars[lb_host]['ansible_host'] }} delegate_to: "{{ lb_host }}" vars: lb_host: "lb01.example.com"
inventory_hostname(委任元のWebサーバー名)と hostvars[lb_host]['ansible_host'](委任先LBのIP)を同一タスク内で自然に組み合わせられます。ローリングアップデートのドレイン処理や、複数ホストの連携操作でよく使うパターンです。groupsとgroup_namesでグループ情報を動的に使う
groups と group_names を使うと、Inventoryのグループ構成に基づいた動的な条件分岐や繰り返し処理が可能になります。1. group_namesで「このホストはどのグループか」を判定する
group_names は、現在のホストが属するすべてのグループ名のリストです。when 条件でグループ判定ができます。- name: 本番グループのみfirewalldを有効化する service: name: firewalld state: started enabled: yes when: "'production' in group_names" - name: ステージングではデバッグパッケージを追加する dnf: name: gdb state: present when: "'staging' in group_names"
なお、
group_names で判定するグループは、Inventoryの group_vars/グループ名/ ディレクトリに対応しています。group_names に入っているグループのgroup_vars変数が自動でロードされるため、「どのグループに属しているか」と「どの変数セットが適用されるか」は常に一致します。2. groupsでグループ内の全ホストを参照する
groups['グループ名'] はそのグループに属するホスト名のリストを返します。Jinja2テンプレート内でグループ全体をループする場合に使います。# templates/haproxy.cfg.j2 backend webservers {% for host in groups['webservers'] %} server {{ host }} {{ hostvars[host]['ansible_host'] }}:80 check {% endfor %}
groups['webservers'] を動的に参照するため、Inventoryに新しいWebサーバーを追加するだけでHAProxyの設定ファイルが自動更新されます。3. groups['all']で全ホストを確認する
Ansibleには事前定義のグループall(全ホスト)と ungrouped(グループ未所属のホスト)が常に存在します。# 全ホストのホスト名一覧を出力する(デバッグ用) - debug: msg: "管理対象の全ホスト: {{ groups['all'] }}"
ansible_play_hosts_all | length で対象台数をログに出力し、実行記録として残す運用パターンとも組み合わせやすいです。4. selectattrとhostvarsを組み合わせてホスト変数で絞り込む
host_varsで各ホストにカスタム変数を設定している場合、selectattr を使ってその値でホストを動的に絞り込めます。「primaryロールのホストのIPだけを取得する」といった選択が可能です。# host_vars/web01.yml: role: primary # host_vars/web02.yml: role: replica # host_vars/web03.yml: role: primary # roleがprimaryのホストのIPだけをリスト化する - name: primaryロールのIPリストを生成する set_fact: primary_ips: >- {{ groups['webservers'] | map('extract', hostvars) | selectattr('role', 'defined') | selectattr('role', 'equalto', 'primary') | map(attribute='ansible_host') | list }} # 出力例(環境により異なります): primary_ips = ['192.168.1.10', '192.168.1.12']
・
map('extract', hostvars) — ホスト名のリストを各ホストの変数辞書に変換する・
selectattr('role', 'defined') — role変数が定義されているホストだけに絞る・
selectattr('role', 'equalto', 'primary') — roleがprimaryのホストだけに絞る・
map(attribute='ansible_host') — 各ホストのIPアドレスだけを取り出すInventoryに新しいホストを追加するだけでリストが自動更新されます。Playbookを書き換える必要はありません。
playbook_dir・inventory_dir・ansible_play_hostsで実行コンテキストを扱う
Magic Variablesには、ホストやグループの情報だけでなく「Playbookの実行環境そのもの」を参照する変数も含まれます。特にマルチ環境(dev・staging・prod)を同一Playbookで管理するときや、CI/CDパイプラインに組み込む場面で威力を発揮します。1. playbook_dirで相対パスを排除する
Playbookからローカルファイルを参照するとき、相対パスを書くと実行ディレクトリが変わったとたんにパスが壊れます。playbook_dir を使うと、Playbookファイルの絶対パスが自動で入るため、どのディレクトリから実行しても常に正しいパスを参照できます。# playbook_dirを使ってスクリプトを呼び出す - name: 初期化スクリプトを実行する script: "{{ playbook_dir }}/scripts/init.sh" # files/ディレクトリ内のファイルをコピーする - name: 設定ファイルをコピーする copy: src: "{{ playbook_dir }}/files/app.conf" dest: /etc/app/app.conf # 実行例(/opt/ansible/playbooks/ から実行した場合) # playbook_dir = /opt/ansible/playbooks # → scripts/init.sh = /opt/ansible/playbooks/scripts/init.sh
playbook_dir を使うとPlaybookファイルの場所に追従するため、CI/CDパイプラインへの移植が格段に楽になります。ハードコードしたパスをリポジトリに残すと、別の環境に持ち込んだ途端に動かなくなる典型的な問題を防げます。2. inventory_dirでInventory相対のファイルを参照する
マルチ環境のInventoryを管理するとき、Inventoryファイルと同じディレクトリに環境固有の設定を置く構成はよく使います。inventory_dir でそのディレクトリを絶対パスで取得できます。# ディレクトリ構造の例 # inventory/ # ├── dev/ # │ ├── hosts.ini # │ └── ssl/ ← dev環境のSSL証明書 # └── prod/ # ├── hosts.ini # └── ssl/ ← prod環境のSSL証明書 # inventory_dirでInventory隣のssl/を参照する - name: 環境固有のSSL証明書を配置する copy: src: "{{ inventory_dir }}/ssl/server.crt" dest: /etc/ssl/certs/server.crt # ansible-playbook -i inventory/dev/ site.yml 実行時 # → inventory_dir = /path/to/inventory/dev # ansible-playbook -i inventory/prod/ site.yml 実行時 # → inventory_dir = /path/to/inventory/prod
3. ansible_play_hostsで「Playの最初の1台」にだけタスクを実行する
ansible_play_hosts を使うと、Play内で「最初にアクティブなホスト」を取得できます。データベースのスキーマ変更や共有リソースの初期化など、「全ホストで並列実行すると重複してしまう」タスクを1台だけに絞る場面で効果的です。# ansible_play_hosts[0]と自分のホスト名を比較する - name: DBスキーマ移行は最初の1台だけで実行する command: /opt/app/manage.py migrate when: inventory_hostname == ansible_play_hosts[0] # 複数台のWebサーバーで実行しても migrate は1回だけ走る # ansible_play_hosts[0] は最初のアクティブホスト(失敗ホストは除外済み)
run_once: true と組み合わせると、1台だけでの実行をより明示的に宣言できます。さらに ansible_play_hosts | length と ansible_play_hosts_all | length を比較することで、途中で失敗したホスト数を素早く把握できる運用ログになります。- name: 今回の処理対象台数を記録する debug: msg: > アクティブ: {{ ansible_play_hosts | length }}台 / 全体: {{ ansible_play_hosts_all | length }}台 run_once: true
ansible_play_hosts_all(全対象)との差が1以上あれば、失敗ホストの調査に即移れます。4. ansible_check_modeで本番適用をドライランから安全に切り替える
ansible_check_mode は、--check フラグつきで実行しているときに True になるMagic Variableです。破壊的な操作(データベースマイグレーション・ファイル削除・外部API呼び出しなど)を when: not ansible_check_mode で囲むことで、ドライランでは安全にスキップしつつ、本番実行時だけ適用するPlaybookが書けます。# ドライランでは実行せず、本番実行時だけ migrate を走らせる - name: DBスキーマを移行する(本番実行時のみ) command: /opt/app/manage.py migrate when: not ansible_check_mode # ansible-playbook --check site.yml 実行時: このタスクはスキップされる # ansible-playbook site.yml 実行時: migrate が実行される # check_modeの状態をログに残す場合 - name: 実行モードを確認する debug: msg: "{{ 'ドライラン(--check)モードで実行中' if ansible_check_mode else '本番適用モードで実行中' }}"
ansible_check_mode はタスク単位で check_mode: yes/no を上書きする指示とは別物です。Magic Variableとして参照する用途は「check_modeのときだけ別の処理を走らせたい」「check_modeであることをログに記録したい」といった条件分岐に限定するのが設計上の使い方になります。Magic Variablesを使う際のトラブルシュート
【エラー1】「'hostvars' object has no attribute 'xxx'」
対象ホストがInventoryに定義されていないか、gather_factsが実行されていない場合に発生します。・Inventoryに該当ホストが存在するか確認する:
ansible-inventory --list・Inventoryのグループ階層を視覚的に確認する:
ansible-inventory --graph・gather_facts: yes のPlayを先行して実行し、factsを収集済みにする
・デフォルト値を使う:
{{ hostvars[host]['ansible_host'] | default('未設定') }}【エラー2】inventory_hostnameの値がIPアドレスになっている
Inventoryにホスト名でなくIPアドレスで記載されていると、inventory_hostname もIPアドレスになります。論理名(db01等)でInventoryを定義し、実際の接続先は ansible_host= で指定する書き方を徹底しましょう。# NG: IPアドレスで直接記載(inventory_hostnameがIPになる) [webservers] 192.168.1.10 192.168.1.11 # OK: 論理名 + ansible_host で接続先を分離する [webservers] web01 ansible_host=192.168.1.10 web02 ansible_host=192.168.1.11
【エラー3】group_namesに期待するグループが入っていない
Inventoryのグループ定義が[children] 経由の場合、子グループ経由のホストにも親グループ名は group_names に含まれます。Inventoryの階層構造と group_names の実際の値を debug モジュールで確認しましょう。- debug: var: group_names
ansible-inventory --graph と合わせて確認することで、想定外の親グループ継承に気づきやすくなります。【エラー4】map('extract', hostvars, 'key') で KeyError が出る
map('extract', hostvars, 'ansible_host') を使う場合、グループ内のすべてのホストに ansible_host が定義されている必要があります。未定義のホストが含まれると実行時エラーになります。# NG: ansible_hostが未定義のホストが含まれる場合にエラー {{ groups['webservers'] | map('extract', hostvars, 'ansible_host') | list }} # OK: default フィルタでフォールバック値を指定する {{ groups['webservers'] | map('extract', hostvars) | map(attribute='ansible_host', default='127.0.0.1') | list }}
map(attribute='キー名', default='値') の形式でデフォルト値を指定すると、未定義ホストが混在しても安全に処理できます。【エラー5】ansible_play_hostsとplay_hostsを混同している
古いPlaybookや記事ではplay_hosts が使われていますが、これは非推奨です。ansible-core 2.8以降は ansible_play_hosts(失敗ホストを除くアクティブリスト)と ansible_play_hosts_all(失敗含む全対象リスト)を使います。play_hosts のまま残しておくと、将来のバージョンで予期しない挙動に変わる危険があります。# 旧(非推奨) - debug: msg: "対象: {{ play_hosts }}" # 新(推奨) - debug: msg: "アクティブ: {{ ansible_play_hosts }} 全体: {{ ansible_play_hosts_all }}"
本記事のまとめ
AnsibleのMagic Variables(特殊変数)を使いこなすことで、Inventoryに追加したホストを自動認識し、ホスト固有の設定生成やクロスホスト参照が可能になります。Jinja2フィルタと組み合わせることで、さらに柔軟な動的リスト生成が実現します。| やりたいこと | 使うMagic Variable / フィルタ |
|---|---|
| Inventoryのホスト名をファイル名に使う | inventory_hostname / inventory_hostname_short |
| 他ホストのIPや変数をconfigに埋め込む | hostvars['ホスト名']['変数名'] |
| グループ内全ホストのIPリストを一行で取得する | groups['グループ名'] | map('extract', hostvars, 'ansible_host') | list |
| グループメンバーをループして設定を生成する | groups['グループ名'](テンプレートのforループ) |
| このホストが特定グループに属するか判定する | group_names(whenで条件分岐) |
| ホスト変数の値でグループを動的に絞り込む | groups | map('extract', hostvars) | selectattr(...) | list |
| Playbookのディレクトリを絶対パスで参照する | {{ playbook_dir }}/scripts/script.sh |
| Inventory相対のファイルを環境ごとに切り替える | {{ inventory_dir }}/ssl/server.crt |
| 最初の1台にだけタスクを実行する | when: inventory_hostname == ansible_play_hosts[0] |
| 詳細モード時だけデバッグ情報を表示する | ansible_verbosity(when: ansible_verbosity >= 1) |
| アクティブな対象ホスト数を確認する | ansible_play_hosts | length(全体はansible_play_hosts_all | length) |
| ドライラン時だけ破壊的処理をスキップする | when: not ansible_check_mode |
Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます