ansible-builderとansible-runnerを試してみた
はじめに
anible-runnerとansible-builderは名前こそ聞いたことがあったが触ったことがありませんでした。
Pythonのスクリプトからansibleを動かしたいなと考えたときに、ansible-runnerが使えるという情報をいただいたので入門してみました。
まずは手始めにansible-builderで作成したコンテナをansible-runnerで動かすのをやってみようと思います。
ansible-builder
Ansible BuilderはAnsibleの実行環境をコンテナで用意するためのPythonモジュール
ansible-runner
Ansible Runnerは、コンテナを介してPlaybookを起動したり Pythonスクリプトなどに組み込むことで直接Ansibleの起動が可能になるPythonモジュール
前提条件
コンテナの実行環境を事前に準備しておく必要があります
- Docker
- Podman
私は事前にDocekrをインストールしておきました。
インストール
ansible-builder/ansible-runnerはpipでインストールが可能です。
$ pip install ansible-builder
$ pip install ansible-runner
Anibleのインストールは不要です
$ pip freeze | grep ansible ansible-builder==1.0.1 ansible-runner==2.0.1
Build環境の準備
まず、初めにansible-builderを使って実行環境のコンテナ(execution-environment)の設定を定義したファイルを作成します
execution-environment.yml
--- version: 1.1 dependencies: galaxy: requirements.yml python: requirements.txt additional_build_steps: prepend: | RUN pip3 install --upgrade pip setuptools append: - RUN ls -la /etc
dependenciesのgalaxy/pythonで定義したrequirementsファイルを作成していきます
additional_build_stepsでBuild時に実施するコマンドを定義できます
(ここでansibleを削除し、任意のVersionをインストールしようとしましたが失敗してしまいました、、本筋ではないので省略します)
pythonのライブラリをインストールするためにrequirements.txtを作成する(中身は適当です)
requirements.txt.yml
paramiko jmespath netaddr
インストールするCollectionなどを記載するrequirements.ymlを作成します
requirements.yml
--- collections: - name: ansible.netcommon - name: ansible.utils
Build
事前のDocker Imagesを確認
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE
Build(数分かかります)
$ ansible-builder build --tag ee001 Running command: docker build -f context/Dockerfile -t ee001 context Complete! The build context can be found at: /home/xx/xx/context
オプション--verbosity
を利用することでログを表示が可能です
また、コマンド実行時にcontext
ディレクトが作成されDockerfileやrequirements.txtなどが格納されます
--context
を指定することで任意のPATHに作成が可能です
--build-arg EE_BASE_IMAGE
オプションを指定するとpullするイメージファイルを選べるようです
※ただし、初めからansibleが入っているimageじゃないと動かなさそう・・
オプション付きBuild
$ ansible-builder build --tag ee002 --verbosity 3 --context tmp Ansible Builder is building your execution environment image, "ee002". File tmp/_build/requirements.yml will be created. File tmp/_build/requirements.txt will be created. File tmp/_build/ansible.cfg will be created. Rewriting Containerfile to capture collection requirements Running command: docker build -f tmp/Dockerfile -t ee002 tmp Sending build context to Docker daemon 6.656kB Step 1/22 : ARG EE_BASE_IMAGE=quay.io/ansible/ansible-runner:latest Step 2/22 : ARG EE_BUILDER_IMAGE=quay.io/ansible/ansible-builder:latest Step 3/22 : FROM $EE_BASE_IMAGE as galaxy ---> 7f28c304a37a Step 4/22 : ARG ANSIBLE_GALAXY_CLI_COLLECTION_OPTS= ---> Using cache ---> 817d228223c7 Step 5/22 : USER root ---> Using cache ---> abe073792147 Step 6/22 : ADD _build/ansible.cfg ~/.ansible.cfg ---> Using cache ---> 86f235ef5837 Step 7/22 : ADD _build /build ---> Using cache ---> 1d4ae1669f34 Step 8/22 : WORKDIR /build ---> Using cache ---> 3222fad0e2e6 Step 9/22 : RUN ansible-galaxy role install -r requirements.yml --roles-path /usr/share/ansible/roles ---> Using cache ---> c43f05572389 Step 10/22 : RUN ansible-galaxy collection install $ANSIBLE_GALAXY_CLI_COLLECTION_OPTS -r requirements.yml --collections-path /usr/share/ansible/collections ---> Using cache ---> 714d46cd3e58 Step 11/22 : FROM $EE_BUILDER_IMAGE as builder ---> 5806c16c9ae2 Step 12/22 : COPY --from=galaxy /usr/share/ansible /usr/share/ansible ---> Using cache ---> 6348ad77818a Step 13/22 : ADD _build/requirements.txt requirements.txt ---> Using cache ---> 555ce006fb1c Step 14/22 : RUN ansible-builder introspect --sanitize --user-pip=requirements.txt --write-bindep=/tmp/src/bindep.txt --write-pip=/tmp/src/requirements.txt ---> Using cache ---> 29d5ad112a18 Step 15/22 : RUN assemble ---> Using cache ---> 2bcba19d4f70 Step 16/22 : FROM $EE_BASE_IMAGE ---> 7f28c304a37a Step 17/22 : USER root ---> Using cache ---> a8ea1ddb1c81 Step 18/22 : RUN pip3 install --upgrade pip setuptools ---> Using cache ---> 342d9b393f12 Step 19/22 : COPY --from=galaxy /usr/share/ansible /usr/share/ansible ---> Using cache ---> 5a355f923a51 Step 20/22 : COPY --from=builder /output/ /output/ ---> Using cache ---> 2f14d46eac3e Step 21/22 : RUN /output/install-from-bindep && rm -rf /output/wheels ---> Using cache ---> b86981d1e55f Step 22/22 : RUN ls -la /etc ---> Using cache ---> e94c237fcda9 Successfully built e94c237fcda9 Successfully tagged ee002:latest Complete! The build context can be found at: /home/xx/xx/tmp
tmpにDokcerfileなどが格納されます
imagesを確認すると、指定したtagでimageが作成されたことが確認できます
(ついでにいろいろ増えているのはよくわからないですが、、、)
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ee001 latest e94c237fcda9 4 minutes ago 741MB ee002 latest e94c237fcda9 4 minutes ago 741MB <none> <none> 2bcba19d4f70 4 minutes ago 779MB <none> <none> 714d46cd3e58 6 minutes ago 708MB quay.io/ansible/ansible-runner latest 7f28c304a37a 8 hours ago 703MB quay.io/ansible/ansible-builder latest 5806c16c9ae2 8 hours ago 611MB
runner環境準備
imageができたのでansible-runnerを動かしてみます
ansible-runnerは利用前に下記2個のディレクトリを作成します
- env: runnerの実行のために必要な設定を格納する
- project: playbookやinventoryを格納する
ansible-runnerを実行時には/projectsの配下を見に行くようで、projectsディレクトリを作成しその配下にPlaybookを配置する必要があるようです
xxx ├── env │ └── settings └── project └── playbooks └── sample001.yml
env/settingsで実行対象のimagesファイルの指定などを行っています
process_isolation
はプレイブックを実行するファイルシステム上のどのディレクトリにアクセスできるかを制限するものらしいです
(Towerでいうところの分離されたジョブってところがイメージ近いかな?)
container_image: ee001 <--ここでBuilderで作成したImageを指定 process_isolation_executable: docker process_isolation: true
ここで実行対象のコンテナイメージを指定しなかった場合quay.io/ansible/ansible-runner:deve
というimageが自動的にダウンロードされ実行されます
runner実行時に--container-image
オプションを使っても指定可能です。
settingに対象イメージを書いていると--container-image
オプションよりも優先されるような動きをしました
用意したPlaybookはLocalhostに対してコマンドを実行するシンプルなものです
(おまけで最後にcollectionsで追加したnetcommonが使えるか確認しています)
sample001.yml
--- - hosts: localhost connection: local gather_facts: false tasks: - name: send command command: ansible --version register: res_version - name: debug version debug: msg: "{{ res_version.stdout_lines }}" - name: send command command: ansible-galaxy collection list register: res_collections - name: debug version debug: msg: "{{ res_collections.stdout_lines }}" - name: send command command: pip freeze register: res_pip_freeze - name: debug version debug: msg: "{{ res_pip_freeze.stdout_lines }}" - name: debug ip debug: msg: "{{ ip | ansible.netcommon.ipv4 }}" vars: ip: '192.168.1.1/32'
runner実行
それでは実行をしてみます
ansible-runner run
コマンドで実行します
runの後はprojectが格納されているディレクトリを指定し、-p
でplaybookを指定します
runnerはprojectディレクトリにあるファイルを参照するので、少し注意が必要です。
ここで-p project/playbooks/sample001.yml
のようにパスを指定してしまうと失敗してしまいます。
$ ansible-runner run . -p playbooks/sample001.yml [WARNING]: Unable to parse /runner/inventory/hosts as an inventory source [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] *************************************************************** TASK [send command1] *********************************************************** changed: [localhost] TASK [debug version] *********************************************************** ok: [localhost] => { "msg": [ "ansible [core 2.11.3rc1.post0] ", " config file = None", " configured module search path = ['/home/runner/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']", " ansible python module location = /usr/local/lib/python3.8/site-packages/ansible", " ansible collection location = /home/runner/.ansible/collections:/usr/share/ansible/collections", " executable location = /usr/local/bin/ansible", " python version = 3.8.6 (default, Jan 29 2021, 17:38:16) [GCC 8.4.1 20200928 (Red Hat 8.4.1-1)]", " jinja version = 2.10.3", " libyaml = True" ] } TASK [send command2] *********************************************************** changed: [localhost] TASK [debug collection] ******************************************************** ok: [localhost] => { "msg": [ "", "# /usr/share/ansible/collections/ansible_collections", "Collection Version", "----------------- -------", "ansible.netcommon 2.2.0 ", "ansible.utils 2.3.0 " ] } TASK [send command3] *********************************************************** changed: [localhost] TASK [debug pip freeze] ******************************************************** ok: [localhost] => { "msg": [ "ansible-core @ file:///output/wheels/ansible_core-2.11.3rc1.post0-py3-none-any.whl", "ansible-pylibssh==0.2.0", "ansible-runner @ file:///output/wheels/ansible_runner-2.0.0.0a4.dev61-py3-none-any.whl", "asn1crypto==1.2.0", "attrs==21.2.0", "Babel==2.7.0", "bcrypt==3.2.0", "cffi==1.13.2", "chardet==3.0.4", "cryptography==2.8", "decorator==5.0.9", "docutils==0.17.1", "dumb-init==1.2.5", "future==0.18.2", "gssapi==1.6.14", "idna==2.8", "Jinja2==2.10.3", "jmespath==0.10.0", "jsonschema==3.2.0", "jxmlease==1.0.3", "lockfile==0.12.2", "lxml==4.4.1", "MarkupSafe==1.1.1", "ncclient==0.6.12", "netaddr==0.8.0", "ntlm-auth==1.5.0", "packaging==21.0", "paramiko==2.7.2", "pexpect==4.8.0", "ply==3.11", "ptyprocess==0.7.0", "pyasn1==0.4.8", "pycparser==2.19", "pykerberos==1.2.1", "PyNaCl==1.4.0", "pyOpenSSL==19.1.0", "pyparsing==2.4.7", "pypsrp==0.5.0", "pyrsistent==0.18.0", "PySocks==1.7.1", "pyspnego==0.1.6", "python-daemon==2.3.0", "pytz==2019.3", "pywinrm==0.4.2", "PyYAML==5.4.1", "requests==2.22.0", "requests-credssp==1.2.0", "requests-ntlm==1.1.0", "resolvelib==0.5.4", "six==1.12.0", "textfsm==1.1.2", "toml==0.10.2", "ttp==0.7.1", "urllib3==1.25.7", "xmltodict==0.12.0" ] } TASK [debug ip] **************************************************************** ok: [localhost] => { "msg": "192.168.1.1/32" } PLAY RECAP ********************************************************************* localhost : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
正常に完了しました
完了すると、artifacts
ディレクトリが生成され、その中に実行結果やjob_eventが表示されます
stdoutを見るとAnsibleで実行した結果が見れるのですが、色コード?のようなものが入ってしまって見にくいです。。
まとめ
ansible-builderとansible-runnerに入門してみました
コンテナイメージを作成しておいて、実行時だけ起動するというのはとてもスマートだなと感じました
venvを切ってライブラリを・・・と今まで当たり前のようにやっていましたが、builderとrunnerを利用して簡単に環境のセットアップができるようになれば良いなと思います。
いまいちわからなかったのが、ansibleを任意のVersionで実行したい場合。。こちら継続で調べたいと思います・・
次回はPythonスクリプトにansible-runnerを組み込んでみる
公式Docs
ansible-runner.readthedocs.io ansible-builder.readthedocs.io