はじめに
2026/05/18 に ansible-core 2.21.0 がリリースされました。
コードネームは「The Rain Song」です。
CHANGELOG などでぱっと見で気になった「便利そうなポイント」と「ちょっと注意ポイント」を簡単ですがまとめます。
先に、リリース時によくチェックするドキュメントをまとめておきます。詳細や正確な情報は、以下の一次情報をご参照ください。
- CHANGELOG
- ポーティングガイド
- いつもはよく書かれているのですが、今回はなぜか内容がありません。現状は changelog 側の Breaking Changes / Porting Guide 、Deprecated Features、Removed Features (previously deprecated) を見るのがよさそうです。
ちなみに、よくあるサポートする最低 Python バージョンの引き上げは今回はありません(参考)。
便利そうなポイント
とても便利そう、地味に便利そうな機能追加や変更点についてです。
レジスタ変数の取り回しがしやすくなる Register Projections
個人的には一番の目玉だと思っています。
1つのタスクで、レジスタ(register)変数を複数定義できる機能です。
ちょっと個別に扱いたかったので、別の記事にしました。
- 関連PR: Register projections and action plugin dynamic host/group/var API by nitzmahone · Pull Request #86241 · ansible/ansible · GitHub
- changelog:
register projections - The register task keyword allows mapping multiple variable names to Jinja expressions to transform task results and other variables. The mapping form can replace many usages of set_fact and allows order-independent chained access to other variable expressions within the same task.
暗黙的なタスクオブジェクト _task による register の省略
同じくレジスタ変数回りの機能追加です。
register を定義しなくても、タスク内であれば _task.result で参照できるようになったりします。
これも個別に扱いたかったので、先述の Register Projections の件と合わせて別の記事にしました。
- 関連PR: Register projections and action plugin dynamic host/group/var API by nitzmahone · Pull Request #86241 · ansible/ansible · GitHub
- changelog:
task implicit object - A new task implicit object is available for use in register and task conditional expressions (e.g., failed_when). The result of the current task can be accessed via the task.result property, without the use of register. Under a loop, task.result is the most recently completed result and task.loop_result provides access to accumulated loop results. The _task.polymorphic_result property provides compatibility with classic name-only register in loops. The value is the result of the most recent loop iteration, then becomes the final list loop result once the loop is complete.
include_* 系の動的読み込みのログの出力有無を切り替え可能に
include_* 系の ansible.builtin.include_tasks モジュールや、ansible.builtin.include_role モジュールで、タスクやロールを動的に読み込む際、今までは Playbook 実行ログに included ~ というログが表示されていました(関連 Issue)。
たとえば、ansible.builtin.include_tasks を loop でループさせると、ループごとに included ~ が表示されノイジーになってしまうことがあります。
こんな課題を解決できるように、このログの表示、非表示を切り替えられるようになりました。
設定項目は、ansible.builtin.default コールバックプラグインの display_included_hosts です。デフォルトは true なので、つまり今までと同じ挙動です。
例えば ansible.cfg で false を設定したい場合は以下のように指定します。
[defaults] display_included_hosts = false
以下のようなタスクを用意します。ansible.builtin.include_tasks を loop で回します。
(ちなみにループは ansible.builtin.import_tasks ではできません)
- name: Test include_tasks ansible.builtin.include_tasks: file: "{{ item }}" loop: - tasks1.yml - tasks2.yml - tasks3.yml
以下のような実行ログになります。included: ~ のログが表示されないことが分かります。
% ansible-playbook -i localhost, include.yml
PLAY [Test Play] ***************************************************************************************************
TASK [Test include_tasks] ******************************************************************************************
TASK [Test debug] **************************************************************************************************
ok: [localhost] => {
"msg": "in tasks1.yml"
}
TASK [Test debug] **************************************************************************************************
ok: [localhost] => {
"msg": "in tasks2.yml"
}
TASK [Test debug] **************************************************************************************************
ok: [localhost] => {
"msg": "in tasks3.yml"
}
PLAY RECAP *********************************************************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
比較のため display_included_hosts をデフォルト(true)に戻して実行すると、included: のログが表示されます。
% ansible-playbook -i localhost, include.yml
PLAY [Test Play] ***************************************************************************************************
TASK [Test include_tasks] ******************************************************************************************
included: /Users/akira/ansible/ac221/tasks1.yml for localhost => (item=tasks1.yml)
included: /Users/akira/ansible/ac221/tasks2.yml for localhost => (item=tasks2.yml)
included: /Users/akira/ansible/ac221/tasks3.yml for localhost => (item=tasks3.yml)
TASK [Test debug] **************************************************************************************************
ok: [localhost] => {
"msg": "in tasks1.yml"
}
TASK [Test debug] **************************************************************************************************
ok: [localhost] => {
"msg": "in tasks2.yml"
}
TASK [Test debug] **************************************************************************************************
ok: [localhost] => {
"msg": "in tasks3.yml"
}
PLAY RECAP *********************************************************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
なお、本機能追加は @usagi_automate さんによるものです。リリースおめでとうございます!
関連記事: https://usage-automate.hatenablog.com/entry/2025/12/16/085853
- 関連PR: https://github.com/ansible/ansible/pull/85758
- changelog:
default callback - add display_included_hosts option to control the included: banner lines for include_tasks/include_role (#84499)
モジュール呼び出し時のパラメーターを register 変数内に仕込めるようになった(INJECT_INVOCATION)
Playbook では モジュールに対してパラメーター(オプション)を指定しますが、これらのパラメーターの値(的なもの)をそのタスクの register 変数に仕込めるようになりました。
正確には、モジュールに指定したパラメーターそのものではなく、取り除かれたり内部のパラメーターが追加されたりの加工があるようです。
設定項目は INJECT_INVOCATIONです。デフォルトは False で無効なので、今まで通りです。
例えば以下のような Playbook があるとします。1つ目のタスクの実行結果が入っている register 変数(result_copy)を、2つ目のタスクで表示して中身を確認する Playbook です。
--- - name: Test Play hosts: localhost connection: local gather_facts: false vars: target_file: output.txt tasks: - name: Generate a file ansible.builtin.copy: content: hello dest: "{{ target_file }}" mode: '0644' register: result_copy - name: Print the registered output ansible.builtin.debug: msg: "{{ result_copy }}"
上記の Playbook を、環境変数 ANSIBLE_INJECT_INVOCATION で true を指定して INJECT_INVOCATION を有効化して実行します。
$ ANSIBLE_INJECT_INVOCATION=true ansible-playbook -i localhost, inject_invocation.yml
PLAY [Test Play] ******************************************************************************************************
TASK [Generate a file] ************************************************************************************************
ok: [localhost]
TASK [Print the registered output] ************************************************************************************
ok: [localhost] => {
"msg": {
"changed": false,
"checksum": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
"dest": "output.txt",
"diff": [],
"failed": false,
"gid": 1000,
"group": "yokochi",
"invocation": {
"module_args": {
"_diff_peek": null,
"_original_basename": ".s7ffcq98",
"access_time": null,
"access_time_format": "%Y%m%d%H%M.%S",
"attributes": null,
"dest": "output.txt",
"follow": true,
"force": false,
"group": null,
"mode": "0644",
"modification_time": null,
"modification_time_format": "%Y%m%d%H%M.%S",
"owner": null,
"path": "output.txt",
"recurse": false,
"selevel": null,
"serole": null,
"setype": null,
"seuser": null,
"src": null,
"state": "file",
"unsafe_writes": false
}
},
"mode": "0644",
"owner": "akira",
"path": "output.txt",
"size": 5,
"state": "file",
"uid": 1000
}
}
PLAY RECAP ************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
上記実行結果のうち、2つ目のタスクの ansible.builtin.debug モジュールで表示しているログのうち、invocation 配下が INJECT_INVOCATION を有効化したことによるものです。
先ほど「加工があるようです」と書きましたが、たとえば Playbook 側の content オプションが invocation には含まれませんし、逆に Playbook 側になかった modification_time_format が invocation に含まれています。
Playbook 上は変数による指定(dest: "{{ target_file }}")でも、ログ上は実際の値("dest": "output.txt")になります。そのため、デバッグしやすい形式になってるとも言えるかなと思います。
- 関連PR: [stable-2.21] Add `INJECT_INVOCATION` config (#86771) by nitzmahone · Pull Request #86865 · ansible/ansible · GitHub
- changelog:
task results - Python and Powershell modules do not include the invocation task result key by default. Injection of the invocation task result key for Python and Powershell modules may be enabled with the var-settable INJECT_INVOCATION config item. Most callbacks mask invocation when displaying a task or loop item result.
補足1: ansible.cfg で設定する場合の注意(ansible-core 2.21.0 時点)
2026/05/20 現在、devel 版ドキュメント上のINJECT_INVOCATIONに掲載されている ini (ansible.cfgのこと)で指定するキーが誤っています。interpreter_python となっていますが、正しくは inject_invocation のようなキーのはずです。
interpreter_python は INTERPRETER_PYTHON というメジャーな設定項目のキーですでに使われています。ドキュメント上だけの不備ではなく実際の挙動にも影響しています。
すでに 修正PRが出されていますので、おそらく ansible-core 2.21.1 で修正されると思います。
補足2: invocation って前からあったような?
「あれ? そういえば前からログに invocation が含まれてる場合もあったような?」と思って ansible-core 2.20 系で確認しました。すると、ログに invocation が表示されるのは -v の v を3つ(-vvv)以上指定して Playbook を実行したときでした。ただ、後続のタスクで register 変数の中身を確認すると invocation が含まれていませんでした。
ansible-core 2.21.0 で INJECT_INVOCATION を有効にすると、ちゃんと register 変数にも invocation が含まれます。
モジュールによって異なるかもしれませんが、今回は ansible.builtin.copy モジュールと ansible.builtin.default コールバックプラグインという組み合わせで確認しました。
template モジュールでエラーの箇所が分かりやすく
Jinja2 テンプレートは便利ではありますが、ときどきエラー時のデバッグがしにくいことがあります。
今回、ansible.builtin.template モジュールを利用したテンプレートに構文エラーがある場合、エラー行にマーカーが引かれて分かりやすくなりました。
例えば、以下のような変数が未定義のテンプレートの場合で説明します。
{% if station_name == "kyoto" %}
京都
{% else if station_name == "demachiyanagi" %}
出町柳
{% else %}
その他の駅
{% endif %}
上記のテンプレートを ansible.builtin.template モジュールで参照すると、以下のようなエラーになります。初見の分かりやすさが向上しました。
TASK [Generate a file] ********************************************************************************************
[ERROR]: Task failed: Syntax error in template: expected token 'end of statement block', got 'if'
Task failed.
Origin: /Users/akira/ansible/ac221/jinja2.yml:11:7
9
10 tasks:
11 - name: Generate a file
^ column 7
<<< caused by >>>
Syntax error in template: expected token 'end of statement block', got 'if'
Origin: /Users/akira/ansible/ac221/test.j2:3
1 {% if station_name == "kyoto" %}
2 京都
3 {% else if station_name == "demachiyanagi" %}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Task failed: Syntax error in template: expected token 'end of statement block', got 'if'"}
PLAY RECAP ********************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
changelog 上は template module の変更点として記載されていますが、試す限り ansible.builtin.template ルックアッププラグイン でも同様でした。
なお、エラーの原因は elif が正しいところ、else if にしているためです。
ちなみに、ansible-core 2.20 までは以下のようなエラーでした。比較のために掲載します。先程と違ってテンプレートファイルのエラーの箇所が表示されていないですね。
TASK [Generate a file] *******************************************************************************************
[ERROR]: Task failed: Syntax error in template: expected token 'end of statement block', got 'if'
Task failed.
Origin: /Users/akira/ansible/ac221/jinja2.yml:11:7
9
10 tasks:
11 - name: Generate a file
^ column 7
<<< caused by >>>
Syntax error in template: expected token 'end of statement block', got 'if'
Origin: /Users/akira/ansible/ac221/test.j2
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Task failed: Syntax error in template: expected token 'end of statement block', got 'if'"}
- 関連PR: https://github.com/ansible/ansible/pull/86101
- changelog:
template module - Report the line number for Jinja syntax errors in template files.
ちょっと注意ポイント
デグレになるかもしれない少し注意が必要そうな変更点や削除された機能についてです。
Interpreter Discovery の legacy 系モードの削除
ターゲットノードの Python 環境を自動的に検出する Interpreter Discovery という機能があります。
検出モードがいくつかありますが、以下のモードが削除されました。
auto_legacyauto_legacy_silent
これまでは Playbook 実行時に以下のような警告が表示されていました。
[DEPRECATION WARNING]: The 'auto_legacy' option for 'INTERPRETER_PYTHON' now has the same effect as 'auto'. This feature will be removed from ansible-core version 2.21.
今回、いよいよ削除されたという形です。
デフォルト値は auto のままです。これまで意図的に legacy 系のモードを指定していた場合は注意が必要です。
なお、試しに ansible-core 2.20.0 で ansible_python_interpreter 変数に auto_legacy を指定した場合は以下のようなエラーが表示されました。auto_legacy が予約されたキーワードではなく、Python インタープリターのパスとして認識された形ですね。
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Task failed: Failed to get information on remote file (test.txt): /bin/sh: auto_legacy: command not found"}
- 関連PR: interpreter_discovery: removed auto_silent* option by Akasurde · Pull Request #86006 · ansible/ansible · GitHub
- changelog:
interpreter_discovery - removed auto_legacy and auto_legacy_slient options (#85995).
ansible-galaxy でコレクションをインストール時に ansible-core のバージョン互換性をチェックするようになった
コレクションでは、どの ansible-core のバージョンをサポートするかを meta.runtime.yml 内の requires_ansible で定義できます。例えば、ansible.utils コレクション 6.0.0 では requires_ansible: ">=2.16.0" と定義され、ansible-core 2.16.0 以上をサポートしていることを示しています。
ansible-core 2.20 までの場合、デフォルトでは requires_ansible の条件に関わらずインストールする挙動でした。「この ansible-core のバージョンはサポートされてないよ」という旨のメッセージが表示されることはありましたが、それはインストール時ではなく Playbook 実行時でした。
ansible-core 2.21.0 では、デフォルトでは requires_ansible の条件を満たしていればインストールし、満たしていなければインストールしない、という挙動になりました。仕様変更というよりバグ修正の扱いです。個人的にもあるべき姿になったかなと思っています。
requires_ansible の条件を満たしていない場合に、インストールを試みたときのログは以下の通りです。具体的には ansible-core 2.21.0 で requires_ansible: ">=2.22.0" が指定されたコレクションをインストールしようとしています。
$ ansible-galaxy collection install git+https://github.com/akira6592/ansible.utils.git,devel Cloning into '/home/yokochi/.ansible/tmp/ansible-local-54954eul3rlht/tmpqo2yc7ps/ansible.utilsfiwl83qa'... remote: Enumerating objects: 5107, done. remote: Counting objects: 100% (198/198), done. remote: Compressing objects: 100% (152/152), done. remote: Total 5107 (delta 110), reused 44 (delta 44), pack-reused 4909 (from 2) Receiving objects: 100% (5107/5107), 1.35 MiB | 7.37 MiB/s, done. Resolving deltas: 100% (3032/3032), done. branch 'devel' set up to track 'origin/devel'. Switched to a new branch 'devel' Starting galaxy collection install process Process install dependency map ansible-galaxy is looking at multiple versions of ansible.utils to determine which version is compatible with other requirements. This could take a while. [ERROR]: Failed to resolve the requested dependencies map. Could not satisfy the following requirements: * ansible.utils:6.0.2 (dependency of git collection from a Git repo) requires ansible-core >=2.22.0 Hint: To disregard whether the collection supports the current version of ansible-core, configure COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH as "ignore". Hint: Pre-releases hosted on Galaxy or Automation Hub are not installed by default unless a specific version is requested. To enable pre-releases globally, use --pre.
以下の行を見れば理由がよくわかるようになっていますね。
* ansible.utils:6.0.2 (dependency of git collection from a Git repo) requires ansible-core >=2.22.0
※ 現状、requires_ansible: ">=2.22.0" が指定されている公開コレクションが思い浮かばなかったので、検証用にフォークして書き換えたコレクションを指定しています。
あまり多くないケースだと思いますが、もし requires_ansible の条件に関わらずコレクションをインストールしたい場合は、COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH に ignore を指定します。
- 関連PR: ansible-galaxy - only install/download collections which support ansible-core by default by s-hertel · Pull Request #86183 · ansible/ansible · GitHub
- changelog:
ansible-galaxy install and ansible-galaxy collection install|download - collections that declare a requires_ansible version that is not compatible with the running ansible-core version are now excluded from installation and download by default. In previous versions, ansible-galaxy would install such collections even if doing so resulted in an error at load time. To restore the previous behavior, set COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH to ignore in your configuration. (#78539)
おわりに
2つ前の ansible-core 2.19 の時にはテンプレート処理の変更という、影響度高めの変更がありましたが、前回(2.20)と今回(2.21)はそこまで大きいものはなさそうです。
最初に挙げた「Register Projections」は Playbook がすっきり書けるようになって便利そうです。使えるタイミングがあれば使ってみたいと思います。


