Ubuntu 18.04でMoleculeおよびTravis CIを使用してAnsibleロールの継続的なテストを実装する方法

著者は、Write for DOnationsプログラムの一部として寄付を受け取るためにMozilla Foundationを選択しました。

前書き

Ansibleは、YAMLテンプレートを使用して、ホストで実行されるタスクのリストを定義するエージェントレス構成管理ツールです。 Ansibleでは、rolesは変数、タスク、ファイル、テンプレート、およびモジュールのコレクションであり、これらを一緒に使用して、単一の複雑な機能を実行します。

Moleculeは、Ansibleロールの自動テストを実行するためのツールであり、一貫して適切に記述され、維持されているロールの開発をサポートするように特別に設計されています。 Moleculeのユニットテストにより、開発者は複数の環境に対して異なるパラメーターでロールを同時にテストできます。 開発者は、頻繁に変更されるコードに対してテストを継続的に実行することが重要です。このワークフローにより、コードライブラリを更新してもロールが引き続き機能します。 Travis CIなどの継続的インテグレーションツールを使用してMoleculeを実行すると、テストを継続的に実行できるため、コードへの貢献によって重大な変更が発生することはありません。

このチュートリアルでは、UbuntuサーバーとCentOSサーバーにApache Webサーバーとファイアウォールをインストールして構成する、事前に作成されたベースロールを使用します。 次に、そのロールのMoleculeシナリオを初期化してテストを作成し、ロールがターゲット環境で意図したとおりに実行されることを確認します。 Moleculeを構成したら、Travis CIを使用して、新しく作成したロールを継続的にテストします。 コードに変更が加えられるたびに、Travis CIはmolecule testを実行して、役割が引き続き正しく実行されることを確認します。

前提条件

このチュートリアルを始める前に、次のものが必要です。

[[step-1 -—- forking-the-base-role-repository]] ==ステップ1—基本ロールリポジトリをフォークする

Apacheをインストールし、DebianベースおよびRedHatベースのディストリビューションにファイアウォールを構成するansible-apacheと呼ばれる事前に作成されたロールを使用します。 このロールを分岐してベースとして使用し、その上でMoleculeテストをビルドします。 フォークを使用すると、リポジトリのコピーを作成して、元のプロジェクトを変更せずにリポジトリに変更を加えることができます。

ansible-apacheロールのフォークを作成することから始めます。 ansible-apacheリポジトリに移動し、Forkボタンをクリックします。

リポジトリをフォークすると、GitHubからフォークのページに移動します。 これは基本リポジトリのコピーですが、自分のアカウントにあります。

緑色のClone or Downloadボタンをクリックすると、Clone with HTTPSのボックスが表示されます。

リポジトリに表示されているURLをコピーします。 次のステップでこれを使用します。 URLは次のようになります。

https://github.com/username/ansible-apache.git

usernameをGitHubユーザー名に置き換えます。

フォークをセットアップしたら、サーバー上でフォークを複製し、次のセクションでロールの準備を開始します。

[[step-2 -—- preparing-your-role]] ==ステップ2—役割の準備

前提条件のHow To Test Ansible Roles with Molecule on Ubuntu 18.04のステップ1に従うと、MoleculeとAnsibleが仮想環境にインストールされます。 この仮想環境を使用して、新しい役割を開発します。

まず、以下を実行して、前提条件に従って作成した仮想環境をアクティブにします。

source my_env/bin/activate

次のコマンドを実行して、手順1でコピーしたURLを使用してリポジトリをクローニングします。

git clone https://github.com/username/ansible-apache.git

出力は次のようになります。

OutputCloning into 'ansible-apache'...
remote: Enumerating objects: 16, done.
remote: Total 16 (delta 0), reused 0 (delta 0), pack-reused 16
Unpacking objects: 100% (16/16), done.

新しく作成したディレクトリに移動します。

cd ansible-apache

ダウンロードした基本役割は、次のタスクを実行します。

  • Includes variables:役割は、ホストの分布に従って、必要なすべてのvariablesを含めることから始まります。 Ansibleは変数を使用して、異なるシステム間の格差を処理します。 Ubuntu18.04とCentOS7をホストとして使用しているため、ロールはOSファミリがそれぞれDebianとRed Hatであり、vars/Debian.ymlvars/RedHat.ymlの変数を含むことを認識します。

  • Includes distribution-relevant tasks:これらのタスクにはtasks/install-Debian.ymltasks/install-RedHat.ymlが含まれます。 指定されたディストリビューションに応じて、関連するパッケージがインストールされます。 Ubuntuの場合、これらのパッケージはapache2ufwです。 CentOSの場合、これらのパッケージはhttpdfirewalldです。

  • Ensures latest index.html is present:このタスクは、ApacheがWebサーバーのホームページとして使用するテンプレートtemplates/index.html.j2をコピーします。

  • Starts relevant services and enables them on boot:最初のタスクの一部としてインストールされた必要なサービスを開始して有効にします。 CentOSの場合、これらのサービスはhttpdfirewalldであり、Ubuntuの場合、これらはapache2ufwです。

  • Configures firewall to allow traffic:これにはtasks/configure-Debian-firewall.ymlまたはtasks/configure-RedHat-firewall.ymlのいずれかが含まれます。 Ansibleは、FirewalldまたはUFWのいずれかをファイアウォールとして構成し、httpサービスをホワイトリストに登録します。

この役割がどのように機能するか理解できたので、Moleculeを設定してテストします。 これらのタスクのテストケースを作成して、それらが行う変更をカバーします。

[[step-3 -—- writing-your-tests]] ==ステップ3—テストを書く

基本ロールが意図したとおりにタスクを実行することを確認するには、Moleculeシナリオを開始し、ターゲット環境を指定して、3つのカスタムテストファイルを作成します。

次のコマンドを使用して、このロールの分子シナリオを初期化することから始めます。

molecule init scenario -r ansible-apache

次の出力が表示されます。

Output--> Initializing new scenario default...
Initialized scenario in /home/sammy/ansible-apache/molecule/default successfully.

Molecule構成ファイルにプラットフォームとして含めることにより、CentOSとUbuntuをターゲット環境として追加します。 これを行うには、テキストエディタを使用してmolecule.ymlファイルを編集します。

nano molecule/default/molecule.yml

次の強調表示されたコンテンツをMolecule構成に追加します。

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

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: centos7
    image: milcom/centos7-systemd
    privileged: true
  - name: ubuntu18
    image: solita/ubuntu-systemd
    command: /sbin/init
    privileged: true
    volumes:
      - /lib/modules:/lib/modules:ro
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8

ここでは、systemdサービスを使用しているため、特権モードで起動される2つのターゲットプラットフォームを指定しています。

  • centos7は最初のプラットフォームであり、milcom/centos7-systemdイメージを使用します。

  • ubuntu18は2番目のプラットフォームであり、solita/ubuntu-systemdイメージを使用します。 特権モードを使用して必要なカーネルモジュールをマウントすることに加えて、起動時に/sbin/initを実行して、iptablesが稼働していることを確認します。

ファイルを保存して終了します。

特権コンテナーの実行の詳細については、official Molecule documentationにアクセスしてください。

デフォルトのMoleculeテストファイルを使用する代わりに、ターゲットプラットフォームごとに1つのカスタムテストファイルを作成し、すべてのプラットフォームに共通のテストを作成するために1つのファイルを作成します。 次のコマンドを使用して、シナリオのデフォルトのテストファイルtest_default.pyを削除することから始めます。

rm molecule/default/tests/test_default.py

これで、ターゲットプラットフォームごとに3つのカスタムテストファイルtest_common.pytest_Debian.py、およびtest_RedHat.pyの作成に進むことができます。

最初のテストファイルtest_common.pyには、各ホストが実行する共通のテストが含まれます。 共通のテストファイルtest_common.pyを作成および編集します。

nano molecule/default/tests/test_common.py

ファイルに次のコードを追加します。

~/ansible-apache/molecule/default/tests/test_common.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('file, content', [
  ("/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)

test_common.pyファイルに、必要なライブラリをインポートしました。 また、test_files()というテストを作成しました。これは、役割が実行するディストリビューション間の唯一の共通タスクである、テンプレートをWebサーバーのホームページとしてコピーすることを保持します。

次のテストファイルtest_Debian.pyは、Debianディストリビューションに固有のテストを保持しています。 このテストファイルは、特にUbuntuプラットフォームを対象としています。

次のコマンドを実行して、Ubuntuテストファイルを作成および編集します。

nano molecule/default/tests/test_Debian.py

これで、必要なライブラリをインポートし、ubuntu18プラットフォームをターゲットホストとして定義できます。 このファイルの先頭に次のコードを追加します。

~/ansible-apache/molecule/default/tests/test_Debian.py

import os
import pytest

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('ubuntu18')

次に、同じファイルにtest_pkg()テストを追加します。

test_pkg()テストを定義する次のコードをファイルに追加します。

~/ansible-apache/molecule/default/tests/test_Debian.py

...
@pytest.mark.parametrize('pkg', [
    'apache2',
    'ufw'
])
def test_pkg(host, pkg):
    package = host.package(pkg)

    assert package.is_installed

このテストでは、apache2およびufwパッケージがホストにインストールされているかどうかを確認します。

[.note]#Note: Moleculeテストファイルに複数のテストを追加する場合は、各テストの間に2つの空白行があることを確認してください。そうしないと、Moleculeから構文エラーが発生します。

次のテストtest_svc()を定義するには、ファイルのtest_pkg()テストの下に次のコードを追加します。

~/ansible-apache/molecule/default/tests/test_Debian.py

...
@pytest.mark.parametrize('svc', [
    'apache2',
    'ufw'
])
def test_svc(host, svc):
    service = host.service(svc)

    assert service.is_running
    assert service.is_enabled

test_svc()は、apache2およびufwサービスが実行され、有効になっているかどうかを確認します。

最後に、最後のテストtest_ufw_rules()test_Debian.pyファイルに追加します。

ファイルのtest_svc()テストで次のコードを追加して、test_ufw_rules()を定義します。

~/ansible-apache/molecule/default/tests/test_Debian.py

...
@pytest.mark.parametrize('rule', [
    '-A ufw-user-input -p tcp -m tcp --dport 80 -j ACCEPT'
])
def test_ufw_rules(host, rule):
    cmd = host.run('iptables -t filter -S')

    assert rule in cmd.stdout

test_ufw_rules()は、ファイアウォール構成がApacheサービスによって使用されるポートでのトラフィックを許可することを確認します。

これらの各テストを追加すると、test_Debian.pyファイルは次のようになります。

~/ansible-apache/molecule/default/tests/test_Debian.py

import os
import pytest

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('ubuntu18')


@pytest.mark.parametrize('pkg', [
    'apache2',
    'ufw'
])
def test_pkg(host, pkg):
    package = host.package(pkg)

    assert package.is_installed


@pytest.mark.parametrize('svc', [
    'apache2',
    'ufw'
])
def test_svc(host, svc):
    service = host.service(svc)

    assert service.is_running
    assert service.is_enabled


@pytest.mark.parametrize('rule', [
    '-A ufw-user-input -p tcp -m tcp --dport 80 -j ACCEPT'
])
def test_ufw_rules(host, rule):
    cmd = host.run('iptables -t filter -S')

    assert rule in cmd.stdout

test_Debian.pyファイルには、test_pkg()test_svc()、およびtest_ufw_rules()の3つのテストが含まれるようになりました。

test_Debian.pyを保存して終了します。

次に、test_RedHat.pyテストファイルを作成します。このファイルには、CentOSプラットフォームを対象とするRedHatディストリビューションに固有のテストが含まれています。

次のコマンドを実行して、CentOSテストファイルtest_RedHat.pyを作成および編集します。

nano molecule/default/tests/test_RedHat.py

Ubuntuテストファイルと同様に、test_RedHat.pyファイルに含める3つのテストを作成します。 テストコードを追加する前に、ファイルの先頭に次のコードを追加することで、必要なライブラリをインポートし、centos7プラットフォームをターゲットホストとして定義できます。

~/ansible-apache/molecule/default/tests/test_RedHat.py

import os
import pytest

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('centos7')

次に、test_pkg()テストを追加します。このテストでは、httpdおよびfirewalldパッケージがホストにインストールされているかどうかを確認します。

ライブラリインポートのコードに従って、test_pkg()テストをファイルに追加します。 (繰り返しますが、それぞれの新しいテストの前に2つの空白行を含めることを忘れないでください。)

~/ansible-apache/molecule/default/tests/test_RedHat.py

...
@pytest.mark.parametrize('pkg', [
    'httpd',
    'firewalld'
])
def test_pkg(host, pkg):
    package = host.package(pkg)

      assert package.is_installed

これで、test_svc()テストを追加して、httpdおよびfirewalldサービスが実行されて有効になっていることを確認できます。

test_pkg()テストに続いて、test_svc()コードをファイルに追加します。

~/ansible-apache/molecule/default/tests/test_RedHat.py

...
@pytest.mark.parametrize('svc', [
    'httpd',
    'firewalld'
])
  def test_svc(host, svc):
    service = host.service(svc)

    assert service.is_running
    assert service.is_enabled

test_RedHat.pyファイルの最終テストはtest_firewalld()になり、Firewalldにhttpサービスがホワイトリストに登録されているかどうかがチェックされます。

test_svc()コードの後に​​、ファイルにtest_firewalld()テストを追加します。

~/ansible-apache/molecule/default/tests/test_RedHat.py

...
@pytest.mark.parametrize('file, content', [
    ("/etc/firewalld/zones/public.xml", "")
])
def test_firewalld(host, file, content):
    file = host.file(file)

    assert file.exists
    assert file.contains(content)

ライブラリをインポートして3つのテストを追加すると、test_RedHat.pyファイルは次のようになります。

~/ansible-apache/molecule/default/tests/test_RedHat.py

import os
import pytest

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('centos7')


@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", "")
])
def test_firewalld(host, file, content):
    file = host.file(file)

    assert file.exists
    assert file.contains(content)

これで、test_common.pytest_Debian.py、およびtest_RedHat.pyの3つのファイルすべてでテストの記述が完了したので、役割をテストする準備が整いました。 次のステップでは、Moleculeを使用して、新しく構成されたロールに対してこれらのテストを実行します。

[[step-4 -—- testing-against-your-role]] ==ステップ4—あなたの役割に対するテスト

ここで、Moleculeを使用して、基本ロールansible-apacheに対して新しく作成したテストを実行します。 テストを実行するには、次のコマンドを使用します。

molecule test

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.7, pytest-4.1.1, py-1.7.0, pluggy-0.8.1
    rootdir: /home/sammy/ansible-apache/molecule/default, inifile:
    plugins: testinfra-1.16.0
collected 12 items

    tests/test_common.py ..                                                  [ 16%]
    tests/test_RedHat.py .....                                               [ 58%]
    tests/test_Debian.py .....                                               [100%]

    ========================== 12 passed in 80.70 seconds ==========================
Verifier completed successfully.

出力にVerifier completed successfullyが表示されます。これは、ベリファイアがすべてのテストを実行し、それらを正常に返したことを意味します。

役割の開発が正常に完了したので、Gitに変更をコミットし、継続的なテストのためにTravis CIをセットアップできます。

[[step-5 --- using-git-to-share-your-updated-role]] ==ステップ5—Gitを使用して更新された役割を共有する

このチュートリアルでは、これまで、ansible-apacheという役割のクローンを作成し、UbuntuおよびCentOSホストに対して機能することを確認するためのテストを追加しました。 更新されたロールをパブリックと共有するには、これらの変更をコミットしてフォークにプッシュする必要があります。

次のコマンドを実行してファイルを追加し、行った変更をコミットします。

git add .

このコマンドは、現在のディレクトリで変更したすべてのファイルをステージング領域に追加します。

また、正常にコミットするには、名前と電子メールアドレスをgit configに設定する必要があります。 これを行うには、次のコマンドを使用します。

git config user.email "[email protected]"
git config user.name "John Doe"

変更したファイルをリポジトリにコミットします。

git commit -m "Configured Molecule"

次の出力が表示されます。

Output[master b2d5a5c] Configured Molecule
 8 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100644 molecule/default/Dockerfile.j2
 create mode 100644 molecule/default/INSTALL.rst
 create mode 100644 molecule/default/molecule.yml
 create mode 100644 molecule/default/playbook.yml
 create mode 100644 molecule/default/tests/test_Debian.py
 create mode 100644 molecule/default/tests/test_RedHat.py
 create mode 100644 molecule/default/tests/test_common.py

これは、変更を正常にコミットしたことを意味します。 次のコマンドを使用して、これらの変更をフォークにプッシュします。

git push -u origin master

GitHub資格情報のプロンプトが表示されます。 これらの資格情報を入力すると、コードがリポジトリにプッシュされ、次の出力が表示されます。

OutputCounting objects: 13, done.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (13/13), 2.32 KiB | 2.32 MiB/s, done.
Total 13 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 2 local objects.
To https://github.com/username/ansible-apache.git
   009d5d6..e4e6959  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

フォークのリポジトリ(github.com/username/ansible-apache)に移動すると、ファイルに加えた変更を反映したConfigured Moleculeという新しいコミットが表示されます。

これで、Travis CIを新しいリポジトリに統合して、ロールに加えられた変更が自動的にMoleculeテストをトリガーできるようにすることができます。 これにより、ロールが常にUbuntuおよびCentOSホストで機能するようになります。

[[step-6 -—- integrating-travis-ci]] ==ステップ6— TravisCIの統合

このステップでは、Travis CIをワークフローに統合します。 有効にすると、フォークにプッシュした変更はTravis CIビルドをトリガーします。 これの目的は、寄稿者が変更を加えるたびにTravis CIが常にmolecule testを実行するようにすることです。 重大な変更が行われた場合、Travisはビルドステータスを宣言します。

Travis CIに進んで、リポジトリを有効にします。 GitHubのActivateボタンをクリックできるプロファイルページに移動します。

Travis CIでリポジトリをアクティブ化するための詳細なガイダンスhereを見つけることができます。

Travis CIを機能させるには、そのための指示を含む構成ファイルを作成する必要があります。 Travis構成ファイルを作成するには、サーバーに戻って次のコマンドを実行します。

nano .travis.yml

このチュートリアルで作成した環境を複製するには、Travis構成ファイルでパラメーターを指定します。 次のコンテンツをファイルに追加します。

~/ansible-apache/.travis.yml

---
language: python
python:
  - "2.7"
  - "3.6"
services:
  - docker
install:
  - pip install molecule docker
script:
  - molecule --version
  - ansible --version
  - molecule test

このファイルで指定したパラメーターは次のとおりです。

  • language:言語としてPythonを指定すると、CI環境はpythonキーで指定したPythonバージョンごとに個別のvirtualenvインスタンスを使用します。

  • python:ここでは、TravisがPython2.7とPython3.6の両方を使用してテストを実行することを指定しています。

  • services:Moleculeでテストを実行するにはDockerが必要です。 TravisがCI環境にDockerが存在することを確認する必要があることを指定しています。

  • install:ここでは、Travis CIがvirtualenvで実行する予備インストール手順を指定しています。

    • pip install molecule dockerは、DockerリモートAPIのPythonライブラリとともにAnsibleとMoleculeが存在することを確認します。

  • script:これは、TravisCIが実行する必要のあるステップを指定するためのものです。 ファイルでは、3つのステップを指定しています。

    • molecule --versionは、Moleculeが正常にインストールされている場合、Moleculeバージョンを出力します。

    • ansible --versionは、Ansibleが正常にインストールされている場合、Ansibleのバージョンを出力します。

    • molecule testは、最終的にMoleculeテストを実行します。

molecule --versionおよびansible --versionを指定する理由は、バージョン管理によるansibleまたはmoleculeの設定ミスの結果としてビルドが失敗した場合に、エラーをキャッチするためです。

コンテンツをTravisCI構成ファイルに追加したら、.travis.ymlを保存して終了します。

これで、リポジトリに変更をプッシュするたびに、Travis CIは上記の設定ファイルに基づいて自動的にビルドを実行します。 scriptブロック内のコマンドのいずれかが失敗した場合、TravisCIはビルドステータスをそのように報告します。

ビルドステータスを簡単に確認できるように、ロールのREADMEにビルドステータスを示すバッジを追加できます。 テキストエディタを使用してREADME.mdファイルを開きます。

nano README.md

ビルドステータスを表示するには、README.mdに次の行を追加します。

~/ansible-apache/README.md

[![Build Status](https://travis-ci.org/username/ansible-apache.svg?branch=master)](https://travis-ci.org/username/ansible-apache)

usernameをGitHubユーザー名に置き換えます。 以前に行ったように、変更をコミットしてリポジトリにプッシュします。

まず、次のコマンドを実行して、.travis.ymlREADME.mdをステージング領域に追加します。

git add .travis.yml README.md

次を実行して、リポジトリに変更をコミットします。

git commit -m "Configured Travis"

最後に、次のコマンドを使用して、これらの変更をフォークにプッシュします。

git push -u origin master

GitHubリポジトリに移動すると、最初にbuild: unknownが報告されていることがわかります。

build-status-unknown

数分以内にTravisはビルドを開始し、Travis CI Webサイトで監視できます。 ビルドが成功すると、GitHubはREADMEファイルに配置したバッジを使用して、リポジトリのステータスも報告します。

build-status-passing

Travis CI Webサイトにアクセスして、ビルドの完全な詳細にアクセスできます。

travis-build-status

新しい役割にTravis CIを正常に設定したので、Ansibleの役割に対する変更を継続的にテストおよび統合できます。

結論

このチュートリアルでは、GitHubからApache Webサーバーをインストールおよび構成するロールを分岐し、テストを作成してこれらのテストをUbuntuおよびCentOSを実行するDockerコンテナーで構成することにより、Moleculeの統合を追加しました。 新しく作成したロールをGitHubにプッシュすることで、他のユーザーがロールにアクセスできるようになりました。 貢献者によって役割が変更されると、Travis CIは自動的にMoleculeを実行して役割をテストします。

ロールの作成とMoleculeでのテストに慣れたら、これをAnsible Galaxyと統合して、ビルドが成功するとロールが自動的にプッシュされるようにすることができます。

Related