Ubuntu16.04でMoleculeを使用してAnsibleの役割をテストする方法
著者は、 Write for DOnations プログラムの一環として、 MozillaFoundationを選択して寄付を受け取りました。
序章
Ansible での単体テストは、役割が意図したとおりに機能することを確認するための鍵です。 Molecular は、さまざまな環境に対して役割をテストするシナリオを指定できるようにすることで、このプロセスを容易にします。 Moleculeは、内部でAnsibleを使用して、構成された環境に役割をデプロイするプロビジョナーに役割をオフロードし、ベリファイア( Testinfra など)を呼び出して構成のドリフトをチェックします。 これにより、あなたの役割がその特定のシナリオで環境に予想されるすべての変更を加えたことを確認できます。
このガイドでは、 Apache をホストにデプロイし、CentOS7でFirewalldを構成するAnsibleロールを構築します。 この役割が意図したとおりに機能することをテストするには、 Docker をドライバーとして使用し、サーバーの状態をテストするためのPythonライブラリであるTestinfraを使用してMoleculeでテストを作成します。 MoleculeはDockerコンテナーをプロビジョニングして役割をテストし、Testinfraはサーバーが意図したとおりに構成されていることを確認します。 終了すると、環境間でビルドするための複数のテストケースを作成し、Moleculeを使用してこれらのテストを実行できるようになります。
前提条件
このガイドを開始する前に、次のものが必要です。
- 1つのUbuntu16.04サーバー。 Ubuntu 16.04を使用したサーバーの初期設定ガイドの手順に従って、root以外のsudoユーザーを作成し、パスワードなしでサーバーに接続できることを確認します。
- サーバーにDockerがインストールされています。 Ubuntu 16.04 にDockerをインストールして使用する方法の手順1と2に従い、root以外のユーザーを
docker
グループに追加してください。 - Ansibleプレイブックに精通していること。 レビューについては、 Configuration Management 101:Writing AnsiblePlaybooksを参照してください。
ステップ1—環境の準備
役割とテストを作成するために、最初に仮想環境を作成してMoleculeをインストールしましょう。 Moleculeをインストールすると、Ansibleもインストールされ、プレイブックを使用してロールを作成し、テストを実行できるようになります。
root以外のユーザーとしてログインし、リポジトリが最新であることを確認することから始めます。
sudo apt-get update
これにより、パッケージリポジトリにpython-pip
パッケージの最新バージョンが含まれ、pip
とPython2.7がインストールされます。 pip
を使用して仮想環境を作成し、追加のパッケージをインストールします。 pip
をインストールするには、次のコマンドを実行します。
sudo apt-get install -y python-pip
pip
を使用して、virtualenv
Pythonモジュールをインストールします。
python -m pip install virtualenv
次に、仮想環境を作成してアクティブ化します。
python -m virtualenv my_env
これをアクティブにして、アクションがその環境に制限されていることを確認します。
source my_env/bin/activate
pip
を使用してmolecule
およびdocker
をインストールします。
python -m pip install molecule docker
これらの各パッケージの機能は次のとおりです。
molecule
:これは、役割のテストに使用するメインのMoleculeパッケージです。molecule
をインストールすると、Ansibleが他の依存関係とともに自動的にインストールされ、Ansibleプレイブックを使用して役割とテストを実行できるようになります。docker
:このPythonライブラリは、MoleculeがDockerとのインターフェイスに使用します。 Dockerをドライバーとして使用しているため、これが必要になります。
次に、Moleculeでロールを作成しましょう。
ステップ2—分子での役割の作成
環境を設定したら、Moleculeを使用して、Apacheのインストールをテストするための基本的な役割を作成できます。 この役割は、ディレクトリ構造といくつかの初期テストを作成し、Dockerをドライバーとして指定して、MoleculeがDockerを使用してテストを実行するようにします。
ansible-apache
という新しい役割を作成します。
molecule init role -r ansible-apache -d docker
-r
フラグは役割の名前を指定し、-d
はドライバーを指定します。ドライバーはテストで使用するMoleculeのホストをプロビジョニングします。
新しく作成されたロールのディレクトリに移動します。
cd ansible-apache
デフォルトの役割をテストして、Moleculeが正しく設定されているかどうかを確認します。
molecule test
デフォルトの各テストアクションを一覧表示する出力が表示されます。
Output--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix └── default ├── lint ├── destroy ├── dependency ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── destroy ...
テストを開始する前に、Moleculeは構成ファイルmolecule.yml
を検証して、すべてが正常であることを確認します。 また、テストアクションの順序を指定するこのテストマトリックスも出力します。
役割を作成してテストをカスタマイズしたら、各テストアクションについて詳しく説明します。 今のところ、各テストのPLAY_RECAP
に注意し、デフォルトのアクションのいずれもfailed
ステータスを返さないことを確認してください。 たとえば、デフォルトの'create'
アクションのPLAY_RECAP
は、次のようになります。
Output... PLAY RECAP ********************************************************************* localhost : ok=5 changed=4 unreachable=0 failed=0
次に、ApacheとFirewalldを構成するための役割の変更に移りましょう。
ステップ3—ApacheとFirewalldの設定
ApacheとFirewalldを設定するには、ロールのタスクファイルを作成し、インストールするパッケージと有効にするサービスを指定します。 これらの詳細は、デフォルトのApacheインデックスページを置き換えるために使用する変数ファイルとテンプレートから抽出されます。
nano
またはお気に入りのテキストエディタを使用して、ロールのタスクファイルを作成します。
nano tasks/main.yml
ファイルがすでに存在していることがわかります。 そこにあるものを削除し、次のコードを貼り付けて、必要なパッケージをインストールし、正しいサービス、HTMLのデフォルト、およびファイアウォール設定を有効にします。
〜/ ansible-apache / tasks / main.yml
--- - name: "Ensure required packages are present" yum: name: "{{ pkg_list }}" state: present - name: "Ensure latest index.html is present" template: src: index.html.j2 dest: /var/www/html/index.html - name: "Ensure httpd service is started and enabled" service: name: "{{ item }}" state: started enabled: true with_items: "{{ svc_list }}" - name: "Whitelist http in firewalld" firewalld: service: http state: enabled permanent: true immediate: true
このプレイブックには、次の4つのタスクが含まれています。
"Ensure required packages are present"
:このタスクは、pkg_list
の下の変数ファイルにリストされているパッケージをインストールします。 変数ファイルは~/ansible-apache/vars/main.yml
にあり、このステップの最後に作成します。"Ensure latest index.html is present"
:このタスクは、テンプレートページindex.html.j2
をコピーし、Apacheによって生成されたデフォルトのインデックスファイル/var/www/html/index.html
に貼り付けます。 このステップでは、テンプレートも作成します。"Ensure httpd service is started and enabled"
:このタスクは、変数ファイルのsvc_list
にリストされているサービスを開始して有効にします。"Whitelist http in firewalld"
:このタスクは、firewalld
のhttp
サービスをホワイトリストに登録します。 Firewalldは、CentOSサーバーにデフォルトで存在する完全なファイアウォールソリューションです。http
サービスを機能させるには、必要なポートを公開する必要があります。firewalld
にサービスをホワイトリストに登録するように指示すると、サービスに必要なすべてのポートがホワイトリストに登録されます。
終了したら、ファイルを保存して閉じます。
次に、index.html.j2
テンプレートページのtemplates
ディレクトリを作成しましょう。
mkdir templates
ページ自体を作成します。
nano templates/index.html.j2
次の定型コードを貼り付けます。
〜/ ansible-apache / templates / index.html.j2
<div style="text-align: center"> <h2>Managed by Ansible</h2> </div>
ファイルを保存して閉じます。
ロールを完了するための最後のステップは、変数ファイルを作成することです。このファイルは、メインのロールプレイブックにパッケージとサービスの名前を提供します。
nano vars/main.yml
pkg_list
とsvc_list
を指定する次のコードを使用して、デフォルトのコンテンツを貼り付けます。
〜/ ansible-apache / vars / main.yml
--- pkg_list: - httpd - firewalld svc_list: - httpd - firewalld
これらのリストには、次の情報が含まれています。
pkg_list
:これには、ロールがインストールするパッケージの名前が含まれています:httpd
およびfirewalld
。svc_list
:これには、ロールが開始して有効にするサービスの名前が含まれます:httpd
およびfirewalld
。
注:変数ファイルに空白行がないことを確認してください。空白行がない場合、リンティング中にテストが失敗します。
ロールの作成が完了したので、意図したとおりに機能するかどうかをテストするようにMoleculeを構成しましょう。
ステップ4—テストを実行するための役割を変更する
この場合、Moleculeを構成するには、Molecule構成ファイルmolecule.yml
を変更してプラットフォーム仕様を追加する必要があります。 httpd
systemdサービスを構成して開始するロールをテストしているため、systemdが構成され、特権モードが有効になっているイメージを使用する必要があります。 このチュートリアルでは、DockerHubで利用可能なmilcom/centos7-systemd
イメージを使用します。 特権モードでは、コンテナーをホストマシンのほぼすべての機能で実行できます。
molecule.yml
を編集して、これらの変更を反映させましょう。
nano molecule/default/molecule.yml
強調表示されたプラットフォーム情報を追加します。
〜/ ansible-apache /molecule / default /molecule.yml
--- dependency: name: galaxy driver: name: docker lint: name: yamllint platforms: - name: centos7 image: milcom/centos7-systemd privileged: true provisioner: name: ansible lint: name: ansible-lint scenario: name: default verifier: name: testinfra lint: name: flake8
完了したら、ファイルを保存して閉じます。
テスト環境の構成が正常に完了したので、役割の実行後にMoleculeがコンテナーに対して実行するテストケースの作成に移りましょう。
ステップ5—テストケースの作成
この役割のテストでは、次の条件を確認します。
httpd
およびfirewalld
パッケージがインストールされていること。httpd
およびfirewalld
サービスが実行され、有効になっていること。http
サービスがファイアウォール設定で有効になっていること。- その
index.html
には、テンプレートファイルで指定されたものと同じデータが含まれています。
これらのテストがすべて合格した場合、役割は意図したとおりに機能します。
これらの条件のテストケースを作成するには、~/ansible-apache/molecule/default/tests/test_default.py
のデフォルトのテストを編集してみましょう。 Testinfraを使用して、Moleculeクラスを使用するPython関数としてテストケースを記述します。
test_default.py
を開きます:
nano molecule/default/tests/test_default.py
テストを最初から作成できるように、ファイルの内容を削除します。
注:テストを作成するときは、テストが2行で区切られていることを確認してください。そうしないと、失敗します。
必要なPythonモジュールをインポートすることから始めます。
〜/ ansible-apache /molecule / default / tests / test_default.py
import os import pytest import testinfra.utils.ansible_runner
これらのモジュールには次のものが含まれます。
os
:この組み込みのPythonモジュールは、オペレーティングシステムに依存する機能を有効にし、Pythonが基盤となるオペレーティングシステムとインターフェイスできるようにします。pytest
:pytestモジュールはテスト書き込みを有効にします。testinfra.utils.ansible_runner
:このTestinfraモジュールは、コマンド実行のバックエンドとしてAnsibleを使用します。
モジュールのインポートの下に、次のコードを貼り付けます。このコードは、Ansibleバックエンドを使用して現在のホストインスタンスを返します。
〜/ ansible-apache /molecule / default / tests / test_default.py
... testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
Ansibleバックエンドを使用するようにテストファイルを構成したら、ホストの状態をテストする単体テストを作成しましょう。
最初のテストでは、httpd
とfirewalld
がインストールされていることを確認します。
〜/ ansible-apache /molecule / default / tests / test_default.py
... @pytest.mark.parametrize('pkg', [ 'httpd', 'firewalld' ]) def test_pkg(host, pkg): package = host.package(pkg) assert package.is_installed
テストはpytest.mark.parametrizeデコレータで始まります。これにより、テストの引数をパラメータ化できます。 この最初のテストでは、[X23X]およびfirewalld
パッケージの存在をテストするためのパラメーターとしてtest_pkg
を使用します。
次のテストでは、httpd
とfirewalld
が実行され、有効になっているかどうかを確認します。 パラメータとしてtest_svc
を取ります。
〜/ ansible-apache /molecule / default / tests / test_default.py
... @pytest.mark.parametrize('svc', [ 'httpd', 'firewalld' ]) def test_svc(host, svc): service = host.service(svc) assert service.is_running assert service.is_enabled
最後のテストでは、parametrize()
に渡されたファイルとコンテンツが存在することを確認します。 ファイルが自分の役割によって作成されておらず、コンテンツが適切に設定されていない場合、assert
はFalse
を返します。
〜/ ansible-apache /molecule / default / tests / test_default.py
... @pytest.mark.parametrize('file, content', [ ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"), ("/var/www/html/index.html", "Managed by Ansible") ]) def test_files(host, file, content): file = host.file(file) assert file.exists assert file.contains(content)
各テストで、assert
はテスト結果に応じてTrue
またはFalse
を返します。
完成したファイルは次のようになります。
〜/ ansible-apache /molecule / default / tests / test_default.py
import os import pytest import testinfra.utils.ansible_runner testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') @pytest.mark.parametrize('pkg', [ 'httpd', 'firewalld' ]) def test_pkg(host, pkg): package = host.package(pkg) assert package.is_installed @pytest.mark.parametrize('svc', [ 'httpd', 'firewalld' ]) def test_svc(host, svc): service = host.service(svc) assert service.is_running assert service.is_enabled @pytest.mark.parametrize('file, content', [ ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"), ("/var/www/html/index.html", "Managed by Ansible") ]) def test_files(host, file, content): file = host.file(file) assert file.exists assert file.contains(content)
テストケースを指定したので、役割をテストしましょう。
ステップ6—Moleculeを使用して役割をテストする
テストを開始すると、Moleculeはシナリオで定義したアクションを実行します。 次に、デフォルトのmolecule
シナリオを再度実行して、それぞれを詳しく調べながら、デフォルトのテストシーケンスでアクションを実行してみましょう。
デフォルトシナリオのテストを再度実行します。
molecule test
これにより、テスト実行が開始されます。 初期出力は、デフォルトのテストマトリックスを出力します。
Output--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix └── default ├── lint ├── destroy ├── dependency ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── destroy
リンティングから始めて、各テストアクションと期待される出力を見ていきましょう。
linting アクションは、yamllint
、flake8
、およびansible-lint
を実行します。
yamllint
:このリンターは、ロールディレクトリに存在するすべてのYAMLファイルで実行されます。flake8
:このPythonコードリンターは、Testinfra用に作成されたテストをチェックします。ansible-lint
:Ansibleプレイブック用のこのリンターは、すべてのシナリオで実行されます。
Output... --> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in /home/sammy/ansible-apache/... Lint completed successfully. --> Executing Flake8 on files found in /home/sammy/ansible-apache/molecule/default/tests/... Lint completed successfully. --> Executing Ansible Lint on /home/sammy/ansible-apache/molecule/default/playbook.yml... Lint completed successfully.
次のアクションdestroyは、destroy.yml
ファイルを使用して実行されます。 これは、新しく作成されたコンテナでの役割をテストするために行われます。
デフォルトでは、destroyは2回呼び出されます。テスト実行の開始時に既存のコンテナを削除し、最後に新しく作成されたコンテナを削除します。
Output... --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* ok: [localhost] => (item=None) ok: [localhost] TASK [Delete docker network(s)] ************************************************ skipping: [localhost] PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0
破棄アクションが完了すると、テストは依存関係に進みます。 このアクションにより、役割で必要な場合にansible-galaxyから依存関係をプルできます。 この場合、役割は次のことを行いません。
Output... --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file.
次のテストアクションは構文チェックで、デフォルトのplaybook.yml
プレイブックで実行されます。 これは、コマンドansible-playbook --syntax-check playbook.yml
の--syntax-check
フラグと同じように機能します。
Output... --> Scenario: 'default' --> Action: 'syntax' playbook: /home/sammy/ansible-apache/molecule/default/playbook.yml
次に、テストはcreateアクションに進みます。 これは、ロールのMoleculeディレクトリにあるcreate.yml
ファイルを使用して、指定したDockerコンテナを作成します。
Output... --> Scenario: 'default' --> Action: 'create' PLAY [Create] ****************************************************************** TASK [Log into a Docker registry] ********************************************** skipping: [localhost] => (item=None) skipping: [localhost] TASK [Create Dockerfiles from image names] ************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Discover local Docker images] ******************************************** ok: [localhost] => (item=None) ok: [localhost] TASK [Build an Ansible compatible image] *************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Create docker network(s)] ************************************************ skipping: [localhost] TASK [Create molecule instance(s)] ********************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) creation to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=5 changed=4 unreachable=0 failed=0
作成後、テストはprepareアクションに進みます。 このアクションは、コンバージを実行する前にホストを特定の状態にする準備プレイブックを実行します。 これは、役割を実行する前に、役割でシステムの事前構成が必要な場合に役立ちます。 繰り返しますが、これは私たちの役割には適用されません。
Output... --> Scenario: 'default' --> Action: 'prepare' Skipping, prepare playbook not configured.
準備後、converge アクションは、playbook.yml
プレイブックを実行して、コンテナーでの役割を実行します。 molecule.yml
ファイルで複数のプラットフォームが構成されている場合、Moleculeはこれらすべてに収束します。
Output... --> Scenario: 'default' --> Action: 'converge' PLAY [Converge] **************************************************************** TASK [Gathering Facts] ********************************************************* ok: [centos7] TASK [ansible-apache : Ensure required packages are present] ******************* changed: [centos7] TASK [ansible-apache : Ensure latest index.html is present] ******************** changed: [centos7] TASK [ansible-apache : Ensure httpd service is started and enabled] ************ changed: [centos7] => (item=httpd) changed: [centos7] => (item=firewalld) TASK [ansible-apache : Whitelist http in firewalld] **************************** changed: [centos7] PLAY RECAP ********************************************************************* centos7 : ok=5 changed=4 unreachable=0 failed=0
カバーした後、テストはべき等に進みます。 このアクションは、べき等性についてプレイブックをテストし、複数の実行で予期しない変更が行われないことを確認します。
Output... --> Scenario: 'default' --> Action: 'idempotence' Idempotence completed successfully.
次のテストアクションは、副作用アクションです。 これにより、HAフェイルオーバーなど、より多くのことをテストできる状況を作り出すことができます。 デフォルトでは、Moleculeは副作用のプレイブックを構成せず、タスクはスキップされます。
Output... --> Scenario: 'default' --> Action: 'side_effect' Skipping, side effect playbook not configured.
次に、Moleculeは、デフォルトのベリファイアであるTestinfraを使用してverifierアクションを実行します。 このアクションは、test_default.py
で以前に作成したテストを実行します。 すべてのテストに合格すると、成功メッセージが表示され、Moleculeは次のステップに進みます。
Output... --> Scenario: 'default' --> Action: 'verify' --> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/... ============================= test session starts ============================== platform linux2 -- Python 2.7.12, pytest-3.8.0, py-1.6.0, pluggy-0.7.1 rootdir: /home/sammy/ansible-apache/molecule/default, inifile: plugins: testinfra-1.14.1 collected 6 items tests/test_default.py ...... [100%] ========================== 6 passed in 56.73 seconds =========================== Verifier completed successfully.
最後に、Molecule destroy は、テスト中に完了したインスタンスを破棄し、それらのインスタンスに割り当てられたネットワークを削除します。
Output... --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Delete docker network(s)] ************************************************ skipping: [localhost] PLAY RECAP ********************************************************************* localhost : ok=2 changed=2 unreachable=0 failed=0
これでテストアクションが完了し、役割が意図したとおりに機能したことを確認します。
結論
この記事では、ApacheとFirewalldをインストールして構成するためのAnsibleロールを作成しました。 次に、Moleculeが役割が正常に実行されたことを表明するために使用したTestinfraを使用して単体テストを作成しました。
非常に複雑な役割にも同じ基本的な方法を使用でき、CIパイプラインを使用してテストを自動化することもできます。 Moleculeは高度に構成可能なツールであり、Dockerだけでなく、Ansibleがサポートする任意のプロバイダーで役割をテストするために使用できます。 独自のインフラストラクチャに対するテストを自動化して、役割が常に最新で機能していることを確認することもできます。 公式のMoleculeドキュメントは、Moleculeの使用方法を学ぶための最良のリソースです。