インシデント
午前2時、ルーチンの本番環境へのデプロイが突然停止しました。特定の IP アドレスに依存する設定ファイルをテンプレート化しようとしていますが、Ansible が突然動作を止めてしまいます。プレイブックは、次のようなそっけないメッセージを表示して失敗します。
fatal: [web-server-01]: FAILED! => {"msg": "AnsibleError: dict object has no attribute 'eth0'"}
このエラーは、Jinja2 テンプレートまたはタスクが、現在のコンテキストに存在しないディクショナリのキーにアクセスしようとしたときに発生します。ネットワークファクトを扱っている場合、これは通常、ターゲットホストのインターフェース名が異なっているか、ファクトの収集(fact gathering)が全く行われていないことを意味します。これはよくあるフラストレーションの原因ですが、修正は簡単です。
なぜこのエラーが発生するのか?
Ansible がファクトのディクショナリ内で 'eth0' やその他のインターフェースを見つけられない主な理由は3つあります。
- 予測可能なネットワークインターフェース名: Ubuntu 22.04 や RHEL 9 などの最新の Linux ディストリビューションでは、デフォルトが
eth0ではなくなっています。ローカルの VM ではeth0を使用していても、AWS EC2 インスタンスではens5、VMWare のゲストマシンではens160が使用されている場合があります。 - ファクト収集の無効化: プレイブックのヘッダーに
gather_facts: falseが含まれている場合、ansible_factsディクショナリは空のままになります。Ansible はホストのハードウェアについて何も関知しません。 - Hostvars のスコープ:
hostvars['db_server']['ansible_eth0']を取得しようとしている可能性があります。もしdb_serverが現在の実行プロセスでまだ処理されていない場合、そのファクトはまだメモリに保存されていません。
解決方法
1. 防御的アプローチ:'default' フィルタの使用
ファクトが存在することを前提にするのは避けましょう。欠落している可能性のあるディクショナリ属性を処理する最も安全な方法は、Jinja2 の default フィルタを使用することです。これにより、キーが見つからない場合にフォールバック値を提供し、クラッシュを防ぐことができます。
# 直接参照する代わりに:
# ip_address: "{{ ansible_facts['eth0']['ipv4']['address'] }}"
# フォールバック値を使用する:
ip_address: "{{ ansible_facts['eth0']['ipv4']['address'] | default('127.0.0.1') }}"
2. ポータブルな手法:デフォルト IPv4 ファクトの使用
eth0 をハードコードすると、いずれ自動化が壊れる原因になります。Ansible には ansible_default_ipv4 という組み込みのファクトが用意されています。この変数は、システムのデフォルトルーティングテーブルで使用されているインターフェースを自動的に指し示します。これは、異なるクラウドプロバイダーや OS バージョン間において、極めて信頼性の高い方法です。
# インターフェース名に関わらず、AWS、Azure、ベアメタルで動作します
my_ip: "{{ ansible_default_ipv4.address }}"
3. ファクト収集の確認
ansible_facts が完全に空の場合は、プレイブックの冒頭を確認してください。グローバルでファクトの収集を無効にしていないかチェックします。フルスキャンを行わずに特定のネットワークデータだけを素早く取得したい場合は、フィルタを指定して setup モジュールを使用します。
- name: 特定のネットワークファクトを収集する
setup:
filter: ansible_eth*
- name: IP が存在する場合に表示する
debug:
msg: "IP is {{ ansible_eth0.ipv4.address }}"
when: ansible_eth0 is defined
4. ディクショナリの構造を確認する
どのキーが利用可能かわからない場合は、アドホックコマンドを実行してください。これが、リモートホストが Ansible に何をレポートしているかを正確に確認する最短の方法です。
ansible all -m setup -a "filter=ansible_interfaces"
あるいは、プレイブックにデバッグタスクを追加して、ネットワーク構造をダンプすることもできます。
- name: すべてのネットワークファクトをデバッグ表示する
debug:
var: ansible_facts.networking
Hostvars の問題を解決する
Web サーバーの設定中にデータベースサーバーの IP が必要な場合、データベースホストがまだ「訪問」されていないためにこのエラーが発生することがあります。Ansible は、現在の実行フローに含まれているホストの hostvars のみに値を投入します。
これを解決するには、プレイブックの最初でインベントリ全体のファクト収集を強制します。
- name: すべてのホストファクトが利用可能であることを確認する
hosts: all
gather_facts: true
- name: Web サーバーの設定
hosts: webservers
tasks:
- name: hostvars から DB の IP を取得する
set_fact:
db_ip: "{{ hostvars['db-server-01']['ansible_default_ipv4']['address'] }}"
予防策とベストプラクティス
プレイブックの安定性を保つために、以下のガイドラインに従ってください。
eth0やenp0s3のような特定の名前よりも、ansible_default_ipv4を優先して使用すること。assertモジュールを使用して変数を検証すること。これにより、不可解な Jinja2 のトレースログではなく、役立つエラーメッセージを表示してプレイブックを停止させることができます。- ディクショナリ内のネストされたキーにアクセスするときは、常に
| defaultフィルタを適用すること。
レガシーな環境を扱う場合、ファクトが信頼できないことがよくあります。そのような場合、私は Subnet Calculator を使用して CIDR ブロックを確認しています。これにより、静的なフォールバック値を host_vars にハードコードする前に、その値が実際に正しいかどうかを確認できます。
検証
--check フラグを付けてプレイブックを実行し、修正を確認してください。default() フィルタが正しく実装されていれば、タスクはクラッシュする代わりにスキップされるか、フォールバック値を使用するはずです。
ansible-playbook site.yml --limit web-server-01 --check
チェックモードを通過すれば、あなたの設定は多様なネットワークインターフェースに対応できる柔軟性を備えたことになります。

