如何在 Ubuntu 18.04 上使用分子测试 Ansible 角色

作者选择了 Mozilla 基金会作为 写给捐赠计划的一部分接受捐赠。

介绍

Ansible中,单元测试是确保角色正常运作的关键。 Molecule通过允许您指定测试角色与不同环境的场景来简化此过程。使用 Ansible 在帽子下,Molecule将角色卸载到部署角色在配置环境中的供应商,并呼叫验证器(如 Testinfra)来检查配置偏差。

在本指南中,您将构建一个 Ansible 角色,将 Apache部署到主机并配置 firewalld在 CentOS 7 上。 为了测试该角色是否按预期工作,您将使用 Molecule 创建测试,使用 Docker作为驱动程序和 Testinfra,一个 Python 库来测试服务器的状态。

前提条件

在您开始本指南之前,您将需要以下内容:

步骤一:准备环境

如果你遵循了前提条件,你应该有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_listsvc_list:

1[label ~/ansible-apache/vars/main.yml]
2---
3pkg_list:
4  - httpd
5  - firewalld
6svc_list:
7  - httpd
8  - firewalld

这些清单包含以下信息:

  • pkg_list:此包含角色将安装的包名称: httpdfirewalld.
  • svc_list:此包含角色将启动和启用的服务名称: httpdfirewalld.

<$>[注] 注: 请确保您的变量文件没有空行或测试在链接过程中失败。

现在你已经完成了创建你的角色,让我们配置分子来测试它是否按预期工作。

步骤 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 后端,让我们写单元测试来测试主机的状态。

第一个测试将确保安装httpdfirewalld:

 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 作为参数来测试 httpdfirewalld 包的存在。

下一个测试会检查httpdfirewalld是否正在运行并启用,并将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,flake8ansible-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/)是学习如何使用分子的最佳资源。

Published At
Categories with 技术
comments powered by Disqus