Ansible Moleculeを使ってテストを自動化してみよう

#目次
<ul>
<li><a href="#1-molecule%E3%81%A8%E3%81%AF">1. Moleculeとは</a></li>
<li><a href="#2-%E6%A4%9C%E8%A8%BC%E7%92%B0%E5%A2%83">2. 検証環境</a></li>
<li><a href="#3-%E5%8F%82%E7%85%A7%E8%B3%87%E6%96%99">3. 参考資料</a></li>
<li><a href="#4-molecule%E3%81%AE%E6%A7%8B%E7%AF%89">4. Moleculeの構築</a></li>
<li><a href="#5-molecule%E3%81%AE%E6%93%8D%E4%BD%9C%E6%89%8B%E9%A0%86">5. Moleculeの操作手順</a>
    <ul>
    <li><a href="#51-role%E3%81%AE%E4%BD%9C%E6%88%90">5.1. roleの作成</a></li>
    <li><a href="#52-molecule-lint">5.2. Molecule lint</a></li>
    <li><a href="#53-%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%81%AE%E7%A2%BA%E8%AA%8D">5.3. インスタンスの確認</a></li>
    <li><a href="#54-molecule-create">5.4. molecule create</a></li>
    <li><a href="#55-molecule-login">5.5. molecule login</a></li>
    <li><a href="#56-molecule-converge">5.6. molecule converge</a></li>
    <li><a href="#57-molecule-destroy">5.7. molecule destroy</a></li>
    </ul>
    </li>
<li><a href="#6-molecule-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%BE%E3%81%A8%E3%82%81">6. molecule コマンドまとめ</a></li>
<li><a href="#7-%E5%AE%9F%E8%A1%8C%E3%81%8C%E5%A4%B1%E6%95%97%E3%81%97%E3%81%9F%E3%82%BF%E3%82%B9%E3%82%AF">7. 実行が失敗したタスク</a></li>
<li><a href="#8-%E6%8A%80%E8%A1%93%E3%83%96%E3%83%AD%E3%82%B0">8. 技術ブログ</a></li>
</ul>


#1. Moleculeとは
MoleculeはAnsible roleを開発したりテストしたりするツールです。
コマンドを実行することにより、作成したroleの構文チェック、実行環境構築、実行テスト、実行環境削除まで行ってくれます。
実行環境構築するには、『ドライバー』というものが必要になります。

実際にMoleculeを使用しroleの作成、実行環境の作成、role実行テスト、実行環境の削除の検証した結果を記録します。
ドライバーは『docker』を使用します。

#2. 検証環境
<ul>
  <li>CentOS 7.7</li>
  <li>Ansible 2.9.6</li>
  <li>Molecule 3.0.2</li>
  <li>Python 3.6.8</li>
  <li>Docker 1.13.1</li>
</ul>

#3. 参照資料
Molecule公式ページです。
https://molecule.readthedocs.io/en/latest/
Molecule2系3系の違いについて参考にさせていただきました。
https://qiita.com/answer_d/items/78b047eb5708dda1375d
書籍では『Ansible 構築・運用ガイドブック』に記載されていました。

#4. Moleculeの構築
epel-releaseのインストール

~~~
[root@Kensyo ~]# yum install -y epel-release
~~~

パッケージのインストール

~~~
[root@Kensyo ~]# yum install -y gcc python3-pip python3-devel openssl-devel libselinux-python
~~~

dockerのインストール

~~~
[root@Kensyo ~]# yum install -y docker
~~~

dockerの起動・自動起動を有効

~~~
[root@Kensyo ping]# systemctl start docker
[root@Kensyo ping]# systemctl enable docker
[root@Kensyo ping]# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
~~~

setuptoolsの更新

~~~
[root@Kensyo ~]# python3 -m pip install --upgrade setuptools
~~~

moleculeをインストール

~~~
[root@Kensyo ~]# python3 -m pip install molecule
[root@Kensyo ~]# molecule --version
molecule 3.0.2
   ansible==2.9.6 python==3.6
~~~

dockerドライバーをインストール

~~~
[root@Kensyo ~]# python3 -m pip install molecule[docker]
~~~

構文チェックツールをインストール

~~~
[root@Kensyo ~]# python3 -m pip install "molecule[lint]"
~~~

#5. Moleculeの操作手順
##5.1. roleの作成
『molecule init』を実行しロールを作成します。
ロールの作成は[molecule init role “role名”]で作成されます。
※コメント※
既存のロールを使用する場合は[molecule init scenario -r “role名”]を実行してください。
実行場所は対象ロールに移動して実行してください。


~~~
[root@Kensyo ~]# mkdir roles
[root@Kensyo ~]# cd roles
[root@Kensyo roles]# molecule init role my-role-name
--> Initializing new role my-role-name...
Initialized role in /root/roles/my-role-name successfully.
[root@Kensyo roles]# cd my-role-name
[root@Kensyo my-role-name]# ls
README.md  defaults  files  handlers  meta  molecule  tasks  templates  tests  vars
~~~

不要ディレクトリを削除します。
「meta」ディレクトリ配下のmain.ymlが構文チェックでエラーとなるため削除してください。
※コメント※
本手順では「meta」ディレクトリは不要な要素のため削除しております。必要に応じて修正してください。

~~~
[root@Kensyo my-role-name]# rm -rf meta/
~~~

tasks/main.ymlにコメントが記載されていますので修正してください。
今回の検証はhttpdのインストールを行うタスクを作成します。
記載内容はcatコマンドの出力内容を確認してください。

~~~
[root@Kensyo my-role-name]# cd tasks
[root@Kensyo tasks]# vi main.yml
[root@Kensyo tasks]# cat main.yml
---
- name: install httpd
  yum:
    name: httpd
    state: present
[root@Kensyo tasks]# cd /root/roles/my-role-name/
[root@Kensyo my-role-name]# pwd
/root/roles/my-role-name
~~~

molecule.ymlに記載を修正します。
内容はcatコマンド出力内容を確認してください。
molecule バージョン3系でlintはデフォルトで無効となっており、記載されていない為追記しています。moleculeバージョン2系に比べ記載方法も変わっています。

~~~
[root@Kensyo my-role-name]# cd molecule/default/
[root@Kensyo my-role-name]# cp -p molecule.yml molecule.yml.org
[root@Kensyo default]# vi molecule.yml
[root@Kensyo default]# cat molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:7
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible
lint: |
   yamllint .
   ansible-lint .
   flake8
~~~

##5.2. Molecule lint
『molecule lint』を実行し構文をチェックします。
エラーが表示されなければ問題ありません。

~~~
[root@Kensyo default]# cd /root/roles/my-role-name/
[root@Kensyo my-role-name]# molecule lint
--> Test matrix

└─default
   ├─ dependency
   └─ lint

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'
--> Executing: yamllint .
ansible-lint .
flake8
~~~

構文に不備がある場合は以下のように表示されます。

~~~
[root@Kensyo my-role-name]# cat tasks/main.yml
---
- name: install httpd
   yum:
   name: httpd
   state: present
[root@Kensyo my-role-name]# molecule lint
--> Test matrix

└─default
   ├─ dependency
   └─ lint

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'
--> Executing: yamllint .
ansible-lint .
flake8
./tasks/main.yml
  3:7       error    syntax error: mapping values are not allowed here (syntax)

Syntax Error while loading YAML.
  mapping values are not allowed here

The error appears to be in '/root/roles/my-role-name/tasks/main.yml': line 3, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

- name: install httpd
   yum:
      ^ here
~~~

##5.3. インスタンスの確認
インスタンスの状態を確認します。『Created』『Converged』が『false』な状態です。

~~~
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          false       false
~~~

##5.4. molecule create
『molecule create』を実行しコンテナの作成を行います。
[failed=1]エラー終了していないことを確認してください。

~~~
[root@Kensyo my-role-name]# molecule create
--> Test matrix

└─default
   ├─ dependency
   ├─ create
   └─ prepare

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'create'
--> Sanity checks: 'docker'

    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)

    TASK [Check presence of custom Dockerfiles] ************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Create Dockerfiles from image names] *************************************
    skipping: [localhost] => (item=None)

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image (new)] *********************************
    skipping: [localhost] => (item=molecule_local/docker.io/pycontribs/centos:7)

    TASK [Create docker network(s)] ************************************************

    TASK [Determine the CMD directives] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=instance)

    TASK [Wait for instance(s) creation to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
~~~

インスタンスの状態を確認します。Createdが『true』になっています。コンテナが作成されている状態です。

~~~
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          true       false
~~~

##5.5. molecule login
『molecule login』を実行しコンテナにログインします。ロール実行前の環境を確認するため、
実行予定のhttpdパッケージがインストールされているか確認してください。

~~~
[root@Kensyo my-role-name]# molecule login
[root@instance /]#
[root@instance /]# rpm -qa | grep httpd
[root@instance /]#
~~~

##5.6. molecule converge
molecule convergeを実行し、ロールの実行を行います。
「failed=1」エラー終了していないことを確認してください。

~~~
[root@Kensyo ping]# molecule converge
--> Test matrix

└─default
   ├─ dependency
   ├─ create
   ├─ prepare
   └─ converge

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'create'
--> Sanity checks: 'docker'


    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)

    TASK [Check presence of custom Dockerfiles] ************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Create Dockerfiles from image names] *************************************
    skipping: [localhost] => (item=None)

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image (new)] *********************************
    skipping: [localhost] => (item=molecule_local/docker.io/pycontribs/centos:7)

    TASK [Create docker network(s)] ************************************************

    TASK [Determine the CMD directives] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=instance)

    TASK [Wait for instance(s) creation to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'

    PLAY [Converge] ****************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [Include my-role-name] ****************************************************

    TASK [my-role-name : install httpd] ********************************************
    changed: [instance]

    PLAY RECAP *********************************************************************
    instance                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
~~~

インスタンスの状態を確認します。Convergedが「true」になっているため、ロールが実行された状態です。

~~~
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          true       true
~~~

molecule loginを実行しコンテナ内でパッケージがインストールされているか確認します。

~~~
[root@Kensyo my-role-name]# molecule login
[root@instance /]#
[root@instance /]# rpm -qa | grep httpd
httpd-2.4.6-90.el7.centos.x86_64
httpd-tools-2.4.6-90.el7.centos.x86_64
[root@instance /]# exit
[root@Kensyo my-role-name]#
~~~

##5.7. molecule destroy
molecule destroyを実行し、作成したコンテナを削除します。

~~~
[root@Kensyo my-role-name]# molecule destroy
--> Test matrix

└─default
   ├─ dependency
   ├─ cleanup
   └─ destroy

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
--> Sanity checks: 'docker'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=instance)

    TASK [Wait for instance(s) deletion to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

--> Pruning extra files from scenario ephemeral directory
~~~

#6. Molecule コマンドまとめ
molecule コマンドはたくさんありますので検証で使用したコマンドをまとめます。
***
『molecule create』コマンドはインスタンスを作成します。
『molecule converge』コマンドはインスタンスの作成とロールの実行を行います。
『molecule login』コマンドは作成したインスタンスにログインします。
『molecule destroy』コマンドは作成したインスタンスを削除します。
『molecule converge』や『molecule test』を複数回行うことで冪等性のチェックが可能です。
『molecule test』コマンドはインスタンスの作成から削除までの一連の工程を実行します。
Molecule コマンドを実行する際にデバッグを出力することが可能です。
例：　[molecule --debug create]
その他コマンドにつきましては、公式サイト『Command Line Reference』をご参照ください。
***

#7. 実行が失敗したタスク
/etc/hosts の修正を行うタスクを記載し実行したところ、タスクが失敗しエラーとなりました。

~~~
(中略)
    PLAY RECAP *********************************************************************
    instance                   : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

            "seuser": null,
            "src": "/root/.ansible/tmp/ansible-tmp-1585125530.483603-191325592318519/source",
            "unsafe_writes": null,
            "validate": null
        }
    },
    "msg": "Unable to make /root/.ansible/tmp/ansible-tmp-1585125530.483603-191325592318519/source into to /etc/hosts, failed final rename from /etc/.ansible_tmp9N6Mzahosts: [Errno 16] Device or resource busy"
}

ERROR:
~~~
Dockerを使用していることによるエラー内容です。
ドライバー以外(例えばvmwareなど)を対象に作成したrolesは場合によりエラーとなるかもしれません。
Moleculeを実践的に使用する方法としては、
dockerを対象に構築したrolesをテストするために、dockerドライバーを使い『Molecule』を使用するのがよさそうです。
Molecule2系では複数ドライバーが存在したので、3系でも今後使用できる環境が増えるといいですね。

#8. 技術ブログ
[会社Webサイト](http://c.itdo.jp/author/tsukada/)にて技術ブログを掲載しております。ご覧いただければ幸いです。