作者选择了 Mozilla 基金会作为 写给捐赠计划的一部分接受捐赠。
介绍
在 Ansible中,单元测试是确保角色正常运作的关键。 Molecule通过允许您指定测试角色与不同环境的场景来简化此过程。使用 Ansible 在帽子下,Molecule将角色卸载到部署角色在配置环境中的供应商,并呼叫验证器(如 Testinfra)来检查配置偏差。
在本指南中,您将构建一个 Ansible 角色,将 Apache部署到主机并配置 Firewalld在 CentOS 7 上。 为了测试该角色是否按预期工作,您将使用 Molecule 创建测试,使用 Docker作为驱动程序,以及 Testinfra,一个 Python 库来测试服务器的状态。 Molecule 将提供 Docker 容器来测试该角色,Testinfra 将验证服务器已按预期配置。
前提条件
在您开始本指南之前,您将需要以下内容:
- 一个 Ubuntu 16.04 服务器. 按照 初始服务器设置与 Ubuntu 16.04 指南的步骤创建一个非 root sudo 用户,并确保您可以连接到服务器没有密码。
- Docker 安装在您的服务器上。 按照 如何在 Ubuntu 16.04 上安装和使用 Docker 中的步骤 1 和 2,并确保将您的非 root 用户添加到
docker
组。
步骤一:准备环境
为了创建我们的角色和测试,让我们先创建一个虚拟环境并安装Molecule。安装Molecule也将安装Ansible,允许使用玩本创建角色和运行测试。
首先,登录为您的非根用户,并确保您的存储库是最新的:
1sudo apt-get update
这将确保您的包库包含python-pip
包的最新版本,其中将安装pip
和Python 2.7. 我们将使用pip
创建虚拟环境并安装额外的包。
1sudo apt-get install -y python-pip
使用pip
来安装virtualenv
Python 模块:
1python -m pip install virtualenv
接下来,让我们创建和激活虚拟环境:
1python -m virtualenv my_env
启用它,以确保您的操作仅限于该环境:
1source my_env/bin/activate
使用pip
安装molecule
和docker
:
1python -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
您将看到输出列出每个默认测试操作:
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...
在开始测试之前,Molecule 验证了配置文件 molecule.yml
以确保一切顺序. 它还打印了这个测试矩阵,它指定了测试操作的顺序。
您创建角色并定制测试后,我们将详细讨论每个测试操作。 现在,请注意每个测试的PLAY_RECAP
,并确保任何默认操作都不会返回失败
状态。
1[secondary_label Output]
2...
3PLAY RECAP *********************************************************************
4localhost : ok=5 changed=4 unreachable=0 failed=0
让我们继续修改角色来配置Apache和Firewalld。
步骤 3 – 配置 Apache 和 Firewalld
要配置 Apache 和 Firewalld,您将为角色创建一个任务文件,指定要安装的包和启用的服务. 这些细节将从变量文件和模板中提取,您将使用它来替代默认的 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
*: 这个任务将启动并允许在
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 linux2 -- Python 2.7.12, pytest-3.8.0, py-1.6.0, 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 56.73 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 和 Firewalld. 然后,您用 Testinfra 写了单元测试, Molecule 用来声称该角色运行成功。
您可以使用相同的基本方法用于高度复杂的角色,并使用 CI 管道自动化测试。 Molecule 是一个高度可配置的工具,可以用来测试与 Ansible 支持的任何提供商的角色,而不仅仅是 Docker。 它还可以对自己的基础设施进行自动化测试,确保您的角色始终是最新和功能。 官方 Molecule 文档是学习如何使用 Molecule 的最佳资源。