ロジックのギャップ:スキップされたタスクがプレイブックを停止させる理由
本番環境へのデプロイの途中で、プレイブックが突然停止した場面を想像してみてください。これはサーバーのクラッシュやネットワークの不具合によるものではなく、ロジック上のエッジケースが原因です。あるタスクの結果を変数に保存しているものの、そのタスクがスキップされたために、次のステップが存在しないデータを読み取ろうとして失敗しているのです。
このエラーは通常、register ステートメントと when 条件を組み合わせた際に見られます。条件が偽(false)の場合、タスクはバイパスされます。その結果、登録された変数には rc (Return Code)、stdout、stderr といった標準的なキーが含まれなくなります。
問題が発生するシナリオ
- name: Check if custom service is running
command: systemctl is-active my-service
register: service_check
when: check_service_health | default(false)
ignore_errors: yes
- name: Restart service if it failed the check
service:
name: my-service
state: restarted
when: service_check.rc != 0
check_service_health が false の場合、最初のタスクはスキップされます。Ansible は状態を追跡するために service_check 変数を初期化しますが、そこには基本的なメタデータしか含まれません:
{
"skipped": true,
"changed": false
}
2番目のタスクが service_check.rc にアクセスしようとすると、壁にぶつかります。スキップされた結果には rc キーが存在しないため、Jinja2 がエラーを吐き出します:
The conditional check 'service_check.rc != 0' failed. The error was: 'dict object' has no attribute 'rc'
Ansible がこのように動作する理由
Ansible の register キーワードは永続的です。モジュールが実行されなくても変数は作成されます。これは意図的な動作であり、後続のタスクで variable.skipped をチェックできるようにするためです。しかし、シェルコマンドからの 0 や 1 といったリターンコードなどのモジュールの特定の戻り値は、コマンドが実際に実行された場合にのみ存在します。
Jinja2 の世界では、存在しないディクショナリのキーにアクセスすることは致命的なエラーです。予期しない動作を防ぐため、プレイブックは即座に停止します。
解決策 1: 'default' フィルタ(クイック修正)
これを素早く解決するには、Jinja2 の default フィルタを使用します。これにより、タスクがスキップされた場合でも比較対象となるフォールバック値が提供されます。
- name: Restart service if it failed the check
service:
name: my-service
state: restarted
when: (service_check.rc | default(0)) != 0
default(0) を使用することで、チェックが実行されなかった場合は「成功」状態であると仮定します。0 != 0 は偽(false)になるため、再起動タスクはクラッシュすることなく安全にバイパスされます。
解決策 2: 堅牢なロジックによるスキップチェック
デフォルト値に頼ると、実際の失敗が隠れてしまうことがあります。よりプロフェッショナルなアプローチは、結果を評価する前にタスクがスキップされたかどうかを明示的にチェックすることです。
明示的なスキップチェック
- name: Restart service if it failed the check
service:
name: my-service
state: restarted
when:
- service_check is not skipped
- service_check.rc != 0
Ansible は when リストを順番に評価します。service_check is not skipped が false であれば、Ansible はそこで処理を停止します。2行目の rc 属性を探しに行くことさえないため、エラーを効果的に回避できます。
'is defined' テストの使用
最大限の安全性を確保したい場合は、属性自体が存在するかどうかを確認します:
- name: Restart service if it failed the check
service:
name: my-service
state: restarted
when: service_check.rc is defined and service_check.rc != 0
検証:修正のテスト
ロジックが正しく機能するかどうかを確認するために、本番環境での実行を待つ必要はありません。スキップの状態は簡単にシミュレートできます。
- スキップを発生させる: 変数を上書きしてプレイブックを実行します:
-e "check_service_health=false" - JSON を検査する: Ansible が実際に何を見ているかを確認するために
debugタスクを追加します:
- name: Debug service check result
debug:
var: service_check
- 詳細モードで実行する:
-vフラグを使用します。タスクの出力がfatalではなくskippingと表示されるはずです。これにより、条件分岐ロジックが欠落した属性を適切に処理できていることが確認できます。
重要なポイント
- 変数の永続性: 実行されたかどうかに関わらず、タスクに到達すれば登録された変数は必ず作成されます。
- 部分的なコンテンツ: スキップされたタスクのディクショナリはほぼ空で、通常は
skipped: trueのみが含まれます。 - 安全な参照: 独自の
when条件を持つタスクからデータを取得する場合は、常に| default()またはis not skippedを使用してください。 - 遅延評価: 条件リストにおいて、Ansible は最初の
falseで停止します。存在チェックを最初に記述しましょう。

