blog.manj.io

ごく稀に更新

Ansible Playbookのパッケージ化

Ansible Advent Calendar の15日目の記事です。

qiita.com

Ansibleって pip install ansible しただけじゃ使えない機能が一杯ある。特定のライブラリをtargetにインストールしてないと使えないmoduleがあったり、localhostにインストールされてないと使えないfilterがあったり。

いざAnsibleを実行してみて、「ライブラリが参照できなくて途中でデプロイが終了しました」みたいなことになると超絶悲惨なので、Ansibleを使うにあたって注意が必要なポイントだ。

ただ、「あらかじめこれらのパッケージをpipでインストールしておけばどんな状況にも対応できます!」みたいな魔法のセットも存在しないし、そもそもAnsible自体のバージョン管理も考えなくちゃいけないので、Ansibleのパッケージ管理は思いの外難しい。

正直、この辺は初学者にとってはとっつきにくいトピックだと思う。Ansibleそのものの使い方とは離れたトピックだし、pythonのパッケージ管理はツールや文化の変化が激しいし。

この記事では、基礎となる考え方をまとめて「Ansible Playbookとその実行環境を1つのパッケージのように扱う方法」として紹介する。

ポイント

  • Ansibleとmodule依存パッケージのバージョン管理
  • venvを使った実行環境の固定
  • localhostに対するAnsible実行時のpython実行環境の指定

Ansibleとmodule依存パッケージのバージョン管理

まずは使うAnsibleと依存パッケージを適切に管理する手段を用意する。

具体的に固定したいパッケージの種類は3種類ある。

  • Ansible本体
    • Ansibleはバージョン間で破壊的な変更が入ることがままあるので、前提となるバージョンをちゃんと決めて使うことが重要
  • localhostで実行するmoduleが依存するライブラリ
    • hosts: localhost で実行するplayやdelegate_to: localhostで実行するtaskで使われるmoduleについて、そのmoduleが依存するライブラリがlocalhostにインストールされていることを保証しなくてはいけない
  • filter plugin, strategy plugin, callback pluginが依存するライブラリ
    • 特殊な文字列操作を行なうためのfilterやmitogenなどの特殊なstrategy plugin、playの出力に応じて特殊な操作を実施するためのcallback pluginなどを独自に組込むケースはまれによくあるので、それらのpluginが依存するライブラリのバージョン管理をどこかで実施する必要がある

また、Ansibleを動作させるPythonのバージョンも固定したいところ。地味だが重要なポイント。

これら全てはpipenvやpoetryなどのツールで管理することができる。

また、ここでは

  • 特定のfilterが依存するライブラリ: ipaddr
  • 特定のmoduleが依存するライブラリ: openshift

を例にする。

1. pipenvを使う

Prepare Pipfile

見て意味を感じてくれ

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
ansible = "~=2.9.0"
openshift = "~=0.11.0"
ipaddr = "~=2.2.0"

[requires]
python_version = "3.9.1"

Install

もし python_version にローカルにインストールされてないバージョンを指定しているのであれば、事前にpyenvを用意しておくこと。

  1. pip install pipenv
  2. (Pipfileを配置したディレクトリ上で) pipenv install

venvの作成とvenvへのパッケージのインストールが実施される。あと、依存ライブラリのバージョン固定が行なわれる。

➜  test-env pipenv install
Warning: Python 3.9.1 was not found on your system...
Would you like us to install CPython 3.9.1 with pyenv? [Y/n]:
Installing CPython 3.9.1 with pyenv (this may take a few minutes)...
✔ Success!
Downloading Python-3.9.1.tar.xz...
-> https://www.python.org/ftp/python/3.9.1/Python-3.9.1.tar.xz
Installing Python-3.9.1...
Installed Python-3.9.1 to /home/test/.pyenv/versions/3.9.1


Creating a virtualenv for this project...
Pipfile: /home/test/src/test-env/Pipfile
Using /home/test/.pyenv/versions/3.9.1/bin/python3.9 (3.9.1) to create virtualenv...
⠏ Creating virtual environment...Already using interpreter /home/test/.pyenv/versions/3.9.1/bin/python3.9
Using base prefix '/home/test/.pyenv/versions/3.9.1'
New python executable in /home/test/.local/share/virtualenvs/test-env-4samwdnR/bin/python3.9
Also creating executable in /home/test/.local/share/virtualenvs/test-env-4samwdnR/bin/python
Installing setuptools, pip, wheel...
done.
Running virtualenv with interpreter /home/test/.pyenv/versions/3.9.1/bin/python3.9

✔ Successfully created virtual environment!
Virtualenv location: /home/test/.local/share/virtualenvs/test-env-4samwdnR
Pipfile.lock not found, creating...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
✔ Success!
Updated Pipfile.lock (46a537)!
Installing dependencies from Pipfile.lock (46a537)...
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 27/27 — 00:00:32
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

Tips

  • PipfilePipfile.lock が既に存在している状態で Pipfile.lock に完全に準拠してパッケージと依存ライブラリのインストールを実施したい場合は、pipenv sync を使えばよい。
  • source.url に独自のpip repoのurlを指定することができる。

2. poetryを使う

結局やることはpipenvと同じ。依存ライブラリのlockが速い(50倍くらい速かった)というメリットがあるが、ライブラリごとのセキュリティチェックが実施されないらしい。(よく知らない)

ただ、poetryはvenvをセットアップするときにローカルのpythonと同じバージョンの環境しか作ってくれない。なので、特定のバージョンのpythonを使いたいときはpyenvでインストール & 切り替えておく必要がある。

コンテナで実行するなら、使うコンテナイメージをちゃんと選ぼう。pyenvさえ入ってればそれでいいpipenvより少し不便。

Prepare pyproject.toml

[tool.poetry]
name = "test-env2"
version = "0.1.0"
description = ""
authors = ["Wataru Manji <wataru.manji@linecorp.com>"]

[tool.poetry.dependencies]
python = "^3.8"
ansible = "~=2.9.0"
openshift = "~=0.11.0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

install

  1. pip install poetry
  2. (pyproject.tomlを配置したディレクトリ上で) poetry install
➜  test-env2 poetry install
Creating virtualenv test-env2 in /home/test/src/test-env2/.venv
Updating dependencies
Resolving dependencies... (0.7s)

Writing lock file


Package operations: 25 installs, 0 updates, 0 removals

  - Installing certifi (2020.12.5)
  - Installing chardet (3.0.4)
  - Installing idna (2.10)
  - Installing pyasn1 (0.4.8)
  - Installing urllib3 (1.26.2)
  - Installing cachetools (4.2.0)
  - Installing oauthlib (3.1.0)
  - Installing pyasn1-modules (0.2.8)
  - Installing requests (2.25.0)
  - Installing rsa (4.6)
  - Installing six (1.15.0)
  - Installing google-auth (1.24.0)
  - Installing markupsafe (1.1.1)
  - Installing python-dateutil (2.8.1)
  - Installing pyyaml (5.3.1)
  - Installing requests-oauthlib (1.3.0)
  - Installing ruamel.yaml.clib (0.2.2)
  - Installing websocket-client (0.57.0)
  - Installing jinja2 (2.11.2)
  - Installing kubernetes (11.0.0)
  - Installing python-string-utils (1.0.0)
  - Installing ruamel.yaml (0.16.12)
  - Installing ansible (2.9.15)
  - Installing ipaddr (2.2.0)
  - Installing openshift (0.11.2)

venvを使った実行環境の固定

pipenvもpoetryも自動的にvenvを作成するので、ansibleのコマンドはvenv内で起動する必要がある。

どちらのツールもvenv内でコマンドを実行するためのヘルパーを持っているので、それを使えばvenvの位置を意識することなくコマンドを実行可能。

  • pipenv: pipenv run ansible
  • poetry: poetry run ansible

localhostに対するAnsible実行時のpython実行環境の指定

特定のfilterやstrategyが依存するライブラリ...例では ipaddr だが、これはAnsibleの実行環境にインストールされていれば特に何も気にせず使える。

しかし、「target上で実行するmoduleが依存するライブラリ」はそうもいかない。localhostでそのmoduleを動かすとしても、pipenv等で作成したvenvを使うように設定してあげないとmoduleは動かない。例では k8s moduleの依存ライブラリとして openshift をインストールすることを想定しているが、シンプルにk8s moduleを実行したとしてもansibleはvenv内のライブラリを参照することができない。

幸い、pipenvもpoetryも VIRTUAL_ENV という環境変数でvenvのpathを保持しているので、これを使ってlocalhostをtargetにしたときのpython実行環境を指定してあげることができる。

僕がよく使うのはinventoryにそのあたりの設定を入れこむテク。inventoryに以下のように書けば、pipenvなどで用意したvenvをよしなに使ってくれるようになる。

all:
  hosts:
    localhost:
      ansible_connection: local
      ansible_python_interpreter: "{{ lookup('env', 'VIRTUAL_ENV') }}/bin/python"

localhostをall配下のhostとして定義してあげて、connection方式とvenvの場所を指定する、という構造。VIRTUAL_ENVpipenv runpipenv shell 等で入る環境でしかsetされていないが、ansibleが動くのはそのような環境の中なので、問題なく VIRTUAL_ENV を使ってvenvのpythonを参照することができる。

パッケージ化

ここまでの「Ansible本体と依存ライブラリをパッケージ化する」手段を用いて、ansibleの実行環境をパッケージングすることができる。

例えば、こういうディレクトリ設計をして

.
├── ansible.cfg
├── custom_modules
│   └── hoge.py
├── filter_plugins
│   └── filter.py
├── inventories
│   └── site1
│       ├── group_vars
│       │   └── all.yml
│       └── hosts.yml
├── Makefile
├── playbook
│   ├── fuga.yml
│   ├── hoge.yml
│   ├── roles
│   │   └── hoge
│   │       ├── defaults
│   │       │   └── main.yml
│   │       ├── tasks
│   │       │   └── main.yml
│   │       └── templates
│   │           └── config.j2
│   └── site.yml
├── poetry.lock
├── pyproject.toml
├── README.md
└── strategy_plugins

こいつをコンテナベースのCI/CDツールで参照すれば、バージョン固定された実行環境で任意のplaybookを実行することができる。

また、新しくこのplaybookを参照して作業したい人が出てきたときも、環境依存なトラブルを未然に回避することができるようになる。

デプロイコードは想定動作と違う挙動をしたり途中で止まったりするとインパクトがデカい部類のものなので、環境をしっかり固定して実行できるよう準備をしておくのはとても大事。今回紹介したような手段を使ってパッケージングしてあげよう。

© 2018 manji0.