Ansible MoleculeとTest Kitchenの比較

備忘

# Ansible Moleculeとは

一言で言うとTest KitchenのAnsible版。
Test Kitchenの様に、以下などを行うCI系ユーティリティ。pythonベースでpipにて導入。
- テスト環境デプロイ
- テスト環境へのコード適用
- テスト環境に対する検証実施
- テスト環境削除
- テスト環境へのログイン
- テスト環境の状態確認

kithenCIと異なり、テスト環境への反映はansibleのみが使用可能。

- https://kitchen.ci
- https://molecule.readthedocs.io/en/latest/index.html


# ごく簡単な比較

|          | Test Kitchen | Ansible Molecule |
|:--------:|:----------|:-----|
| 開発     | chef  | redhat  |
| テスト対象  | chef cookbook | ansible role |
| テスト環境 | vagrant+virtualbox, docker, 既存サーバー, aws ec2, ... | docker, vagrant+virtualbox, aws ec2, ... |
| 構成管理ツール | chef, ansible, .. | ansible |
| テストツール | InSpec, ServerSpec, .. | ansible, testinfra, .. |
| コマンド  | $ kitchen | $ molecule |
| chef cookbook/ansible roleのテンプレート作成  | $ chef generate cookbook \<cookbook> | $ molecule init role \<role>  |
| コードlint | ?   | $ molecule lint  |
| テスト環境作成 | $ kitchen create   | $ molecule create  |
| テスト環境への反映 | $ kitchen converge | $ molecule converge  |
| 検証実施 | $ kitchen verify | $ molecule verify |
| 通しで実行(環境は削除) | $ kitchen test | $ molecule test |
| 通しで実行(環境を残す) | $ kitchen test --destroy=never | $ molecule test --destroy never |
| テスト環境状況 | $ kitchen list | $ molecule list |
| テスト環境ログイン | $ kitchen login | $ molecule login |
| 設定ファイル | kitchen.yml | molecule/default/molecule.yml |


ガッツリと影響を受けていますよね。
kitchenCIに慣れた人なら、moleculeに関してもなんとなく感触が掴めると思えました。


# 環境

- macOS 10.15.6
- pyenvによるPython 3.8.2環境
- docker desktop 2.4.0.0

# 導入手順

https://molecule.readthedocs.io/en/latest/installation.html

## パス追加

コマンド類は~/.local/binに導入されたので、pathを通しておく
(ログインシェルはbashを継続使用)

```
$ echo 'export PATH=~/.local/bin:${PATH}' >> ~/.bash_profile
$ . ~/.bash_profile
```

## 導入


--user指定で入れてみる

```
$ python3 -m pip install --user "molecule[lint]"
...
$ molecule --version
molecule 3.0.8
   ansible==2.10.2 python==3.8
```

(意図せず~/.local/binにansible v2.10も導入されたが、いつかは入れなければと思っていたので良い機会と思うこととする。)


moleculeからdockerを使うために以下も導入しておく
(後で出てくる molecule/default/INSTALL.rst にて指示される)

```
$ python3 -m pip install --user 'molecule[docker]'
...
```


# getting started

以下に沿って試してみる。
https://molecule.readthedocs.io/en/latest/getting-started.html

## ロール作成

```
$ molecule init role my-new-role
--> Initializing new role my-new-role...
Initialized role in /Users/xxxx/my-new-role successfully.

$ cd ./my-new-role

$ tree -a .
.
├── .travis.yml
├── .yamllint
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── default
│       ├── INSTALL.rst
│       ├── converge.yml
│       ├── molecule.yml
│       └── verify.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

10 directories, 14 files
```

基本的には、moleculeディレクトリー以外は、ansible-galaxy init my-new-roleにて作成される、ほぼ通常のansible role構成


### molecule/default/molecule.yml

moleculeの構成ファイル
roleをテストするときに使用する各ツールを構成
kitchenCIにおけるkitchen.ymlに相当

```yaml:molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:8
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible
```


いろいろな設定を実際にはどの様に行う必要があるのか要調査
kitchen.ymlではerbも使えたが、その様な柔軟性があるか
等

- 複数テスト環境を制御するには、platforms下のlistに要素を増やす

- テスト環境にvagrant+virtualboxを使用するには、molecule-vagrantを導入し

```
$ python3 -m pip install --user python-vagrant molecule-vagrant
...
```

molecule.ymlを修正する

```yaml:molecule/default/molecule.ymlの編集
driver:
  name: vagrant

platforms:
  - name: instance
    box: fedora/32-cloud-base
    memory: 512
    cpus: 1
```

設定の詳細
https://github.com/ansible-community/molecule-vagrant

- デフォルトではlint:セクションは追記されない。lintもさせるなら以下を追記しておく。

```
lint: |-
  set -e
  yamllint .
  ansible-lint
  flake8
```

参照:
https://molecule.readthedocs.io/en/latest/configuration.html#lint

yamllintのルールは./.yamllintに定義されている。必要に応じてルールを編集する。

yamllintの詳細:
https://yamllint.readthedocs.io/en/latest/

### molecule/default/converge.yml

このロールを呼び出すansible playbook

```yaml:molecule/default/converge.yml
---
- name: Converge
  hosts: all
  tasks:
    - name: "Include my-new-role"
      include_role:
        name: "my-new-role"
```

どこでroles_pathを設定しているのか、などは未調査。

### molecule/default/verify.yml

検証に使用されるplaybookの雛形

```yaml:molecule/default/verify.yml 
---
# This is an example playbook to execute Ansible tests.

- name: Verify
  hosts: all
  tasks:
  - name: Example assertion
    assert:
      that: true
```

以前のデフォルトのverifierはtestinfraだったらしいが、現在のデフォルトはansible(のassertタスク)の様子。
testinfraはAIXなどUNIX系では使えないという理解なので、個人的にはansible assertの方がまし。

## テスト環境作成: molecule create

```
$ molecule create
--> Test matrix

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

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
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:8)

    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).
    FAILED - RETRYING: Wait for instance(s) creation to complete (299 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (298 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (297 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (296 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (295 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (294 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (293 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (292 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (291 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.
```

## テスト環境の状況表示: molecule list

```
$ molecule list
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          true       false
```

dockerコンテナ確認

```
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                     PORTS               NAMES
544a355d38d6        pycontribs/centos:8       "bash -c 'while true…"   2 minutes ago       Up 2 minutes                                   instance
```

## playbook処理を追記して適用: molecule converge

```
$ vi tasks/main.yml
...

$ cat tasks/main.yml
---
# tasks file for my-new-role
- name: Molecule Hello World!
  debug:
    msg: Hello, World!
```

```
$ molecule converge
--> Test matrix

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

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'create'
Skipping, instances already created.
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'
--> Sanity checks: 'docker'

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

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

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

    TASK [my-new-role : Molecule Hello World!] *************************************
    ok: [instance] => {
        "msg": "Hello, World!"
    }

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

```
$ molecule list
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          true       true
```

verifyの項がない。

## テスト環境の検証: molecule verify

テスト環境に対してmolecule/default/verify.yml playbookを適用

```
$ molecule verify
--> Test matrix
    
└── default
    └── verify
    
--> Scenario: 'default'
--> Action: 'verify'
--> Running Ansible Verifier
--> Sanity checks: 'docker'
    
    PLAY [Verify] ******************************************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [instance]
    
    TASK [Example assertion] *******************************************************
    ok: [instance] => {
        "changed": false,
        "msg": "All assertions passed"
    }
    
    PLAY RECAP *********************************************************************
    instance                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
Verifier completed successfully.
```

## テスト環境へのログイン: molecule login

```
$ molecule login

[root@instance /]# id
uid=0(root) gid=0(root) groups=0(root)

[root@instance /]# hostname
instance

[root@instance /]# cat /etc/os-release 
NAME="CentOS Linux"
VERSION="8 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Linux 8 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-8"
CENTOS_MANTISBT_PROJECT_VERSION="8"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="8"

[root@instance /]# exit
$
```

## テスト環境削除: molecule destroy

```
$ molecule destroy 
--> Test matrix
    
└── default
    ├── dependency
    ├── cleanup
    └── destroy
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
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] *******************************
    FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
    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
```

```
 $ molecule list
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          false      false
```

```
$ docker ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```


# 参考

- https://kitchen.ci
- https://molecule.readthedocs.io/en/latest/index.html 
- [Moleculeに入門してみたよ(v3差分確認編)](https://qiita.com/answer_d/items/78b047eb5708dda1375d)