エラーの内容
リストをループしたり、when条件で変数を使用したりすると、Ansibleが以下のエラーをスローします:
The conditional check 'item > 5' failed. The error was: error while evaluating conditional (item > 5): 'item' is a string and cannot be compared to an integer
プレイブックは停止し、何も実行されません。厄介なのは、値が数字に見えるのに、Jinja2はそう認識しないことです。
発生原因
Ansibleの変数は型が緩やかで、その柔軟性がここで問題を引き起こします。varsファイル、インベントリ、またはregisterの結果でリストを定義すると、数字のように見えても値は文字列として届くことがよくあります:
vars:
port_list:
- "8080"
- "443"
- "3000"
Jinja2は"8080"と5を互換性のない型として見なします。AnsibleがベースにしているPython 3は、比較時に文字列を数値に暗黙的に変換することを拒否します。Python 2ではそれが許されていましたが、Python 3では許されません。
文字列型の数値が生まれる主な原因:
- クォートで囲まれたYAMLの値:
port: "8080" lookup('env', ...)から取得した環境変数register+stdoutで取得したコマンド出力- CLI経由で
--extra-varsとして渡された変数(例外なく常に文字列) - ホストによっては
ansible_processor_countなどのリモートファクト
問題の診断
まず、Ansibleが実際にどの型として認識しているか確認しましょう。失敗している条件の前にデバッグタスクを追加します:
- name: Debug item type
debug:
msg: "Value: {{ item }} | Type: {{ item | type_debug }}"
loop: "{{ port_list }}"
出力にType: intではなくType: strと表示されていれば、それが原因です。
単一の変数の確認も同じ方法で行えます:
- name: Check variable type
debug:
msg: "{{ my_count | type_debug }}"
修正方法1:intフィルターでキャストする(推奨)
intフィルターを使用します。when式の中で文字列を整数に変換できるため、プレイブックの構造を変更する必要はありません:
- name: Only process ports above 1024
debug:
msg: "High port: {{ item }}"
loop: "{{ port_list }}"
when: item | int > 1024
比較が実行される前にキャストが行われます。空文字列など変換できない値があっても、フィルターはデフォルトで0を返すため、実行が中断されることなくクラッシュを防げます。
別のデフォルト値が必要な場合は明示的に設定できます:
when: item | int(default=0) > 1024
修正方法2:変数定義からクォートを削除する
変数定義を直接制御できる場合は、クォートを削除してください:
# 修正前(文字列)
port_list:
- "8080"
- "443"
# 修正後(整数)
port_list:
- 8080
- 443
YAMLではクォートなしの数値は常に整数として解析されます。ソースデータをきれいにしておくことで、プレイブックがシンプルになり、フィルターの操作が不要になります。
修正方法3:register時点でキャストする
registerの結果は常に文字列として返ってきます。早めにキャストしましょう。理想的には使用するwhen条件の中でキャストします:
- name: Get number of running containers
command: docker ps -q | wc -l
register: container_count
- name: Warn if too many containers
debug:
msg: "Container count is high!"
when: container_count.stdout | int > 10
複数の場所で値を再利用する場合は、キャスト結果をファクトに保存しましょう:
- name: Set integer fact
set_fact:
container_count_int: "{{ container_count.stdout | int }}"
- name: Use integer fact in condition
debug:
msg: "High count: {{ container_count_int }}"
when: container_count_int > 10
修正方法4:CLIからのextra-vars
--extra-varsの文字列は見た目にかかわらず常に文字列です。プレイブック内でキャストしてください:
ansible-playbook site.yml --extra-vars "max_retries=5"
- name: Retry check
debug:
msg: "Too many retries"
when: max_retries | int > 3
あるいは、ソース時点で型を保持するためにJSON形式で値を渡すこともできます:
ansible-playbook site.yml --extra-vars '{"max_retries": 5}'
修正の確認
-vオプションで実行し、タスクの出力を確認します:
ansible-playbook site.yml -v
修正が成功した場合の出力はこのようになります:
TASK [Only process ports above 1024]
skipping: [localhost] => (item=443)
ok: [localhost] => (item=8080)
'item' is a stringエラーはもう表示されません。条件は443をスキップし、8080で実行されます。意図した通りです。
キャストが正しく適用されたか確認したい場合は、フィルター後に型を明示的に出力します:
- name: Confirm type after cast
debug:
msg: "Casted type: {{ item | int | type_debug }}"
loop: "{{ port_list }}"
期待される出力:Casted type: int
クイックリファレンス
{{ item | int }}— 文字列を整数にキャスト(失敗時のデフォルトは0){{ item | float }}— 必要に応じて浮動小数点数にキャスト{{ item | string }}— 整数を文字列に戻してキャスト{{ item | type_debug }}— 実際のPythonの型を出力- クォートなしのYAML数値(
8080)は整数、クォートあり("8080")は文字列
まとめ
ほとんどの場合、原因はYAMLの中でクォートされた数値か、シェルコマンドからパイプされた値です。どちらも文字列として届きます。intフィルターはどちらのケースにも一行で対応できます。プレイブックを大きく変える必要はありません。
身につけておきたい習慣があります。varsファイルで数値をクォートするのをやめることです。YAMLは8080を自動的に整数として扱います。それに任せることで、この種の型エラーの半分は発生する前に消えていきます。

