目次
はじめに
Ansible Automation Platform 2.0 がアーリーアクセスで提供されはじめ、次期メジャリリースの情報が出てきました。
- Ansible Automation Platform 2.0 Early Access Homepage – Red Hat Customer Portal
- Introducing Ansible Automation Platform 2.0 Early Access – Red Hat Customer Portal
- What’s New in Ansible Automation Controller 4.0 – Red Hat Customer Portal
- What’s New in Private Automation Hub – Red Hat Customer Portal
目立つところでは、Ansible Tower が Ansible Automation Controller に改名されていますが、アーキテクチャ面でも、制御プレーンと実行プレーンを疎結合にするために Execution Environment(EE)の概念が新たに登場しています。
従来、プレイブックに応じて Python のモジュールや Collection を使い分けたい場合、典型的には Python の仮想環境を用いた環境の分離を行っていました。Execution Environment(EE)は、平たくいえばこれを コンテナに置き換えるもの であり、Ansible のランタイムをコンテナ化したもの と言えそうです。必ずしも Tower(AWX)と組み合わせなくても使えますが、Tower(AWX)目線でも、本体のインスタンスと実行環境が分離されるので、スケールもしやすくなりそうです。
本エントリでは、AWX での Execution Environment(EE)の動きを確かめるための準備として、Ansible Runner と Ansible Builder の動作を確認します。次のエントリ では、本エントリで作成した自前の Execution Environment(EE)を実際に AWX から利用します。
前提とする環境
本エントリでは、次の環境を前提に書いています。
- CentOS 8.2
- Python 3.9.2
- Docker 20.10.7
ざっくりアーキテクチャ
Execution Environment の周辺には、Ansible Runner と Ansible Builder が登場します。荒く整理すると、たぶんこんな感じです。
- Execution Environment
- Ansible のランタイムを含んだ、Red Hat UBI(OSS 版は CentOS)ベースの コンテナイメージ
- 追加したい Collection などの情報をテキストファイル群で定義して Ansible Builder でビルドすることで、独自の環境を作成できる
- コンテナレジストリで Execution Environment を公開することで、他の環境で容易に再利用でき、環境間での動作の一貫性が得られるほか、スケールもしやすくなる
- Ansible Builder
- Execution Environment をビルドするツール
- 雑に言うと、設定ファイル を基に Dockerfile を自動生成 して
podman build
(docker build
)を実行してくれるツール - ベースイメージを指定すると、Ansible のバージョンを(ある程度)選べる
- 任意の Galaxy の Collection や Role、Python モジュール(pip)、OS パッケージ(RPM)を含められる
- ビルド中に任意の処理を追加できる(Dockerfile に
RUN
を追記できるイメージ)
- Ansible Runner
- プレイブックを Execution Environment 上で実行するためのツール
- Execution Environment を使わずに
ansible-playbook
のラッパとしても利用できる
ビルドした Execution Environment はいわゆるコンテナイメージなので、任意のコンテナレジストリにプッシュできます。Ansible はコンテナの中で動くことになるため、従来のような Python の仮想環境の考慮が不要になるほか、環境の移植性(動作の一貫性)も高められ、またスケールもしやすくなります。
なお、コンテナイメージとしての Execution Environment を管理する目的で、Private Automation Hub がコンテナレジストリとしての役割も担えるようになるようですが、本エントリでは割愛します。
Ansible Builder を使う
だいたいの情報は 公式のドキュメント にまとまっています。
本エントリで使っているファイル群の実物は、GitHub に配置済み です。
インストール
ansible-builder
だけあればよく、ansible
はなくても動きます。
python3 -m pip install ansible-builder
必要なファイルの用意
ディレクトリをひとつ作って、execution-environment.yml
としてメインの構成定義ファイルを作成します。
mkdir builder
cd builder
cat <<EOF > execution-environment.yml
---
version: 1
build_arg_defaults:
EE_BASE_IMAGE: quay.io/ansible/ansible-runner:stable-2.10-devel
ansible_config: ansible.cfg
dependencies:
galaxy: requirements.yml
python: requirements.txt
system: bindep.txt
additional_build_steps:
prepend:
- RUN whoami
- RUN cat /etc/os-release
append:
- RUN echo This is a post-install command!
- RUN ls -la /etc
EOF
ベースイメージは、無指定の場合はデフォルトで quay.io/ansible/ansible-runner:latest
になるようで、現時点では latest
は stable-2.11-devel
と同義です。選択肢は quay.io/ansible/ansible-runner
のタグ一覧ページ で確認できますが、2.9
や 2.10
も用意されていて、上記例では違いを分かりやすくするため 2.10
を指定しています。利用する Ansible のバージョンを変更したい場合は、ベースイメージを差し替えるのが無難そうですね。
ほか、ansible.cfg
や requirements.yml
、requirements.txt
、bindep.txt
は必要に応じて指定します。これらは各ツールにそのまま渡されるため、中の書式はそれぞれの標準に従います。ここでは省略しますが、実際の例を GitHub に配置済み です。
additional_build_steps
では、コンテナイメージのビルドプロセス中に任意の処理を追加できます。具体的には、生成される Dockerfile
の所定の位置(pip install
と dnf install
の前後)に任意の行を追記できます。追記される位置は実際に生成された Dockerfile
(後述)の中身を見るのが確実です。
カスタマイズ済みの設定ファイルの例として、AWX に組み込まれている Execution Environment のソースである ansible/awx-ee
も参考にできます。レイヤが重なりすぎるうえに副作用が予想できないのでおすすめはしませんが、自前でビルドするためのベースイメージとしても ビルド済みのイメージ quay.io/ansible/awx-ee
も利用できそうでした。
Execution Environment のビルド
必要なファイルが用意できたら、ansible-builder build
でビルドできます。引数で、できあがったイメージに付与するタグと、ビルドに利用するコンテナランタイム(デフォルトは podman
で、今回は docker
)を指定します。--verbosity
(-v
)は任意ですが、無いとビルドの過程が表示されないようです。
$ ansible-builder build --tag registry.example.com/ansible/ee:2.10-custom --container-runtime docker --verbosity 3
Ansible Builder is building your execution environment image, "registry.example.com/ansible/ee:2.10-custom".
File context/_build/requirements.yml will be created.
File context/_build/requirements.txt will be created.
File context/_build/bindep.txt will be created.
File context/_build/ansible.cfg will be created.
Rewriting Containerfile to capture collection requirements
Running command:
docker build -f context/Dockerfile -t registry.example.com/ansible/ee:2.10-custom context
Sending build context to Docker daemon 7.68kB
Step 1/25 : ARG EE_BASE_IMAGE=quay.io/ansible/ansible-runner:stable-2.10-devel
Step 2/25 : ARG EE_BUILDER_IMAGE=quay.io/ansible/ansible-builder:latest
Step 3/25 : FROM $EE_BASE_IMAGE as galaxy
...
Removing intermediate container a083001a665a
---> 050cf7076379
Successfully built 050cf7076379
Successfully tagged registry.example.com/ansible/ee:2.10-custom
Complete! The build context can be found at: /home/********/awx-on-k3s/builder/context
できた Execution Environment は通常のコンテナイメージなので、docker image ls
で確認できます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.example.com/ansible/ee 2.10-custom 6fb343319a80 2 minutes ago 871MB
このあと手作業で Ansible Runner から使うだけであれば、Execution Environment はここまでで完成です。
もしこの Execution Environment を AWX や外部のシステムから利用する要件がある場合は、コンテナレジストリにプッシュが必要です。今回は GitHub に配置済みのファイル を使って K3s 上にプライベートコンテナレジストリを作成しているので、このままプッシュしました。
$ docker push registry.example.com/ansible/ee:2.10-custom
The push refers to repository [registry.example.com/ansible/ee]
...
2.10-custom: digest: sha256:0138445c58253c733f2e255b618469d9f61337901c13e3be6412984fd835ad55 size: 3880
Dockerfile
の確認
ビルドの過程で生成された Dockerfile
は、カレントディレクトリの context
ディレクトリに保存されています。additional_build_steps
で指定したコマンドの追加されっぷりなどは、実物を見た方が確実です。
$ cat context/Dockerfile
ARG EE_BASE_IMAGE=quay.io/ansible/ansible-runner:stable-2.10-devel
ARG EE_BUILDER_IMAGE=quay.io/ansible/ansible-builder:latest
FROM $EE_BASE_IMAGE as galaxy
ARG ANSIBLE_GALAXY_CLI_COLLECTION_OPTS=
USER root
...
なお、イメージのビルドは行わずに Dockerfile
の生成だけを行いたい場合は、ansible-builder create
コマンドが利用できます。
$ ansible-builder create --verbosity 3
Ansible Builder is generating your execution environment build context.
File context/_build/requirements.yml will be created.
File context/_build/requirements.txt will be created.
File context/_build/bindep.txt will be created.
File context/_build/ansible.cfg will be created.
Rewriting Containerfile to capture collection requirements
Complete! The build context can be found at: /home/********/awx-on-k3s/builder/context
Ansible Runner を使う
これもだいたいの情報は 公式のドキュメント にまとまっています。
本エントリで使っているファイル群の実物は、GitHub に配置済み です。
インストール
Execution Environment を利用する(≒ Ansible をコンテナで動かす)だけであれば、ansible-runner
だけあればよく、ansible
はなくても動きます。ansible-playbook
コマンドのラッパとして利用する場合は、当然ながら ansible
も必要です。
python3 -m pip install ansible-runner
プレイブックの用意
ディレクトリをひとつ作って、必要なファイルを配置していきます。実物は GitHub に配置済み ですが、ここではサンプルとして Ansible の実行環境の構成が判断しやすいプレイブックにしています。
プレイブックは project
ディレクトリに配置します。
mkdir -p runner/project
cat <<EOF > runner/project/demo.yml
---
- hosts: localhost
connection: local
tasks:
- name: Ensure that the host is reachable
ansible.builtin.ping:
- name: Print variables for debugging
ansible.builtin.debug:
var: data
vars:
data:
ansible_playbook_python: "{{ ansible_playbook_python }}"
ansible_python_version: "{{ ansible_python_version }}"
ansible_python.executable: "{{ ansible_python.executable }}"
ansible_version.full: "{{ ansible_version.full }}"
- name: Invoke debug commands
ansible.builtin.command: "{{ item }}"
changed_when: false
failed_when: false
loop:
- hostname
- whoami
- pwd
- python3 -m pip list
- ansible-galaxy collection list -p .
register: command_results
- ansible.builtin.debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ command_results.results }}"
loop_control:
label: "{{ item.cmd }}"
EOF
ansible-playbook
のラッパとして使う
Ansible Runner は、ローカルの ansible-playbook
のラッパとしても利用できます。素の状態(引数で設定を与えず、設定ファイルも用意しなかった場合)ではこの動作です。この場合、Execution Environment の概念はまったく登場しません。
$ ansible-runner run runner -p demo.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [localhost] **************************************************************
...
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
このパタンでは、ansible-playbook
コマンドを手打ちする状態とほとんど変わらず、localhost
は ansible-runner
を実行したホストそれ自体 を指します。つまり、従来通りの意味の localhost
で、何ら特別なことはなく、出力された hostname
、whoami
、pip list
、ansible-galaxy collection list
などの実行結果は、見慣れた自ホストのものになるはずです。サンプルには含めていませんが、localhost
に対する ansible.builtin.yum
や ansible.builtin.copy
なども、意図通りに自ホストに対して 実行されます。
なお、実行結果のログ や 実行されたコマンド は、自動で artifacts
ディレクトリに保存 されます。何もせずとも相当詳細に残るので、これまで ansible.cfg
などでいろいろしないといけなかったログ取得はあまり気にしなくてもよくなりそうです。ただし、何もしないと自動で削除はされずに増え続けるので、適宜 --rotate-artifacts 10
など保持世代数を指定して掃除する必要があります。
プレイブックを Execution Environment で実行する
ansible-runner
に --process-isolation
を引数で与えるか、設定ファイル env/settings
で process_isolation
を true
にして、--process-isolation-executable
(process_isolation_executable
)で docker
(または podman
)を指定すると、Execution Environment を使ってコンテナ上でプレイブックを実行できます。
mkdir runner/env
cat <<EOF > runner/env/settings
---
process_isolation: true
process_isolation_executable: docker
container_image: registry.example.com/ansible/ee:2.10-custom
EOF
なお、--process-isolation
(process_isolation
)を有効にして --process-isolation-executable
(process_isolation_executable
)を指定しないと、デフォルトでは bwrap
でのサンドボックス化が行われますが、個人的に bwrap
になじみがないので割愛しています。
Execution Environment にはデフォルトで quay.io/ansible/ansible-runner:devel
が利用されますが、引数 --container-image
や設定 container_image
で任意のものを指定できます。ここでは先の Ansible Builder の手順で自製した registry.example.com/ansible/ee:2.10-custom
の利用を指定しています。
設定ファイルを用意したうえで ansible-runner
を実行すると、指定した Execution Environment を使ってプレイブックが実行されます。この場合も 実行結果のログ や 実行されたコマンド は、自動で artifacts
ディレクトリに保存 されます。
$ ansible-runner run runner -p demo.yml
...
PLAY [localhost] ***************************************************************
...
ok: [localhost] => {
"data": {
...
"ansible_version.full": "2.10.12rc1.post0"
}
}
ok: [localhost] => (item=['python3', '-m', 'pip', 'list']) => {
"msg": [
"Package Version",
"-------------------- ----------------",
...
"example-pypi-package 0.1.0",
...
ok: [localhost] => (item=['ansible-galaxy', 'collection', 'list', '-p', '.']) => {
"msg": [
"",
"# /usr/share/ansible/collections/ansible_collections",
"Collection Version",
"----------------- -------",
"community.general 3.3.2 "
]
}
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
出力からは、以下のような動作が確認できるはずです。
- Ansible のバージョンが、ビルド時に指定したベースイメージ(今回は
2.10
系)のものになっていること pip list
やansible-galaxy collection list
の結果に、ビルド時に指定したモノが含まれること
Execution Environment の切り替えは設定ファイルやコマンドライン引数で簡単に行えるため、紹介した例以外でもいろいろ試すと、指定した Execution Environment に応じた Ansible のバージョンやパッケージの有無の変化が確認できます。自製した Execution Environment だけでなく、今回のサンプルのように標準モジュール群だけで動くシンプルなプレイブックであれば、 ビルド用のベースイメージである quay.io/ansible/ansible-runner
もそのまま Execution Environment として利用できるので、実際に試してみると楽しいですね。より汎用性の高い既成 Execution Environment としては、AWX に組み込まれている quay.io/ansible/awx-ee
(パブリッククラウド関係のコレクションなどが導入済み)も利用できます。
コンテナの作られっぷりと connection: local
の扱い
ところで、Execution Environment を使った場合、localhost
は、自ホストではなくコンテナ自体を指す ことに注意が必要です。実際、プレイブックの出力結果から、ホスト名(hostname
)や実行ユーザ(whoami
)が見慣れないものに替わっていることが確認できるはずです。
上記の例では、Ansible は Docker のコンテナとして動作します。確認のため、プレイブックに ansible.builtin.pause
を追加し、再度同様に ansible-runner
を実行します(余談ですが、ansible.builtin.pause
を使った入力は Ansible Runner 経由ではうまく動かないようです……)。
- name: Simply pause for a while
ansible.builtin.pause:
minutes: 10
待ち時間中に、実際のコンテナの存在が確認できます。
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f0a7a89e5b28 registry.example.com/ansible/ee:2.10-custom "entrypoint ansible-…" 11 seconds ago Up 11 seconds ansible_runner_9bbedba8-520a-4392-9db9-16f76aa597de
このコンテナを inspect
すると、ansible-runner
の実行時に指定したディレクトリが /runner
にマウントされていることがわかります。
$ docker inspect ansible_runner_9bbedba8-520a-4392-9db9-16f76aa597de | jq .[0].Mounts
[
{
"Type": "bind",
"Source": "/home/********/awx-on-k3s/runner",
"Destination": "/runner",
"Mode": "Z",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "bind",
"Source": "/etc/sample",
"Destination": "/etc/sample",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
逆にいえば、コンテナとして動作しているので、マウントされた /runner
以外 の ホスト側のファイルや状態は Ansible からは不可視 です。
このため、connection: local
で localhost
に作用するプレイブック は、/runner
下以外は コンテナに閉じた揮発的な作用 にしかならず、意図した結果が得られない 点には注意が必要です。
この点は GitHub の Issue でも課題提起がされていて、今後動作が変わる可能性がありますが、現時点では、従来の意味での localhost
に作用させたい場合、コンテナの中から自ホストに SSH させる 必要があります。
なお、次の設定を加えると、コンテナへのバインドマウントを追加でき、/runner
以外のパスも Ansible から作用させられるようになりますが、この設定はドキュメントに記載がない ため、将来的な取り扱いは不明です。
container_volume_mounts:
- /etc/sample:/etc/sample
逆に、ドキュメントには process_isolation_show_paths
の記載がありますが、ソースコードを見た範囲では、bwrap
専用のようで、Docker や Podman では使えませんでした。
おわりに
AWX で EE を使うために、前段として Ansible Builder と Ansible Runner の動作を確認しました。
これまでコンテナでの動作は積極的にはサポートされていませんでしたが、大手を振って使えるようになるわけで、たいへんうれしい進化です。環境の切り替えが用意になっただけでなく、複数の環境での状態の再現が容易になり、可搬性や動作の一貫性も保証しやすくなりました。これからは Playbook は生で叩かずに、できるだけ EE で叩いていきたいですね。
次のエントリ では、実際に AWX で EE を利用します。