作者选择了 Mozilla 基金会作为 写给捐赠计划的一部分接受捐赠。
介绍
在 Ansible中,单元测试是确保角色正常运作的关键。 Molecule通过允许您指定测试角色与不同环境的场景来简化此过程。使用 Ansible 在帽子下,Molecule将角色卸载到部署角色在配置环境中的供应商,并呼叫验证器(如 Testinfra)来检查配置偏差。
在本指南中,您将构建一个 Ansible 角色,将 Apache部署到主机并配置 firewalld在 CentOS 7 上。 为了测试该角色是否按预期工作,您将使用 Molecule 创建测试,使用 Docker作为驱动程序和 Testinfra,一个 Python 库来测试服务器的状态。
前提条件
在您开始本指南之前,您将需要以下内容:
- 一個 Ubuntu 18.04 伺服器. 遵循 初始伺服器設定與 Ubuntu 18.04 指南的步驟來創建一個非根 sudo 用戶,並確保您可以連接到伺服器沒有密碼。
- Docker 安裝在您的伺服器上。 遵循步驟 1 和 2 在 如何在 Ubuntu 18.04 上安裝和使用 Docker 中,包括將您的非根用戶添加到「docker」群組。
- Python 3 和'venv' 安裝和配置在您的伺服器上。 遵循 如何在 Ubuntu 18.04 伺服器上安裝 Python 3 和設定程式環境 的指南。
步骤一:准备环境
如果你遵循了前提条件,你应该有Python 3,venv
和Docker安装并正确配置,让我们先创建一个虚拟环境来测试Ansible with Molecule。
首先,作为非根用户登录并创建一个新的虚拟环境:
1python3 -m venv my_env
启用它,以确保您的操作仅限于该环境:
1source my_env/bin/activate
接下来,在您的启用环境中,安装轮子
包,该包提供bdist_wheel``setuptools
扩展,而pip
用于安装Ansible:
1python3 -m pip install wheel
您现在可以安装分子
和docker
与管道
。Ansible将自动安装为分子
的依赖:
1python3 -m pip install molecule docker
以下是每个包将做什么:
*「分子」:这是你将使用的主要分子包来测试角色。安装「分子」会自动安装Ansible,以及其他依赖,并允许使用Ansible播放书来执行角色和测试。
docker
:这个Python库被Molecule用来与Docker互动。
接下来,让我们在分子中创建一个角色。
步骤 2 – 在分子中创建一个角色
随着环境的设置,您可以使用 Molecule 创建一个基本的角色,您将使用它来测试 Apache 的安装. 这个角色将创建目录结构和一些初始测试,并指定 Docker 作为驱动程序,以便 Molecule 使用 Docker 运行测试。
创建一个名为ansible-apache
的新角色:
1molecule init role -r ansible-apache -d docker
-r
旗指明了角色的名称,而-d
指明了驱动程序,这规定了分子在测试中使用的主机。
更改新创建的角色目录:
1cd ansible-apache
测试默认角色,以检查 Molecule 是否已正确设置:
1molecule test
您将看到输出列出了每个默认测试操作。在开始测试之前,Molecule 会验证配置文件 molecule.yml
,以确保一切顺序。
1[secondary_label Output]
2--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml.
3Validation completed successfully.
4--> Test matrix
5
6└── default
7 ├── lint
8 ├── destroy
9 ├── dependency
10 ├── syntax
11 ├── create
12 ├── prepare
13 ├── converge
14 ├── idempotence
15 ├── side_effect
16 ├── verify
17 └── destroy
18...
您创建角色并定制测试后,我们将详细讨论每个测试操作。 现在,请注意每个测试的PLAY_RECAP
,并确保任何默认操作都不会返回失败
状态。
1[secondary_label Output]
2...
3PLAY RECAP *********************************************************************
4localhost : ok=5 changed=4 unreachable=0 failed=0
让我们继续修改角色来配置Apache和防火墙。
步骤 3 – 配置 Apache 和 Firewalld
要配置 Apache 和 firewalld,您将为角色创建一个任务文件,指定要安装的包和启用的服务. 这些细节将从变量文件和模板中提取,您将使用它来替代默认的 Apache 索引页面。
在ansible-apache
目录中,使用nano
或您最喜欢的文本编辑器创建该角色的任务文件:
1nano tasks/main.yml
您将看到该文件已经存在. 删除存在的文件并用以下代码更换,以安装所需的包,并启用正确的服务、HTML默认和防火墙设置:
1[label ~/ansible-apache/tasks/main.yml]
2---
3- name: "Ensure required packages are present"
4 yum:
5 name: "{{ pkg_list }}"
6 state: present
7
8- name: "Ensure latest index.html is present"
9 template:
10 src: index.html.j2
11 dest: /var/www/html/index.html
12
13- name: "Ensure httpd service is started and enabled"
14 service:
15 name: "{{ item }}"
16 state: started
17 enabled: true
18 with_items: "{{ svc_list }}"
19
20- name: "Whitelist http in firewalld"
21 firewalld:
22 service: http
23 state: enabled
24 permanent: true
25 immediate: true
本剧本包含四个任务:
这个任务将安装在pkg_list
下的变量文件中列出的软件包。变量文件将位于~/ansible-apache/vars/main.yml
的位置,您将在这个步骤结束时创建它。
*
Ensure latest index.html is present : 这个任务将复制一个模板页面,即
index.html.j2,并将其粘贴到由Apache生成的默认索引文件,即
/var/www/html/index.html。您还将创建新的模板在这个步骤中。
Ensure latest index.html is present : 这个任务将启动并启动
index.html.j2中的默认索引文件,即
svc_list`中的服务
保存并关闭文件,当你完成。
接下来,让我们为index.html.j2
模板页面创建一个模板
目录:
1mkdir templates
自己创建页面:
1nano templates/index.html.j2
按下列 boilerplate 代码填写:
1[label ~/ansible-apache/templates/index.html.j2]
2<div style="text-align: center">
3 <h2>Managed by Ansible</h2>
4</div>
保存并关闭文件。
完成角色的最后一步是写变量文件,其中为我们的主要角色播放簿提供了包和服务的名称:
1nano vars/main.yml
将默认内容粘贴到以下代码上,该代码指定pkg_list
和svc_list
:
1[label ~/ansible-apache/vars/main.yml]
2---
3pkg_list:
4 - httpd
5 - firewalld
6svc_list:
7 - httpd
8 - firewalld
这些清单包含以下信息:
pkg_list
:此包含角色将安装的包名称:httpd
和firewalld
.svc_list
:此包含角色将启动和启用的服务名称:httpd
和firewalld
.
<$>[注] 注: 请确保您的变量文件没有空行或测试在链接过程中失败。
现在你已经完成了创建你的角色,让我们配置分子来测试它是否按预期工作。
步骤 4 – 更改运行测试的角色
在我们的情况下,配置 Molecule 涉及修改 Molecule 配置文件 molecule.yml
以添加平台规格. 因为您正在测试配置和启动 httpd
systemd 服务的角色,您需要使用具有 systemd 配置和特权模式的图像。
让我们编辑 molecule.yml 以反映这些变化:
1nano molecule/default/molecule.yml
添加突出的平台信息:
1[label ~/ansible-apache/molecule/default/molecule.yml]
2---
3dependency:
4 name: galaxy
5driver:
6 name: docker
7lint:
8 name: yamllint
9platforms:
10 - name: centos7
11 image: milcom/centos7-systemd
12 privileged: true
13provisioner:
14 name: ansible
15 lint:
16 name: ansible-lint
17scenario:
18 name: default
19verifier:
20 name: testinfra
21 lint:
22 name: flake8
保存并关闭文件,当你完成。
现在您已经成功配置了测试环境,让我们继续写下 Molecule 在执行角色后将对您的容器运行的测试案例。
步骤5 - 撰写测试案例
在这个角色的测试中,你会检查以下条件:
- 已安裝「httpd」和「firewalld」套件。 已啟用「httpd」和「firewalld」服務。 已啟用「http」服務在您的防火牆設定中。 該「index.html」包含您模板檔案中指定的相同數據。
如果所有这些测试都通过,那么角色将按预期运作。
为了写出这些条件的测试案例,让我们在 ~/ansible-apache/molecule/default/tests/test_default.py
中编辑默认测试案例。
打开test_default.py
:
1nano molecule/default/tests/test_default.py
删除文件的内容,以便您可以从头开始写测试。
<$>[注] **注:**当你写你的测试时,请确保它们被两个新行分开,否则它们将失败。
首先,输入所需的 Python 模块:
1[label ~/ansible-apache/molecule/default/tests/test_default.py]
2import os
3import pytest
4
5import testinfra.utils.ansible_runner
这些模块包括:
os
:这个内置的Python模块允许操作系统依赖的功能,使Python能够与底层操作系统进行交互。pytest
:pytest
模块允许测试写作。
在模块导入中,添加以下代码,该代码使用 Ansible 后端返回当前主机实例:
1[label ~/ansible-apache/molecule/default/tests/test_default.py]
2...
3testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
4 os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
随着测试文件配置为使用 Ansible 后端,让我们写单元测试来测试主机的状态。
第一个测试将确保安装httpd
和firewalld
:
1[label ~/ansible-apache/molecule/default/tests/test_default.py]
2...
3
4@pytest.mark.parametrize('pkg', [
5 'httpd',
6 'firewalld'
7])
8def test_pkg(host, pkg):
9 package = host.package(pkg)
10
11 assert package.is_installed
测试始于 pytest.mark.parametrize
decorator,允许我们对测试的参数进行参数化. 第一次测试将使用 test_pkg
作为参数来测试 httpd
和 firewalld
包的存在。
下一个测试会检查httpd
和firewalld
是否正在运行并启用,并将test_svc
作为参数:
1[label ~/ansible-apache/molecule/default/tests/test_default.py]
2...
3
4@pytest.mark.parametrize('svc', [
5 'httpd',
6 'firewalld'
7])
8def test_svc(host, svc):
9 service = host.service(svc)
10
11 assert service.is_running
12 assert service.is_enabled
最後的測試會檢查檔案和內容是否已傳到「parametrize()」存在. 如果檔案不是由您的角色創建的,而內容沒有正確設定,則「assert」會返回「False」:
1[label ~/ansible-apache/molecule/default/tests/test_default.py]
2...
3
4@pytest.mark.parametrize('file, content', [
5 ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"),
6 ("/var/www/html/index.html", "Managed by Ansible")
7])
8def test_files(host, file, content):
9 file = host.file(file)
10
11 assert file.exists
12 assert file.contains(content)
在每个测试中,声明
将根据测试结果返回真
或假
。
完成的檔案看起來像這樣:
1[label ~/ansible-apache/molecule/default/tests/test_default.py]
2import os
3import pytest
4
5import testinfra.utils.ansible_runner
6
7testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
8 os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
9
10@pytest.mark.parametrize('pkg', [
11 'httpd',
12 'firewalld'
13])
14def test_pkg(host, pkg):
15 package = host.package(pkg)
16
17 assert package.is_installed
18
19@pytest.mark.parametrize('svc', [
20 'httpd',
21 'firewalld'
22])
23def test_svc(host, svc):
24 service = host.service(svc)
25
26 assert service.is_running
27 assert service.is_enabled
28
29@pytest.mark.parametrize('file, content', [
30 ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"),
31 ("/var/www/html/index.html", "Managed by Ansible")
32])
33def test_files(host, file, content):
34 file = host.file(file)
35
36 assert file.exists
37 assert file.contains(content)
现在你已经指定了你的测试案例,让我们测试这个角色。
步骤 6 – 测试与分子的作用
一旦你启动测试,分子将执行你在你的场景中定义的操作. 现在让我们再次运行默认的分子
场景,执行默认的测试序列中的操作,同时仔细观察每一个。
重新运行默认场景测试:
1molecule test
初始输出打印了默认测试矩阵:
1[secondary_label Output]
2--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml.
3Validation completed successfully.
4--> Test matrix
5
6└── default
7 ├── lint
8 ├── destroy
9 ├── dependency
10 ├── syntax
11 ├── create
12 ├── prepare
13 ├── converge
14 ├── idempotence
15 ├── side_effect
16 ├── verify
17 └── destroy
让我们通过每个测试操作和预期的输出,从链接开始。
linting 操作执行yamllint
,flake8
和ansible-lint
:
yamllint
:此路由器在角色目录中存在的所有 YAML 文件上运行。flake8
:此路由器对 Testinfra 创建的测试进行 Python 代码检查。
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'lint'
5--> Executing Yamllint on files found in /home/sammy/ansible-apache/...
6Lint completed successfully.
7--> Executing Flake8 on files found in /home/sammy/ansible-apache/molecule/default/tests/...
8Lint completed successfully.
9--> Executing Ansible Lint on /home/sammy/ansible-apache/molecule/default/playbook.yml...
10Lint completed successfully.
下一个操作, destroy,是使用destroy.yml
文件执行,以测试我们在新创建的容器上的作用。
默认情况下,破坏呼叫两次:在测试运行开始时,删除任何现有容器,并在最后删除新创建的容器:
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'destroy'
5
6 PLAY [Destroy] *****************************************************************
7
8 TASK [Destroy molecule instance(s)] ********************************************
9 changed: [localhost] => (item=None)
10 changed: [localhost]
11
12 TASK [Wait for instance(s) deletion to complete] *******************************
13 ok: [localhost] => (item=None)
14 ok: [localhost]
15
16 TASK [Delete docker network(s)] ************************************************
17 skipping: [localhost]
18
19 PLAY RECAP *********************************************************************
20 localhost : ok=2 changed=1 unreachable=0 failed=0
破坏操作完成后,测试将继续到 dependence. 此操作允许您从 ansible-galaxy
中提取依赖,如果您的角色需要它们。
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'dependency'
5Skipping, missing the requirements file.
下一个测试操作是 syntax 检查,该检查在默认的 playbook.yml
播放簿上执行,它以类似的方式运作于 ansible-playbook --syntax-check playbook.yml
命令中的 --syntax-check
旗帜:
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'syntax'
5
6 playbook: /home/sammy/ansible-apache/molecule/default/playbook.yml
接下来,测试将继续到 create 操作中,使用您角色的 Molecule 目录中的 create.yml
文件来创建具有您的规格的 Docker 容器:
1[secondary_label Output]
2...
3
4--> Scenario: 'default'
5--> Action: 'create'
6
7 PLAY [Create] ******************************************************************
8
9 TASK [Log into a Docker registry] **********************************************
10 skipping: [localhost] => (item=None)
11 skipping: [localhost]
12
13 TASK [Create Dockerfiles from image names] *************************************
14 changed: [localhost] => (item=None)
15 changed: [localhost]
16
17 TASK [Discover local Docker images] ********************************************
18 ok: [localhost] => (item=None)
19 ok: [localhost]
20
21 TASK [Build an Ansible compatible image] ***************************************
22 changed: [localhost] => (item=None)
23 changed: [localhost]
24
25 TASK [Create docker network(s)] ************************************************
26 skipping: [localhost]
27
28 TASK [Create molecule instance(s)] *********************************************
29 changed: [localhost] => (item=None)
30 changed: [localhost]
31
32 TASK [Wait for instance(s) creation to complete] *******************************
33 changed: [localhost] => (item=None)
34 changed: [localhost]
35
36 PLAY RECAP *********************************************************************
37 localhost : ok=5 changed=4 unreachable=0 failed=0
创建后,测试继续进行 prepare 操作. 此操作执行了准备播放簿,在运行 converge 之前将主机带到特定状态。
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'prepare'
5Skipping, prepare playbook not configured.
准备后, converge 操作通过运行playbook.yml
播放簿在容器上执行你的角色。
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'converge'
5
6 PLAY [Converge] ****************************************************************
7
8 TASK [Gathering Facts] *********************************************************
9 ok: [centos7]
10
11 TASK [ansible-apache : Ensure required packages are present] *******************
12 changed: [centos7]
13
14 TASK [ansible-apache : Ensure latest index.html is present] ********************
15 changed: [centos7]
16
17 TASK [ansible-apache : Ensure httpd service is started and enabled] ************
18 changed: [centos7] => (item=httpd)
19 changed: [centos7] => (item=firewalld)
20
21 TASK [ansible-apache : Whitelist http in firewalld] ****************************
22 changed: [centos7]
23
24 PLAY RECAP *********************************************************************
25 centos7 : ok=5 changed=4 unreachable=0 failed=0
覆盖后,测试继续到 idempotence. 此操作测试播放簿的 idempotence,以确保在多个运行中没有发生意外的更改:
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'idempotence'
5Idempotence completed successfully.
下一个测试操作是 side-effect 操作,这允许您产生情况,您将能够测试更多的东西,如 HA failover。默认情况下,Molecule 不会配置副作用播放簿,并且任务被跳过:
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'side_effect'
5Skipping, side effect playbook not configured.
Molecule 将使用默认验证器 Testinfra 执行 verifier 操作. 此操作将执行您之前在 test_default.py
中写的测试。
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'verify'
5--> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/...
6 ============================= test session starts ==============================
7 platform linux -- Python 3.6.5, pytest-3.7.3, py-1.5.4, pluggy-0.7.1
8 rootdir: /home/sammy/ansible-apache/molecule/default, inifile:
9 plugins: testinfra-1.14.1
10collected 6 items
11
12 tests/test_default.py ...... [100%]
13
14 ========================== 6 passed in 41.05 seconds ===========================
15Verifier completed successfully.
最后,分子 destroys 在测试过程中完成的实例,并删除分配给这些实例的网络:
1[secondary_label Output]
2...
3--> Scenario: 'default'
4--> Action: 'destroy'
5
6 PLAY [Destroy] *****************************************************************
7
8 TASK [Destroy molecule instance(s)] ********************************************
9 changed: [localhost] => (item=None)
10 changed: [localhost]
11
12 TASK [Wait for instance(s) deletion to complete] *******************************
13 changed: [localhost] => (item=None)
14 changed: [localhost]
15
16 TASK [Delete docker network(s)] ************************************************
17 skipping: [localhost]
18
19 PLAY RECAP *********************************************************************
20 localhost : ok=2 changed=2 unreachable=0 failed=0
测试操作已经完成,验证您的角色按预期工作。
结论
在本文中,您创建了一个 Ansible 角色来安装和配置 Apache 和防火墙. 然后,您用 Testinfra 写了单元测试,Molecule 用来声称该角色运行成功。
您可以使用相同的基本方法用于高度复杂的角色,并使用 CI 管道自动化测试。 Molecule 是一个高度可配置的工具,可以用来测试与 Ansible 支持的任何提供商的角色,而不仅仅是 Docker。 您还可以对自己的基础设施进行自动化测试,确保您的角色总是最新的和功能性。 您可以使用 Molecule 和 Travis CI 将连续测试集成到您的工作流程中,使用教程 [如何使用 Molecule 和 Travis CI 在 Ubuntu 184].0(https://andsky.com/tech/tutorials/how-to-implement-continuous-testing-of-ansible-roles-using-molecule-and-travis-ci-on-ubuntu-18-04)。
官方的分子文件(https://molecule.readthedocs.io/en/latest/)是学习如何使用分子的最佳资源。