_著者はhttps://www.brightfunds.org/organizations/mozilla-foundation[Mozilla Foundation]を選択して、https://do.co/w4do-cta [Write for DOnations]プログラムの一環として寄付を受け取りました。

前書き

Ansibleのユニットテストは、ロールが意図したとおりに機能することを確認するための鍵です。 Moleculeでは、さまざまな環境に対して役割をテストするシナリオを指定できるため、このプロセスが簡単になります。 フードの下でAnsibleを使用して、Moleculeはロールをプロビジョニングされた環境に展開するプロビジョニング担当者にオフロードし、ベリファイア(https://github.com/philpep/testinfra[Testinfra]など)を呼び出して構成のドリフトを確認します。 これにより、ロールがその特定のシナリオで環境に対して予想されるすべての変更を行ったことを確認できます。

このガイドでは、https://httpd.apache.org/ [Apache]をホストにデプロイし、CentOS 7でhttps://firewalld.org/[firewalld]を構成するAnsibleロールを構築します。 この役割が意図したとおりに機能することをテストするには、https://www.docker.com/ [Docker]をドライバーとして使用し、Testinfra(サーバーの状態をテストするためのPythonライブラリー)を使用してMoleculeでテストを作成します。 MoleculeはDockerコンテナーをプロビジョニングしてロールをテストし、Testinfraはサーバーが意図したとおりに構成されていることを確認します。 終了したら、環境全体のビルド用に複数のテストケースを作成し、Moleculeを使用してこれらのテストを実行できます。

前提条件

このガイドを始める前に、次のものが必要です。

ステップ1-環境の準備

前提条件に従っている場合は、Python 3、 + venv +、およびDockerがインストールされ、正しく構成されている必要があります。 AnsibleをMoleculeでテストするための仮想環境を作成することから始めましょう。

非rootユーザーとしてログインし、新しい仮想環境を作成することから始めます。

python3 -m venv

それをアクティブにして、アクションがその環境に制限されるようにします。

source /bin/activate

次に、アクティブ化された環境で、 `+ wheel `パッケージをインストールします。これは、 ` pip `がAnsibleのインストールに使用する ` bdist_wheel +`拡張機能を提供します。

python3 -m pip install wheel

+ pip`で + molecule`と `+ docker`をインストールできるようになりました。 AnsibleはMoleculeの依存関係として自動的にインストールされます。

python3 -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

デフォルトの各テストアクションをリストする出力が表示されます。 テストを開始する前に、Moleculeは設定ファイル `+ molecule.yml +`を検証して、すべてが正常であることを確認します。 また、テストアクションの順序を指定するこのテストマトリックスを出力します。

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
...

ロールを作成してテストをカスタマイズしたら、各テストアクションについて詳しく説明します。 とりあえず、各テストの `+ 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インデックスページを置き換えるために使用する変数ファイルとテンプレートから抽出されます。

まだ `+ ansible-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つのタスクが含まれています。

  • "必要なパッケージが存在することを確認する ":このタスクは、変数ファイルの `+ pkg_list `の下にリストされているパッケージをインストールします。 変数ファイルは `〜/ ansible-apache / vars / main.yml +`にあり、このステップの最後に作成します。

  • "最新のindex.htmlが存在することを確認する ":このタスクは、テンプレートページ `+ index.html.j2 `をコピーし、デフォルトのインデックスファイル ` / var / www / html / indexに貼り付けます。 .html + `、Apacheによって生成されます。 このステップでは、新しいテンプレートも作成します。

  • " httpdサービスが開始され有効になっていることを確認する ":このタスクは、変数ファイルの `+ svc_list +`にリストされているサービスを開始して有効にします。

  • " firewalldのhttpをホワイトリストに入れる ":このタスクは、 `+ 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が設定され、特権モードが有効になっているイメージを使用する必要があります。 このチュートリアルでは、 `+ milcom / centos7-systemd +`イメージhttps://hub.docker.com/r/milcom/centos7-systemd/[Docker Hubで利用可能]を使用します。 特権モードでは、コンテナをホストマシンのほぼすべての機能で実行できます。

これらの変更を反映するために `+ molecule.yml +`を編集しましょう:

nano molecule/default/molecule.yml

強調表示されたプラットフォーム情報を追加します。

〜/ ansible-apache / molecule / default / molecule.yml

---
dependency:
 name: galaxy
driver:
 name: docker
lint:
 name: yamllint
platforms:



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

ファイルの内容を削除して、テストを最初から作成できるようにします。

必要なPythonモジュールをインポートすることから始めます。

〜/ ansible-apache / molecule / default / tests / test_default.py

import os
import pytest

import testinfra.utils.ansible_runner

これらのモジュールは次のとおりです。

  • + os +:このビルトインPythonモジュールは、オペレーティングシステムに依存する機能を有効にし、Pythonが基になるオペレーティングシステムとインターフェイスできるようにします。

  • + pytest +:https://docs.pytest.org/en/latest/ [+ pytest +]モジュールはテストの作成を可能にします。

  • + testinfra.utils.ansible_runner +:このTestinfraモジュールは、コマンドの実行にhttps://testinfra.readthedocs.io/en/latest/backends.html#ansible[Ansible as the backend]を使用します。

モジュールのインポートの下に、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

テストはhttps://docs.pytest.org/en/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions [`+ pytest.mark.parametrize `デコレーター]で始まります。テストの引数をパラメーター化します。 この最初のテストは、パラメーターとして ` test_pkg `を使用して、 ` httpd `および ` firewalld +`パッケージの存在をテストします。

次のテストでは、「+ 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 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

破棄アクションが完了すると、テストは_dependency_に進みます。 このアクションを使用すると、ロールで必要な場合にhttps://galaxy.ansible.com/ [+ ansible-galaxy +]から依存関係をプルできます。 この場合、私たちの役割は何もしません:

Output...
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.

次のテストアクションは_syntax_チェックで、デフォルトの `+ 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

カバレッジの後、テストは_idempotence_に進みます。 このアクションは、多重呼び出しのプレイブックをテストして、複数の実行で予期しない変更が行われないことを確認します。

Output...
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.

次のテストアクションは_side-effect_アクションです。 これにより、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 linux -- Python 3.6.5, pytest-3.7.3, py-1.5.4, 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 41.05 seconds ===========================

最後に、Moleculeはテスト中に完了したインスタンスを_destroys_し、それらのインスタンスに割り当てられたネットワークを削除します。

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およびTravis CIとhttps://www.digitalocean.com/community/tutorials/how-to-implement-continuous-testing-of-ansible-roles-using-molecule-andを使用して、継続的なテストをワークフローに統合できます。 -travis-ci-on-ubuntu-18-04 [Ubuntu 18.04でMoleculeおよびTravis CIを使用してAnsibleロールの連続テストを実装する方法]チュートリアル。

公式のhttps://molecule.readthedocs.io/en/latest/[Molecule documentation]は、Moleculeの使用方法を学ぶのに最適なリソースです。