エラーの理解
15個のNginxバーチャルホストをデプロイするためにプレイブックを実行しており、タスクが壁に突き当たるまではすべて順順調に見えます。Ansibleは突然停止し、次の特定のエラーをスローします。
Invalid data passed to 'loop', it requires a list, got <class 'dict'>
Ansibleはここで文字通りのことを伝えています。バージョン2.5で標準となった loop キーワードは、厳密にYAMLリスト(配列)を必要とします。もしディクショナリ(波括弧で囲まれたキーと値のペアのセット)を渡すと、タスクは即座に失敗します。これは、丸い穴に四角い杭を打ち込もうとするようなものです。
なぜこれが発生するのか
ほとんどの場合、この問題はデータの構造化や取得方法が原因で発生します。主な3つの原因は以下の通りです。
- ディクショナリの直接使用: 変数をディクショナリとして定義したが、単純なリストであるかのようにループさせようとした。
- 登録済み変数:
findのようなモジュールの出力をキャプチャしたが、その中のファイルリストではなく、結果オブジェクト全体をループさせようとしている。 - YAML構文のミス: 変数ファイルのハイフン(
-)の欠落により、意図せずリストがディクショナリになってしまっている。
ステップバイステップの修正方法
シナリオ1:実行時にディクショナリを変換する
例えば、vars/main.yml で次のようにユーザーリストが定義されているとします。
# vars/main.yml
users:
alice:
uid: 1001
shell: /bin/bash
bob:
uid: 1002
shell: /bin/zsh
もし loop: "{{ users }}" を使おうとすると、Ansibleはそれをディクショナリと見なして失敗します。修正策は dict2items フィルターです。これにより、ディクショナリをAnsibleが処理できるリスト形式に再構成します。
# 修正後のタスク
- name: ディクショナリからユーザーを作成する
ansible.builtin.user:
name: "{{ item.key }}"
uid: "{{ item.value.uid }}"
shell: "{{ item.value.shell }}"
loop: "{{ users | dict2items }}"
シナリオ2:登録済み変数の構造をたどる
これは find や stat モジュールを使用する際の典型的な罠です。これらのモジュールは、メタデータ、タイムスタンプ、ステータスコードを含む巨大なディクショナリを返します。実際に必要なリストは、通常1つ下の階層に隠れています。
# 間違った方法
- name: 古いログファイルを検索する
ansible.builtin.find:
paths: /var/log/nginx
patterns: "*.log.gz"
register: found_logs
- name: ログをクリーンアップする
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ found_logs }}" # これは失敗します!
修正方法: ループを files 属性に向ける必要があります。そこに実際のリストが存在します。
# 正しい方法
- name: ログをクリーンアップする
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ found_logs.files }}" # 特定のリストをターゲットにする
シナリオ3:YAMLフォーマットのエラーを見つける
YAMLは繊細です。ハイフンを忘れると、データ構造が完全に変わってしまいます。この比較を見てください。
# 不正確:これはディクショナリです
my_packages:
git: present
vim: present
# 正確:これはリストです
my_packages:
- git
- vim
変数ファイルを確認してください。loop が my_packages を指している場合、各項目がハイフンで始まっていることを確認してください。ハイフンがないと、Ansibleはディクショナリと見なし、エラーを誘発します。
検証手順
扱っているデータ型が不明な場合は、debug モジュールと type_debug フィルターを使用して、実行中の中間変数を検査してください。推測の手間を大幅に省けます。
- name: データ型を確認する
ansible.builtin.debug:
msg: "変数の型は {{ users | type_debug }} です"
- name: 変換を検証する
ansible.builtin.debug:
msg: "フィルター適用後の型は {{ (users | dict2items) | type_debug }} です"
出力が list であれば、準備完了です。
成功のための実践的なヒント
プレイブックが大きくなるにつれて、YAMLを視覚化するのは難しくなります。行き詰まったときは、YAML to JSON変換ツールを使用してみてください。JSONはブラケット(角括弧)とブレース(波括弧)によって構造がより厳格であるため、オブジェクト(ディクショナリ)を作成したのか配列(リスト)を作成したのかが一目でわかります。
最後にもう一つのテクニック:プレイブックを -v オプションで実行してください。Ansibleは登録済み変数の完全なJSON構造を表示します。これにより、属性名を推測することなく、ループに必要なリストを保持しているキーを正確に特定できます。

