Ansible Role を快適にユニットテストする方法を検証したのでまとめます.
Molecule とは
Molecule は Ansible Role のための公式のユニットテストフレームワークです.Ansible Role を実際に何かしらの環境に対して実行し,実行されたインフラに対してインフラテストを行い挙動を確認することで Role 単位でのユニットテストをテストを行うことができます.
LXD とは
LXD は Linux のためのシステムコンテナです.有名なコンテナ技術としては Docker が挙げられますがこちらはアプリケーションを実行するためのコンテナでアプリケーションコンテナと言われます.一方システムコンテナである LXD はシステムを用意するために使われるもので,使用感としては VM のようなコンテナに近いものになります.Ansible は一般的に VM に対して実行するつもりで作成するため,このような Ansible を検証する目的において LXD を選択することは良い選択だと思います.LXD はコンテナであるため作成・破棄を低コストで行うことができるので開発と検証をイテレーションを行う際にも時間的ボトルネックが小さなものになります.
Molecule は Ansible Role を流す対象に Docker, LXD, EC2 などを選ぶことができますが,今回はこのシステムコンテナである LXD に対して検証を行うことで品質の高いテストを実現したいと思います.
検証環境
- Ubuntu: 18.04
- Ansible: 2.9.1
- Molecule: 2.22
LXD 環境の構築
LXD はストレージバックエンドとして外部のデバイスを用意することが推奨されています.本稿ではブロックデバイスが /dev/sdb として認識されていることを前提として進めていきます.検証目的であればローカルディレクトリも利用することができます.詳しくはドキュメントを参照の上ストレージ設定部分を読み替えてください.
まずは LXD 環境を構築します.snap 経由での導入が推奨されているため,まずは snapd をインストールします.
$ sudo apt install snapd
snap を使えるようになったら LXD をインストールします.snap でインストールされたパッケージの実行形式は違う場所にインストールされるため PATH を通しておきます.適宜シェルの設定ファイルに設定してください.
$ sudo snap install lxd $ export PATH="/snap/bin:$PATH" # ~/.profile などに設定
lxdコマンドが実行できることを確認しておきます.
$ lxd --version 3.18
次に LXD を構築しますが,ネットワーク設定を行う際に競合して失敗してしまうため,一時的に bind9 を停止しておきます.
$ sudo systemctl stop bind9.service
LXD 構築は対話型で進むため質問をよく読んで進めてください.基本的にデフォルトのままで問題ありません.私の場合は既存のブロックデバイス /dev/sdb を利用するためその質問だけをデフォルトから変更してブロックデバイスへの PATH を入力しています.最後の質問に yes と答えると設定内容を YAML で出力してくれるので便利です.
$ sudo lxd init Would you like to use LXD clustering? (yes/no) [default=no]: Do you want to configure a new storage pool? (yes/no) [default=yes]: Name of the new storage pool [default=default]: Name of the storage backend to use (btrfs, ceph, dir, lvm, zfs) [default=zfs]: Create a new ZFS pool? (yes/no) [default=yes]: Would you like to use an existing block device? (yes/no) [default=no]: yes Path to the existing block device: /dev/sdb Would you like to connect to a MAAS server? (yes/no) [default=no]: Would you like to create a new local network bridge? (yes/no) [default=yes]: What should the new bridge be called? [default=lxdbr0]: What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: Would you like LXD to be available over the network? (yes/no) [default=no]: Would you like stale cached images to be updated automatically? (yes/no) [default=yes] Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
実行後に bind9 を再開しておきます.
$ sudo systemctl start bind9.service
ここまでで構築は完了です.LXD のコマンドラインインターフェースは lxc になります.このコマンドは管理者権限が必要ですが一々 sudo を付けるのも検証では不便です.lxd グループに所属していれば不要になるので設定してしまいます.設定後再ログインすれば有効化されます.
$ sudo usermod -aG lxd $(whoami)
LXD を使ってみる
LXD の感覚が分からないと何をしているのか不明になってしまう恐れがあるため LXD の基本的な使い方を紹介します.Molecule の利用に関しては完全に不要なため飛ばしてしまっても構いません.
実際に LXD を lxc コマンドで操作して使ってみましょう.Ubuntu 16.04 を起動してみます.これは ubuntu リポジトリの 16.04 イメージを使って xenial と名前をつけたコンテナを起動する意味になります.
$ lxc launch ubuntu:16.04 xenial
コンテナの状態は下記コマンドで確認可能です.
$ lxc list +--------+---------+---------------------+-----------------------------------------------+------------+-----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | +--------+---------+---------------------+-----------------------------------------------+------------+-----------+ | xenial | RUNNING | ZZ.ZZ.ZZ.ZZ (eth0) | ZZZZ:ZZZZ:ZZZZ:ZZZZ:ZZZZ:ZZZZ:ZZZZ:ZZZZ (eth0) | PERSISTENT | 0 | +--------+---------+---------------------+-----------------------------------------------+------------+-----------+
利用可能なリポジトリは下記コマンドで確認できます.今回はこの中の ubuntu リポジトリを利用したことになります.
$ lxc remote list +-----------------+------------------------------------------+---------------+-------------+--------+--------+ | NAME | URL | PROTOCOL | AUTH TYPE | PUBLIC | STATIC | +-----------------+------------------------------------------+---------------+-------------+--------+--------+ | images | https://images.linuxcontainers.org | simplestreams | none | YES | NO | +-----------------+------------------------------------------+---------------+-------------+--------+--------+ | local (default) | unix:// | lxd | file access | NO | YES | +-----------------+------------------------------------------+---------------+-------------+--------+--------+ | ubuntu | https://cloud-images.ubuntu.com/releases | simplestreams | none | YES | YES | +-----------------+------------------------------------------+---------------+-------------+--------+--------+ | ubuntu-daily | https://cloud-images.ubuntu.com/daily | simplestreams | none | YES | YES | +-----------------+------------------------------------------+---------------+-------------+--------+--------+
images リポジトリを使う場合は次のようになります.LinuxContainers公式サイトを確認して CentOS 8 のコンテナを centos8 という名前で起動してみます.
$ lxc launch images:centos/8/amd64 centos8
コンテナリストを出力すると増えていることを確認できます.
また,コンテナに対して直接コマンドを実行できます.bash を実行すれば擬似的にログインしたような感覚で作業することができます.
$ lxc exec xenial bash root@xenial:~#
コンテナの停止は lxc stop CONTAINERNAME で行うことができます.削除は delete になります.処理は明確だと思うので例は省略します.
検証用 Ansible Role の用意
今回は検証のための最小構成の Role を用意します.この Role は Ubuntu に対して tmux をインストールするものになります.
$ tree ansible-role-tmux/
ansible-role-tmux/
└── tasks
└── main.yml
$ cat ansible-role-tmux/tasks/main.yml
---
- name: Install tmux
apt:
name: tmux
update_cache: yes
これを検証するのを目標とします.
Molecule によるユニットテストの導入
今回はライブラリの管理に Pipenv を使います.Role のルート直下に下記内容の Pipfile を用意します.
[[source]] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] molecule = "==2.22" [packages] ansible = "==2.9.1" [requires] python_version = "3.6"
Pipenv で Python の環境を構築します.
$ pipenv install -d
Molecule でテストを行うための設定を行います.手動で書いても良いのですが面倒なので用意されているコマンドで雛形を生成します.
$ pipenv run molecule init scenario --driver-name lxd
ここまでで下記状態になっているはずです.
$ tree .
.
├── molecule
│ └── default
│ ├── INSTALL.rst
│ ├── molecule.yml
│ ├── playbook.yml
│ └── tests
│ ├── __pycache__
│ │ └── test_default.cpython-36.pyc
│ └── test_default.py
├── Pipfile
├── Pipfile.lock
└── tasks
└── main.yml
Molecule が生成してくれるテンプレートは情報が古いため微修正します.具体的には実行先のコンテナのベースイメージを pull する部分の情報が古いためその部分を更新します.platforms の項目だけが変更点です.変更後のファイル内容全体を下記に貼り付けています.
$ cat molecule/default/molecule.yml
---
dependency:
name: galaxy
driver:
name: lxd
lint:
name: yamllint
platforms:
- name: instance
source:
type: image
mode: pull
server: https://images.linuxcontainers.org
protocol: simplestreams
alias: ubuntu/xenial/amd64
architecture: x86_64
provisioner:
name: ansible
lint:
name: ansible-lint
verifier:
name: testinfra
lint:
name: flake8
ここまででとりあえずテストは実行できるはずです.実行してみましょう.実行結果は省略します.ログを詳しく確認してみてください.
$ pipenv run molecule test
この段階ではこの Role を実行して確かに tmux がインストールされた状態になっているか確認することはできていません.テストコードは molecule/default/tests/ ディレクトリに配置されます.テスト自体は testinfra の形式なっているため詳しくはドキュメントをご確認ください.今回は tmux がインストールされていることを保証するコードを書いてみます.
下記テストコードを molecule/default/tests/test_default.py に追加します.
def test_tmux_is_installed(host):
tmux = host.package('tmux')
assert tmux.is_installed
もう一度テストを実行してみてください.テスト実行の所で collected 2 items になっていれば認識されています.テストに到達しない場合は flake8 や yamllint などの lint で落ちている場合があるので確認してみてください.テストが成功したら「存在しないパッケージが存在するはず」という間違ったテストを書いてみて実際にテストが通らないことを確認して挙動を確認してみてください.便利さが実感できると思います.
まとめ
Ansible Role をユニットテストする方法を解説しました.今回は tmux を入れるだけという簡単な Role でしたが,LXD を採用しているため systemd や Docker コンテナを操作するタスクの検証をすることもできます(Docker を LXD 内で動かすためにはオプションが必要です).実際の VM に近い LXD コンテナを活用することで検証が大変な Ansible をテストし,品質と開発速度を上げることでインフラ管理が少しでも楽になります.もし手動で動作確認をしているのであればぜひこのようなテストの仕組みを導入してみていただけたらと思います.