著者は、Write for DOnationsプログラムの一部として寄付を受け取るためにMozilla Foundationを選択しました。
前書き
Ansibleでの単体テストは、役割が意図したとおりに機能することを確認するための鍵です。 Moleculeを使用すると、さまざまな環境に対して役割をテストするシナリオを指定できるため、このプロセスが簡単になります。 Moleculeは、内部でAnsibleを使用して、構成された環境に役割をデプロイするプロビジョナーに役割をオフロードし、検証者(Testinfraなど)を呼び出して構成のドリフトをチェックします。 これにより、ロールがその特定のシナリオで環境に対して予想されるすべての変更を行ったことを確認できます。
このガイドでは、Apacheをホストにデプロイし、CentOS 7でfirewalldを構成するAnsibleロールを作成します。 この役割が意図したとおりに機能することをテストするには、Dockerをドライバーとして使用し、サーバーの状態をテストするためのPythonライブラリであるTestinfraを使用してMoleculeでテストを作成します。 MoleculeはDockerコンテナーをプロビジョニングしてロールをテストし、Testinfraはサーバーが意図したとおりに構成されていることを確認します。 完了したら、環境全体のビルド用に複数のテストケースを作成し、Moleculeを使用してこれらのテストを実行できます。
前提条件
このガイドを始める前に、次のものが必要です。
-
Ubuntu 18.04サーバー1台。 Initial Server Setup with Ubuntu 18.04ガイドの手順に従って、root以外のsudoユーザーを作成し、パスワードなしでサーバーに接続できることを確認します。
-
サーバーにインストールされたDocker。 root以外のユーザーを
docker
グループに追加するなど、How To Install and Use Docker on Ubuntu 18.04の手順1と2に従います。 -
Python 3と
venv
がサーバーにインストールされ、構成されています。 ガイダンスについては、How To Install Python 3 and Set Up a Programming Environment on an Ubuntu 18.04 Serverに従ってください。 -
Ansibleプレイブックに精通している。 レビューについては、Configuration Management 101: Writing Ansible Playbooksを参照してください。
[[step-1 -—- preparing-the-environment]] ==ステップ1-環境の準備
前提条件を満たしている場合は、Python 3、venv
、Dockerがインストールされ、正しく構成されている必要があります。 AnsibleをMoleculeでテストするための仮想環境を作成することから始めましょう。
非rootユーザーとしてログインし、新しい仮想環境を作成することから始めます。
python3 -m venv my_env
それをアクティブにして、アクションがその環境に制限されるようにします。
source my_env/bin/activate
次に、アクティブ化された環境で、wheel
パッケージをインストールします。これは、pip
がAnsibleのインストールに使用するbdist_wheel
setuptools
拡張機能を提供します。
python3 -m pip install wheel
これで、molecule
およびdocker
をpip
とともにインストールできます。 AnsibleはMoleculeの依存関係として自動的にインストールされます。
python3 -m pip install molecule docker
これらの各パッケージの機能は次のとおりです。
-
molecule
:これは、ロールのテストに使用するメインのMoleculeパッケージです。molecule
をインストールすると、Ansibleが他の依存関係とともに自動的にインストールされ、Ansibleプレイブックを使用して役割とテストを実行できるようになります。 -
docker
:このPythonライブラリは、MoleculeがDockerとインターフェイスするために使用します。 Dockerをドライバーとして使用しているため、これが必要になります。
次に、Moleculeでロールを作成しましょう。
[[step-2 -—- creating-a-role-in-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を構成します。
[[step-3 -—- configuring-apache-and-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つのタスクが含まれています。
-
"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
Managed by Ansible
ファイルを保存して閉じます。
ロールを完了するための最後のステップは、変数ファイルを作成することです。このファイルは、パッケージとサービスの名前をメインのロールプレイブックに提供します。
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
。
[.note]#Note:変数ファイルに空白行がないことを確認してください。空白行がない場合、リンティング中にテストが失敗します。
#
役割の作成が完了したので、Moleculeが意図したとおりに機能するかどうかをテストするように設定します。
[[step-4 -—- formifying-the-role-for-running-tests]] ==ステップ4—テストを実行するためのロールの変更
この場合、Moleculeの構成には、Molecule構成ファイルmolecule.yml
を変更してプラットフォーム仕様を追加することが含まれます。 httpd
systemdサービスを構成して開始する役割をテストしているため、systemdが構成され、特権モードが有効になっているイメージを使用する必要があります。 このチュートリアルでは、milcom/centos7-systemd
イメージavailable on 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:
- 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がコンテナーに対して実行するテストケースの作成に移りましょう。
[[step-5 --- writing-test-cases]] ==ステップ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
ファイルの内容を削除して、テストを最初から作成できるようにします。
[.note]#Note:テストを作成するときは、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 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
テストはpytest.mark.parametrize
decoratorで始まります。これにより、テストの引数をパラメーター化できます。 この最初のテストでは、httpd
および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", ""),
("/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", ""),
("/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 ---分子を使用した役割のテスト]] ==ステップ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に進みます。 このアクションにより、ロールで依存関係が必要な場合に、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 ===========================
Verifier completed successfully.
最後に、Moleculedestroysは、テスト中に完了したインスタンスを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がサポートするプロバイダーでロールをテストするために使用できる高度に構成可能なツールです。 また、独自のインフラストラクチャに対するテストを自動化して、ロールが常に最新かつ機能していることを確認することもできます。 How To Implement Continuous Testing of Ansible Roles Using Molecule and Travis CI on Ubuntu 18.04チュートリアルでMoleculeとTravis CIを使用して、継続的テストをワークフローに統合できます。
公式のMolecule documentationは、Moleculeの使用方法を学ぶための最良のリソースです。