見出し画像

MoleculeでAnsibleのテストをやってみた

AnsibleのテストフレームワークであるMoleculeを使ってansibleのroleが問題なく稼働するテストを体験やってみました。

やったこと

①moleculeをインストールする
②molecule initでテスト構造を作成する
③molecule testで初期テストが行えるか確認する
④2つのdockerコンテナ(CentOS7,8)にhttpdをインストールし、自動起動が有効化されているか検証する
参考文献

Moleculeとは

ドキュメントによるとansible roleのテストをサポートするためのツールと記載されてあります。(2018年頃にCISCO社からRedHat社へドネーションされたようです)roleのテストを実行してくれるだけでなく、テスト終了後に自動的にテスト環境も削除してくれるのでゴミが残らず便利なものです。

必要パッケージ

・ansible 2.8以上
・python 3.6以上

前回記事でマシンの環境情報を記載しましたが、要求するバージョンが高めなのが印象的でした。

moleculeインストール

ドキュメントに記載されている手順通りに進めていきます。

$ sudo yum install -y gcc python3-pip python3-devel openssl-devel python3-libselinux
$ python3 -m pip install --user "molecule[lint]"

#dockerドライバのインストール
$ python3 -m pip install --user "molecule[docker]"

Moleculeのテスト

インストール後にmolecule initコマンドでmoleculeのテンプレートが作成されます。

$ molecule init role testmol
--> Initializing new role testmol...
Initialized role in /home/yuhta/Desktop/test/testmol successfully.

$ tree testmol
testmol
tqq README.md
tqq defaults
x?? mqq main.yml
tqq files
tqq handlers
x?? mqq main.yml
tqq meta
x?? mqq main.yml
tqq molecule
x?? mqq default
x??     tqq INSTALL.rst
x??     tqq converge.yml
x??     tqq molecule.yml
x??     mqq verify.yml
tqq tasks
x?? mqq main.yml
tqq templates
tqq tests
x?? tqq inventory
x?? mqq test.yml
mqq vars
   mqq main.yml

10 directories, 12 files

Moleculeではテストの前準備を含めた一連の処理のことをScenarioと呼びます。Scenarioにはステップという単位で処理の流れを表します。

代表的なステップ
・dependency 依存関係を処理
・create テスト環境の構築
・prepare テストの前処理を実行
・syntax 構文チェック
・converge Roleの実行
・verify テストの実行
・destroy テスト環境の削除

テンプレートを作成した直後にmolecule testでテストの実行が可能となります。

$ molecule test
--> Test matrix

mqq default
   tqq dependency
   tqq lint
   tqq cleanup
   tqq destroy
   tqq syntax
   tqq create
   tqq prepare
   tqq converge
   tqq idempotence
   tqq side_effect
   tqq verify
   tqq cleanup
   mqq destroy

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'
--> Lint is disabled.
--> 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] *******************************
   ok: [localhost] => (item=None)
   ok: [localhost]

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

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

--> Scenario: 'default'
--> Action: 'syntax'

   playbook: /home/yuhta/Desktop/Molecule/testmol/molecule/default/converge.yml
--> Scenario: 'default'
--> Action: 'create'

   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).
   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.
--> Scenario: 'default'
--> Action: 'converge'

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

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

   TASK [Include testmol] *********************************************************

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

--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Running Ansible Verifier

   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.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'

   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のテンプレートはdefaultで作成したものでは、すべてのステップを実施せず最小限のテストを実行できるようになっております。(ログを確認するとskippingになっているものがあるのがわかると思います)

遭遇したトラブル

テスト対象のroleの詳細は↑のリポジトリに格納してありますので、中身については割愛します。ここでは検証中につまずいたポイントを記載します。

$ molecule test
--> Test matrix
   
mqq default
   tqq dependency
   tqq lint
   tqq cleanup
   tqq destroy
   tqq syntax
   tqq create
   tqq prepare
   tqq converge
   tqq idempotence
   tqq side_effect
   tqq verify
   tqq cleanup
   mqq destroy
   
~~~~~~~~~~~中略~~~~~~~~~~~~~~~~~~~~~~~~~

--> Scenario: 'default'
--> Action: 'converge'
   
   PLAY [Converge] ****************************************************************
   
   TASK [Gathering Facts] *********************************************************
   ok: [instance2]
   ok: [instance1]
   
   TASK [Include testmol] *********************************************************
   
#パッケージのインストールに失敗している
   TASK [testmol : install httpd package] *****************************************
fatal: [instance2]: FAILED! => {"changed": false, "msg": "Failed to download metadata for repo 'AppStream'", "rc": 1, "results": []}
fatal: [instance1]: FAILED! => {"changed": false, "msg": "Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os&infra=container error was\n14: curl#6 - \"Could not resolve host: mirrorlist.centos.org; Unknown error\"\n\n\n One of the configured repositories failed (Unknown),\n and yum doesn't have enough cached data to continue. At this point the only\n safe thing yum can do is fail. There are a few ways to work \"fix\" this:\n\n     1. Contact the upstream for the repository and get them to fix the problem.\n\n     2. Reconfigure the baseurl/etc. for the repository, to point to a working\n        upstream. This is most often useful if you are using a newer\n        distribution release than is supported by the repository (and the\n        packages for the previous distribution release still work).\n\n     3. Run the command with the repository temporarily disabled\n            yum --disablerepo=<repoid> ...\n\n     4. Disable the repository permanently, so yum won't use it by default. Yum\n        will then just ignore the repository until you permanently enable it\n        again or use --enablerepo for temporary usage:\n\n            yum-config-manager --disable <repoid>\n        or\n            subscription-manager repos --disable=<repoid>\n\n     5. Configure the failing repository to be skipped, if it is unavailable.\n        Note that yum will try to contact the repo. when it runs most commands,\n        so will have to try and fail each time (and thus. yum will be be much\n        slower). If it is a very temporary problem though, this is often a nice\n        compromise:\n\n            yum-config-manager --save --setopt=<repoid>.skip_if_unavailable=true\n\nCannot find a valid baseurl for repo: base/7/x86_64\n", "rc": 1, "results": []}
   
   PLAY RECAP *********************************************************************
   instance1                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
   instance2                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
   
ERROR: 
An error occurred during the test sequence action: 'converge'. Cleaning up.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
   
   PLAY [Destroy] *****************************************************************
   
   TASK [Destroy molecule instance(s)] ********************************************
   changed: [localhost] => (item=instance1)
   changed: [localhost] => (item=instance2)
   
   TASK [Wait for instance(s) deletion to complete] *******************************
   changed: [localhost] => (item=None)
   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 testコマンドを実行しましたが、途中のhttpdパッケージでインストールが失敗する事象に遭遇しました。

$ docker exec -it instance2 /bin/bash #CentOS 8のコンテナへ接続
[root@instance2 /]# 
[root@instance2 /]# 
[root@instance2 /]# dnf install epe- ; l-release
Failed to set locale, defaulting to C.UTF-8
CentOS-8 - AppStream  [=== ] --  B/s |   0  B  --:-- ETACentOS-8 - AppStream     [===                                                ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [   ===                                             ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [      ===                                          ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [         ===                                       ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [            ===                                    ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [               ===                                 ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                  ===                              ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                     ===                           ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                        ===                        ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                           ===                     ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                              ===                  ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                                 ===               ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                                    ===            ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                                       ===         ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                                          ===      ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                    [                                             ===   ] ---  B/s |   0  B     --:-- ETACentOS-8 - AppStream                                                                                          0.0  B/s |   0  B     00:05    
Failed to download metadata for repo 'AppStream'
Error: Failed to download metadata for repo 'AppStream'
[root@instance2 /]# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=53 time=26.6 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=53 time=28.2 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=53 time=43.4 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=53 time=35.2 ms
^C
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 8ms
rtt min/avg/max/mdev = 26.566/33.362/43.448/6.663 ms
[root@instance2 /]# cat /etc/resolv.conf 
# Generated by NetworkManager
search localdomain
nameserver 192.168.11.1
[root@instance2 /]# ping 192.168.11.1 
PING 192.168.11.1 (192.168.11.1) 56(84) bytes of data.
64 bytes from 192.168.11.1: icmp_seq=1 ttl=63 time=1.21 ms
64 bytes from 192.168.11.1: icmp_seq=2 ttl=63 time=1.07 ms
64 bytes from 192.168.11.1: icmp_seq=3 ttl=63 time=1.12 ms
^C
--- 192.168.11.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 5ms
rtt min/avg/max/mdev = 1.066/1.131/1.207/0.064 ms
[root@instance2 /]# ping dns.google
ping: dns.google: Name or service not known

dockerコンテナの中に入って直接dnf updateができるが試しましたが、外からの名前解決がうまくいっていないことが分かりました。(pingは通るのになぜか名前解決が上手くいっていないようです)

対策

調べてみるとローカルマシンがCentOS 8の時に起こる事象のようです。(雑誌はCentOS 7を行うことを前提としていた)
ローカルのNAPT設定を行ったらうまくいきました。

#rootユーザで実行
# firewall-cmd --add-masquerade --permanent
# firewall-cmd --reload

#通常ユーザーに戻って実行
$ molecule test
--> Test matrix
   
mqq default
   tqq dependency
   tqq lint
   tqq cleanup
   tqq destroy
   tqq syntax
   tqq create
   tqq prepare
   tqq converge
   tqq idempotence
   tqq side_effect
   tqq verify
   tqq cleanup
   mqq destroy
   
~~~~~~~~~~~中略~~~~~~~~~~~~~~~~~~~~~~~~~

--> Scenario: 'default'
--> Action: 'converge'
   
   PLAY [Converge] ****************************************************************
   
   TASK [Gathering Facts] *********************************************************
   ok: [instance2]
   ok: [instance1]
   
   TASK [Include testmol] *********************************************************
   
   TASK [testmol : install httpd package] *****************************************
   changed: [instance2]
   changed: [instance1]
   
   TASK [testmol : start httpd] ***************************************************
   changed: [instance2]
   changed: [instance1]
   
   PLAY RECAP *********************************************************************
   instance1                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   instance2                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Running Ansible Verifier
   
   PLAY [Verify] ******************************************************************
   
   TASK [Gathering Facts] *********************************************************
   ok: [instance2]
   ok: [instance1]
   
   TASK [httpdパッケージの存在を確認する] ******************************************************
   ok: [instance2]
   ok: [instance1]
   
   TASK [httpdプロセスが起動していることを確認する] *************************************************
   changed: [instance2]
   changed: [instance1]
   
   TASK [httpdサービスが自動起動になっているかを確認する] **********************************************
   changed: [instance2]
   changed: [instance1]
   
   TASK [結果をまとめて確認する] *************************************************************
   ok: [instance1] => (item={'results': [{'envra': '0:httpd-2.4.6-93.el7.centos.x86_64', 'name': 'httpd', 'repo': 'base', 'epoch': '0', 'version': '2.4.6', 'release': '93.el7.centos', 'yumstate': 'available', 'arch': 'x86_64'}, {'envra': '0:httpd-2.4.6-93.el7.centos.x86_64', 'name': 'httpd', 'repo': 'installed', 'epoch': '0', 'version': '2.4.6', 'release': '93.el7.centos', 'yumstate': 'installed', 'arch': 'x86_64'}], 'failed': False, 'changed': False}) => {
       "ansible_loop_var": "result",
       "changed": false,
       "msg": "All assertions passed",
       "result": {
           "changed": false,
           "failed": false,
           "results": [
               {
                   "arch": "x86_64",
                   "envra": "0:httpd-2.4.6-93.el7.centos.x86_64",
                   "epoch": "0",
                   "name": "httpd",
                   "release": "93.el7.centos",
                   "repo": "base",
                   "version": "2.4.6",
                   "yumstate": "available"
               },
               {
                   "arch": "x86_64",
                   "envra": "0:httpd-2.4.6-93.el7.centos.x86_64",
                   "epoch": "0",
                   "name": "httpd",
                   "release": "93.el7.centos",
                   "repo": "installed",
                   "version": "2.4.6",
                   "yumstate": "installed"
               }
           ]
       }
   }
   ok: [instance2] => (item={'msg': '', 'results': [{'name': 'httpd', 'arch': 'x86_64', 'epoch': '0', 'release': '16.module_el8.1.0+256+ae790463', 'version': '2.4.37', 'repo': '@System', 'nevra': '0:httpd-2.4.37-16.module_el8.1.0+256+ae790463.x86_64', 'yumstate': 'installed'}, {'name': 'httpd', 'arch': 'x86_64', 'epoch': '0', 'release': '16.module_el8.1.0+256+ae790463', 'version': '2.4.37', 'repo': 'AppStream', 'nevra': '0:httpd-2.4.37-16.module_el8.1.0+256+ae790463.x86_64', 'yumstate': 'available'}], 'failed': False, 'changed': False}) => {
       "ansible_loop_var": "result",
       "changed": false,
       "msg": "All assertions passed",
       "result": {
           "changed": false,
           "failed": false,
           "msg": "",
           "results": [
               {
                   "arch": "x86_64",
                   "epoch": "0",
                   "name": "httpd",
                   "nevra": "0:httpd-2.4.37-16.module_el8.1.0+256+ae790463.x86_64",
                   "release": "16.module_el8.1.0+256+ae790463",
                   "repo": "@System",
                   "version": "2.4.37",
                   "yumstate": "installed"
               },
               {
                   "arch": "x86_64",
                   "epoch": "0",
                   "name": "httpd",
                   "nevra": "0:httpd-2.4.37-16.module_el8.1.0+256+ae790463.x86_64",
                   "release": "16.module_el8.1.0+256+ae790463",
                   "repo": "AppStream",
                   "version": "2.4.37",
                   "yumstate": "available"
               }
           ]
       }
   }
   ok: [instance1] => (item={'changed': True, 'end': '2020-06-02 13:57:56.189657', 'stdout': 'root       321     1  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     322   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     323   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     324   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     325   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     326   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'cmd': 'ps -ef | grep http[d]', 'rc': 0, 'start': '2020-06-02 13:57:54.829974', 'stderr': '', 'delta': '0:00:01.359683', 'stdout_lines': ['root       321     1  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     322   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     323   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     324   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     325   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     326   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND'], 'stderr_lines': [], 'failed': False}) => {
       "ansible_loop_var": "result",
       "changed": false,
       "msg": "All assertions passed",
       "result": {
           "changed": true,
           "cmd": "ps -ef | grep http[d]",
           "delta": "0:00:01.359683",
           "end": "2020-06-02 13:57:56.189657",
           "failed": false,
           "rc": 0,
           "start": "2020-06-02 13:57:54.829974",
           "stderr": "",
           "stderr_lines": [],
           "stdout": "root       321     1  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     322   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     323   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     324   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     325   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     326   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
           "stdout_lines": [
               "root       321     1  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     322   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     323   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     324   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     325   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     326   321  0 13:56 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND"
           ]
       }
   }
   ok: [instance2] => (item={'cmd': 'ps -ef | grep http[d]', 'stdout': 'root       382     1  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     383   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     384   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     385   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     386   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'stderr': '', 'rc': 0, 'start': '2020-06-02 13:57:55.319723', 'end': '2020-06-02 13:57:55.451594', 'delta': '0:00:00.131871', 'changed': True, 'stdout_lines': ['root       382     1  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     383   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     384   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     385   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND', 'apache     386   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND'], 'stderr_lines': [], 'failed': False}) => {
       "ansible_loop_var": "result",
       "changed": false,
       "msg": "All assertions passed",
       "result": {
           "changed": true,
           "cmd": "ps -ef | grep http[d]",
           "delta": "0:00:00.131871",
           "end": "2020-06-02 13:57:55.451594",
           "failed": false,
           "rc": 0,
           "start": "2020-06-02 13:57:55.319723",
           "stderr": "",
           "stderr_lines": [],
           "stdout": "root       382     1  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     383   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     384   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     385   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND\napache     386   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
           "stdout_lines": [
               "root       382     1  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     383   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     384   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     385   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND",
               "apache     386   382  0 13:55 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND"
           ]
       }
   }
   ok: [instance2] => (item={'cmd': 'systemctl is-enabled httpd', 'stdout': 'enabled', 'stderr': '', 'rc': 0, 'start': '2020-06-02 13:58:07.605856', 'end': '2020-06-02 13:58:07.678498', 'delta': '0:00:00.072642', 'changed': True, 'stdout_lines': ['enabled'], 'stderr_lines': [], 'failed': False}) => {
       "ansible_loop_var": "result",
       "changed": false,
       "msg": "All assertions passed",
       "result": {
           "changed": true,
           "cmd": "systemctl is-enabled httpd",
           "delta": "0:00:00.072642",
           "end": "2020-06-02 13:58:07.678498",
           "failed": false,
           "rc": 0,
           "start": "2020-06-02 13:58:07.605856",
           "stderr": "",
           "stderr_lines": [],
           "stdout": "enabled",
           "stdout_lines": [
               "enabled"
           ]
       }
   }
   ok: [instance1] => (item={'changed': True, 'end': '2020-06-02 13:58:09.146209', 'stdout': 'enabled', 'cmd': 'systemctl is-enabled httpd', 'rc': 0, 'start': '2020-06-02 13:58:07.921600', 'stderr': '', 'delta': '0:00:01.224609', 'stdout_lines': ['enabled'], 'stderr_lines': [], 'failed': False}) => {
       "ansible_loop_var": "result",
       "changed": false,
       "msg": "All assertions passed",
       "result": {
           "changed": true,
           "cmd": "systemctl is-enabled httpd",
           "delta": "0:00:01.224609",
           "end": "2020-06-02 13:58:09.146209",
           "failed": false,
           "rc": 0,
           "start": "2020-06-02 13:58:07.921600",
           "stderr": "",
           "stderr_lines": [],
           "stdout": "enabled",
           "stdout_lines": [
               "enabled"
           ]
       }
   }
   
   PLAY RECAP *********************************************************************
   instance1                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   instance2                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
   
   PLAY [Destroy] *****************************************************************
   
   TASK [Destroy molecule instance(s)] ********************************************
   changed: [localhost] => (item=instance1)
   changed: [localhost] => (item=instance2)
   
   TASK [Wait for instance(s) deletion to complete] *******************************
   changed: [localhost] => (item=None)
   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

ちゃんとhttpdパッケージがインストールでき、その後の自動起動の有効まで問題なく動作していることが確認できました。

反省点

雑誌ではローカルマシンをCentOS 7系で実施していたのでCentOS 8系でも問題ないと思い、そのまま書かれている通りテストを実行しました。
実際dockerコンテナの起動までは、問題なくできておりコンテナ内の操作はローカルマシンの影響は受けないだろうと高を括りましたが、まさかdockerコンテナからの名前解決ができないと思いませんでした。
直ぐに解決記事が見つかったのでよかったですが、このようなトラブルも考えられるので紹介されているサンプルコードを行う前提条件の違いは注意するべきだと痛感しました。

この記事が気に入ったらサポートをしてみませんか?