てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] ansible-builder はコレクションが依存しているパッケージもインストールしてくれる

はじめに

Ansible の Execution Environment(EE、Playbookを実行するコンテナイメージ)をビルドするツールとして、ansible-builderがあります。

正直、私はこれまで「Dockerfile / Containerfile を yaml で記述できるちょっとしたラッパーツール」くらいにしか思っていなかったのですが、最近 Ansible 関連のツールならではの機能があることを知りました。

それは、ビルドする定義ファイル execution-environment.yml には、インストールしたいコレクションも指定できるのですが、ansible-builder は、そのコレクションが依存している Python パッケージや、システムパッケージもセットでインストールしてくれる点です(除外あり)。

既知の方も多いと思いますが、私は最近気が付きました・・。

Ansible Builder combines all the Python requirements files from all collections into a single file

https://ansible.readthedocs.io/projects/builder/en/latest/collection_metadata/#python-dependencies から

Ansible Builder combines system package entries from multiple collections into a single file.

https://ansible.readthedocs.io/projects/builder/en/latest/collection_metadata/#system-level-dependencies から

it will install the collection inside the image, read requirements.txt inside of the collection

https://ansible.readthedocs.io/projects/builder/en/stable/usage/#examples から

この記事では、挙動を確認したときのことをまとめます。確認は、ビルドしてできたイメージ内のパッケージのインストール状態を確認するまでとします。

  • 検証環境
    • ansible-builder: 3.0.0
    • Docker: 20.10.25

[2023/12/10 追記] これに関するドキュメントの追記の PR がでていました。

github.com

試す定義ファイル

今回試す定義ファイル execution-environment.yml は以下のとおりです。

---
version: 3

images:
  base_image:
    name: registry.redhat.io/ansible-automation-platform-24/ee-minimal-rhel8:latest

dependencies:
  galaxy:
    collections:
      - ansible.posix
      - ansible.utils

options:
  package_manager_path: /usr/bin/microdnf

galaxy では、ansible.posixansible.utils コレクションを指定しています。

ansible.posix コレクションは、依存するシステムパッケージとして bindep.txtrsync が指定されています。ansible.posix.synchronizeが依存していためでしょう。なお、requirements.txt は空です。

もう一つの ansible.utils コレクションは、requirements.txtでは、以下の Python パッケージが指定されていました。

  • jsonschema
  • textfsm
  • ttp
  • xmltodict
  • netaddr

ビルド

今回はベースイメージに registry.redhat.io/ansible-automation-platform-24/ee-minimal-rhel8:latest を指定しているため、あらかじめログインが必要です。

docker registry.redhat.io

続いて ansible-builder でビルドします。ビルドの状態が分かるように -v 3 も付けます(以降、タグは仮です)。

ansible-builder build -t ghcr.io/akira6592/ee-dep -v 3

途中以下のようなログが表示されます。

Creating parent directory for /tmp/src/requirements.txt
---
python:
- 'jsonschema  # from collection ansible.utils'
- 'textfsm  # from collection ansible.utils'
- 'ttp  # from collection ansible.utils'
- 'xmltodict  # from collection ansible.utils'
- 'netaddr  # from collection ansible.utils'
system:
- 'rsync [platform:redhat]  # from collection ansible.posix'
- 'gcc-c++ [doc test platform:rpm]  # from collection ansible.utils'
- 'python3-devel [test platform:rpm]  # from collection ansible.utils'
- 'python3 [test platform:rpm]  # from collection ansible.utils'

定義ファイル内で指定したコレクションが依存している Python パッケージやシステムパッケージが表示されます。# from collection ansible.utils のように、どのコレクションが依存しているのかがコメントで併記されるのがわかりやすいです。

後述もしますが、システムパッケージのほうは doctest のように何かしらのプロファイルが指定されているパッケージは実際にはインストールされません。

今回は指定していませんが、定義ファイル内の pythonsystem で明示的にパッケージを指定した場合は、組み合わせたものが表示されます。明示的に指定した場合は、先コメントの部分は # from collection user と表示されます。

なお、ビルドすると(正確には ansible-builder create でも)context/_build/ ディレクトリ配下に requirements.txtbindep.txt が生成されることがありますが、これらは定義ファイル内で明示的に指定したものです。

以下のように、インストールしている様子も見れました。

Installing:                                              
 rsync-3.1.3-19.el8_7.1.x86_64 ubi-8-baseos-rpms 420.2 kB
Transaction Summary:
 Installing:        1 packages
 Reinstalling:      0 packages
 Upgrading:         0 packages
 Obsoleting:        0 packages
 Removing:          0 packages
 Downgrading:       0 packages
Downloading packages...
Running transaction test...
Installing: rsync;3.1.3-19.el8_7.1;x86_64;ubi-8-baseos-rpms
Complete.
...(略)...
Requirement already satisfied: jsonschema in /usr/lib/python3.9/site-packages (from -r /output/requirements.txt (line 1)) (4.16.0)
Collecting textfsm
  Using cached textfsm-1.1.3-py2.py3-none-any.whl (44 kB)
Collecting ttp
  Using cached ttp-0.9.5-py2.py3-none-any.whl (85 kB)
Requirement already satisfied: xmltodict in /usr/local/lib/python3.9/site-packages (from -r /output/requirements.txt (line 4)) (0.12.0)
Collecting netaddr
  Using cached netaddr-0.8.0-py2.py3-none-any.whl (1.9 MB)
Requirement already satisfied: attrs>=17.4.0 in /usr/lib/python3.9/site-packages (from jsonschema->-r /output/requirements.txt (line 1)) (21.4.0)
Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /usr/lib64/python3.9/site-packages (from jsonschema->-r /output/requirements.txt (line 1)) (0.18.1)
Requirement already satisfied: six in /usr/lib/python3.9/site-packages (from textfsm->-r /output/requirements.txt (line 2)) (1.16.0)
Requirement already satisfied: future in /usr/lib/python3.9/site-packages (from textfsm->-r /output/requirements.txt (line 2)) (0.18.3)
Installing collected packages: textfsm, ttp, netaddr
Successfully installed netaddr-0.8.0 textfsm-1.1.3 ttp-0.9.5
...(略)...

確認

ビルドしたイメージの中を確認します。

システムパッケージの確認

まず、念のためコレクションのインストール状況を確認します。

$ docker run -it ghcr.io/akira6592/ee-dep ansible-galaxy collection list

# /usr/share/ansible/collections/ansible_collections
Collection    Version
------------- -------
ansible.posix 1.5.4  
ansible.utils 2.10.3 

想定どおりです。今回指定したベースイメージには ansible.builtin 以外にはなかった状態なので、純粋にこの2つが追加された状態です。

Python パッケージの確認

次に Python のパッケージの確認です。

$ docker run -it ghcr.io/akira6592/ee-dep pip3 list
Package             Version
------------------- --------
ansible-compat      3.0.2
ansible-core        2.15.0
ansible-lint        6.14.3
ansible-runner      2.3.2
...(略)...
jsonschema          4.16.0    # ansible.utils 依存
lockfile            0.12.2
lxml                4.6.5
MarkupSafe          2.1.0
mypy-extensions     0.4.3
ncclient            0.6.10
netaddr             0.8.0     # ansible.utils 依存
...(略)...
textfsm             1.1.3     # ansible.utils 依存
toml                0.10.2
tomli               2.0.1
ttp                 0.9.5     # ansible.utils 依存
typing-extensions   3.10.0.2
urllib3             1.25.10
wcmatch             8.3
xmltodict           0.12.0    # ansible.utils 依存
yamllint            1.30.0

ansible.utils コレクションの requirements.txt で指定されていたパケージがあることが分かります。

ベースイメージとの比較はこちら(*.before がベース、*.after が今回のビルド分)。

$ diff pip.before pip.after 
29a30
> netaddr             0.8.0
61a63
> textfsm             1.1.3
63a66
> ttp                 0.9.5

diff に表示されない jsonschemaxmltodict はベースイメージにもともと入っていたということも分かります。

システムパッケージの確認

続いて、システムパッケージを確認します。

$ docker run -it ghcr.io/akira6592/ee-dep rpm -qa | sort
...(略)...
rpm-4.14.3-26.el8.x86_64
rpm-libs-4.14.3-26.el8.x86_64
rsync-3.1.3-19.el8_7.1.x86_64     # ansible.posix 依存
sed-4.5-5.el8.x86_64
setup-2.12.2-9.el8.noarch
...(略)...

ansible.posix コレクションの bindep.txt で指定されていた rsync がることことがわかります。

ベースイメージとの比較はこちら(*.before がベース、*.after が今回のビルド分)。

$ diff rpm.before rpm.after 
175a176
> rsync-3.1.3-19.el8_7.1.x86_64

なお、ansible.utils コレクションには bindep.txt の定義もありますが、testdoc プロファイルが指定されます。ansible-builder の仕様としてはプロファイルが指定されていないパッケージがインストール対象)なので、特にインストールされません。

Only requirements with no profiles (runtime requirements) are installed to the image.

https://ansible.readthedocs.io/projects/builder/en/3.0.0/collection_metadata/#system-level-dependencies から

ちなみに、ここまでログの貼りやすさから docker run コマンドを利用していますが、ansible-navigator images コマンド経由でもインストールされたパッケージは確認できます。

補足

試したり調べたりしたときわかったことを、3点補足します。

注意点: 依存パッケージを読み込んでくれないケース

paloaltonetworks.panos コレクションをインストールしたときに気が付いたのですが、requiremsnts.txt\ で折り返されていると、ansible-builder 側で正しく読み込んでくれないようです。

例: https://github.com/PaloAltoNetworks/pan-os-ansible/blob/v2.17.3/requirements.txt

本事象含めたパース失敗の事象は、ansible-builder 3.0.0 時点では Warning レベルのですが、Error レベルに修正する PR も出ています。

また、そもそも、ドキュメントには依存パッケージが明記されているが、requiremsnts.txtbindep.txt がないケースももちろん自動では読み込んでくれません。

requirements.txt というファイル名について

ここまで、便宜上、 requirements.txt というファイル名を利用してきましたが、コレクションの meta/execution-environment.ymldependencies.python にファイル名が指定されていてファイルが存在する場合は、そのファイルを読み込む仕様になっています。

たとえば、azure.azcollection コレクション 1.16.0 には、requirements.txt というファイル名のファイルがありません。あるのは requirements-azure.txt です。meta/execution-environment.ymldependencies.pythonrequirements-azure.txt と指定されているため、 ansible-builder は requirements-azure.txt を読んでくれます。

依存パッケージの一覧確認

コレクションが依存している各パッケージは ansible-builder introspect コレクションを束ねるディレクトリ コマンドで確認できます。すでにコレクションがインストールされている状態が前提です。

たとえば、

~/.ansible/collections
└── ansible_collections
    ├── ansible
    │   └── utils   # ansible.utils コレクションのディレクトリ

上記のようなディレクトリ構成の場合は、以下のように実行します。

実行例:

$ ansible-builder introspect ~/.ansible/collections/
# Dependency data for /Users/sakana/.ansible/collections/
---
python:
  ansible.eda:
  - azure-servicebus
  - aiobotocore
  - aiohttp
  - aiokafka
  - watchdog
  - systemd-python
  - dpath
...(略)...
system:
  ansible.netcommon:
  - gcc-c++ [doc test platform:rpm]
  - libyaml-devel [test platform:rpm]
  - libyaml-dev [test platform:dpkg]
  - python3-devel [test platform:rpm]
  - python3 [test platform:rpm]
  - gcc [compile platform:rpm]
  - libssh-dev [compile platform:dpkg]
...(略)...

簡単なジョブテンプレートの実行

ansible.builtin.debug モジュールしか使ってないのであまり意味はないですが、今回ビルドして EE で、Automation Controller 4.4 にデフォルトで入っている Demo Job Template を実行できるところまでは確認しました。

Demo Job Template の実行

おわりに

ansible-builder で、コレクションが依存しているパッケージをセットでインストールしてくれることを試しました。

当たり前かもしれませんが、ある程度使ったり調べたりしないと、便利さに気づけないものだなと思いました。

あと、公式ドキュメントちゃんと読んでおけば分かっていた話でしたね。