てくなべ

ansible / network automation / 学習メモ / 思考メモ

[Ansible] ansible-core 2.21.0 がリリース。便利そうな点と注意点。注目は Register Projections

はじめに

2026/05/18 に ansible-core 2.21.0 がリリースされました。

コードネームは「The Rain Song」です。

www.youtube.com

CHANGELOG などでぱっと見で気になった「便利そうなポイント」と「ちょっと注意ポイント」を簡単ですがまとめます。

先に、リリース時によくチェックするドキュメントをまとめておきます。詳細や正確な情報は、以下の一次情報をご参照ください。

ちなみに、よくあるサポートする最低 Python バージョンの引き上げは今回はありません(参考)。

便利そうなポイント

とても便利そう、地味に便利そうな機能追加や変更点についてです。

レジスタ変数の取り回しがしやすくなる Register Projections

個人的には一番の目玉だと思っています。

1つのタスクで、レジスタ(register)変数を複数定義できる機能です。

ちょっと個別に扱いたかったので、別の記事にしました。

tekunabe.hatenablog.jp

暗黙的なタスクオブジェクト _task による register の省略

同じくレジスタ変数回りの機能追加です。

register を定義しなくても、タスク内であれば _task.result で参照できるようになったりします。

これも個別に扱いたかったので、先述の Register Projections の件と合わせて別の記事にしました。

tekunabe.hatenablog.jp

  • 関連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_tasksloop でループさせると、ループごとに included ~ が表示されノイジーになってしまうことがあります。 こんな課題を解決できるように、このログの表示、非表示を切り替えられるようになりました。

設定項目は、ansible.builtin.default コールバックプラグインの display_included_hosts です。デフォルトは true なので、つまり今までと同じ挙動です。

例えば ansible.cfgfalse を設定したい場合は以下のように指定します。

[defaults]
display_included_hosts = false

以下のようなタスクを用意します。ansible.builtin.include_tasksloop で回します。

(ちなみにループは 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

モジュール呼び出し時のパラメーターを 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_INVOCATIONtrue を指定して 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_formatinvocation に含まれています。

Playbook 上は変数による指定(dest: "{{ target_file }}")でも、ログ上は実際の値("dest": "output.txt")になります。そのため、デバッグしやすい形式になってるとも言えるかなと思います。

補足1: ansible.cfg で設定する場合の注意(ansible-core 2.21.0 時点)

2026/05/20 現在、devel 版ドキュメント上のINJECT_INVOCATIONに掲載されている ini (ansible.cfgのこと)で指定するキーが誤っています。interpreter_python となっていますが、正しくは inject_invocation のようなキーのはずです。

interpreter_pythonINTERPRETER_PYTHON というメジャーな設定項目のキーですでに使われています。ドキュメント上だけの不備ではなく実際の挙動にも影響しています。

すでに 修正PRが出されていますので、おそらく ansible-core 2.21.1 で修正されると思います。

補足2: invocation って前からあったような?

「あれ? そういえば前からログに invocation が含まれてる場合もあったような?」と思って ansible-core 2.20 系で確認しました。すると、ログに invocation が表示されるのは -vv を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'"}

ちょっと注意ポイント

デグレになるかもしれない少し注意が必要そうな変更点や削除された機能についてです。

Interpreter Discovery の legacy 系モードの削除

ターゲットノードの Python 環境を自動的に検出する Interpreter Discovery という機能があります。

検出モードがいくつかありますが、以下のモードが削除されました。

  • auto_legacy
  • auto_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"}

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.0requires_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_MISMATCHignore を指定します。

おわりに

2つ前の ansible-core 2.19 の時にはテンプレート処理の変更という、影響度高めの変更がありましたが、前回(2.20)と今回(2.21)はそこまで大きいものはなさそうです。

最初に挙げた「Register Projections」は Playbook がすっきり書けるようになって便利そうです。使えるタイミングがあれば使ってみたいと思います。

参考

github.com

tekunabe.hatenablog.jp