如何在Ubuntu16.04上使用Molecule测试Ansible角色

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Mozilla 基金会 来接受捐赠。

介绍

Ansible 中的单元测试是确保角色按预期运行的关键。 Molecule 允许您指定针对不同环境测试角色的场景,从而简化了此过程。 在后台使用 Ansible,Molecule 将角色卸载到配置程序中,该配置程序在配置的环境中部署角色并调用验证程序(例如 Testinfra)来检查配置漂移。 这可确保您的角色在该特定场景中对环境进行了所有预期的更改。

在本指南中,您将构建一个 Ansible 角色,将 Apache 部署到主机并在 CentOS 7 上配置 Firewalld。 要测试此角色是否按预期工作,您将使用 Docker 作为驱动程序和 Testinfra(用于测试服务器状态的 Python 库)在 Molecule 中创建一个测试。 Molecule 将提供 Docker 容器来测试角色,Testinfra 将验证服务器是否已按预期配置。 完成后,您将能够为跨环境的构建创建多个测试用例,并使用 Molecule 运行这些测试。

先决条件

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

第 1 步 — 准备环境

为了创建我们的角色和测试,让我们首先创建一个虚拟环境并安装 Molecule。 安装 Molecule 也将安装 Ansible,允许使用 playbook 来创建角色和运行测试。

首先以非 root 用户身份登录并确保您的存储库是最新的:

sudo apt-get update

这将确保您的包存储库包含最新版本的 python-pip 包,它将安装 pip 和 Python 2.7。 我们将使用 pip 创建一个虚拟环境并安装额外的包。 要安装 pip,请运行:

sudo apt-get install -y python-pip

使用 pip 安装 virtualenv Python 模块:

python -m pip install virtualenv

接下来,让我们创建并激活虚拟环境:

python -m virtualenv my_env

激活它以确保您的操作仅限于该环境:

source my_env/bin/activate

使用 pip 安装 moleculedocker

python -m pip install molecule docker

以下是每个软件包的作用:

  • molecule:这是您将用于测试角色的主要分子包。 安装 molecule 会自动安装 Ansible 以及其他依赖项,并允许使用 Ansible 剧本来执行角色和测试。
  • docker:这个 Python 库被 Molecule 用来与 Docker 交互。 由于您使用 Docker 作为驱动程序,因此您将需要它。

接下来,让我们在 Molecule 中创建一个角色。

第 2 步 — 在分子中创建角色

设置好环境后,您可以使用 Molecule 创建一个基本角色来测试 Apache 的安装。 该角色将创建目录结构和一些初始测试,并将 Docker 指定为驱动程序,以便 Molecule 使用 Docker 运行其测试。

创建一个名为 ansible-apache 的新角色:

molecule init role -r ansible-apache -d docker

-r 标志指定角色的名称,而 -d 指定驱动程序,它为 Molecule 提供用于测试的主机。

切换到新创建角色的目录:

cd ansible-apache

测试默认角色以检查 Molecule 是否已正确设置:

molecule test

您将看到列出每个默认测试操作的输出:

Output--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy
...

在开始测试之前,Molecule 会验证配置文件 molecule.yml 以确保一切正常。 它还打印这个测试矩阵,它指定了测试动作的顺序。

创建角色并自定义测试后,我们将详细讨论每个测试操作。 现在,请注意每个测试的 PLAY_RECAP,并确保没有任何默认操作返回 failed 状态。 例如,默认 'create' 动作的 PLAY_RECAP 应该如下所示:

Output...
PLAY RECAP *********************************************************************
localhost                  : ok=5    changed=4    unreachable=0    failed=0

让我们继续修改角色以配置 Apache 和 Firewalld。

第 3 步 — 配置 Apache 和 Firewalld

要配置 Apache 和 Firewalld,您将为角色创建一个任务文件,指定要安装的包和要启用的服务。 这些详细信息将从变量文件和模板中提取,您将使用它来替换默认的 Apache 索引页面。

使用 nano 或您喜欢的文本编辑器为角色创建任务文件:

nano tasks/main.yml

您会看到该文件已经存在。 删除那里的内容并粘贴以下代码以安装所需的包并启用正确的服务、HTML 默认值和防火墙设置:

~/ansible-apache/tasks/main.yml

---
- name: "Ensure required packages are present"
  yum:
    name: "{{ pkg_list }}"
    state: present

- name: "Ensure latest index.html is present"
  template:
    src: index.html.j2
    dest: /var/www/html/index.html

- name: "Ensure httpd service is started and enabled"
  service:
    name: "{{ item }}"
    state: started
    enabled: true
  with_items: "{{ svc_list }}"

- name: "Whitelist http in firewalld"
  firewalld:
    service: http
    state: enabled
    permanent: true
    immediate: true

该剧本包括 4 个任务:

  • "Ensure required packages are present":此任务将安装 pkg_list 下的变量文件中列出的包。 变量文件将位于 ~/ansible-apache/vars/main.yml,您将在此步骤结束时创建它。
  • "Ensure latest index.html is present":此任务将复制模板页面 index.html.j2 并将其粘贴到 Apache 生成的默认索引文件 /var/www/html/index.html 上。 您还将在此步骤中创建模板。
  • "Ensure httpd service is started and enabled":此任务将启动并启用变量文件中 svc_list 中列出的服务。
  • "Whitelist http in firewalld":此任务会将firewalld中的http服务列入白名单。 Firewalld 是一个完整的防火墙解决方案,默认出现在 CentOS 服务器上。 要使 http 服务正常工作,您需要公开所需的端口。 指示 firewalld 将服务列入白名单可确保它将服务所需的所有端口列入白名单。

完成后保存并关闭文件。

接下来,让我们为index.html.j2模板页面创建一个templates目录:

mkdir templates

创建页面本身:

nano templates/index.html.j2

粘贴以下样板代码:

~/ansible-apache/templates/index.html.j2

<div style="text-align: center">
    <h2>Managed by Ansible</h2>
</div>

保存并关闭文件。

完成角色的最后一步是编写变量文件,它为您的主要角色剧本提供包和服务的名称:

nano vars/main.yml

使用以下代码粘贴默认内容,指定 pkg_listsvc_list

~/ansible-apache/vars/main.yml

---
pkg_list:
  - httpd
  - firewalld
svc_list:
  - httpd
  - firewalld

这些列表包含以下信息:

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

注意: 确保您的变量文件没有任何空行,否则您的测试将在 linting 期间失败。


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

第 4 步 — 修改运行测试的角色

在我们的例子中,配置 Molecule 涉及修改 Molecule 配置文件 molecule.yml 以添加平台规范。 因为您正在测试配置和启动 httpd systemd 服务的角色,所以您需要使用配置了 systemd 并启用特权模式的映像。 在本教程中,您将使用 Docker Hub 上可用的 milcom/centos7-systemd 映像 。 特权模式允许容器使用其主机的几乎所有功能运行。

让我们编辑 molecule.yml 以反映这些变化:

nano molecule/default/molecule.yml

添加突出显示的平台信息:

~/ansible-apache/molecule/default/molecule.yml

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: centos7
    image: milcom/centos7-systemd
    privileged: true
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8

完成后保存并关闭文件。

现在您已经成功配置了测试环境,让我们继续编写 Molecule 在执行角色后将对您的容器运行的测试用例。

第五步——编写测试用例

在对该角色的测试中,您将检查以下条件:

  • 已安装 httpdfirewalld 软件包。
  • httpdfirewalld 服务正在运行并启用。
  • 在您的防火墙设置中启用了 http 服务。
  • index.html 包含在模板文件中指定的相同数据。

如果所有这些测试都通过,则该角色按预期工作。

要为这些条件编写测试用例,让我们在 ~/ansible-apache/molecule/default/tests/test_default.py 中编辑默认测试。 使用 Testinfra,您可以将测试用例编写为使用 Molecule 类的 Python 函数。

打开test_default.py

nano molecule/default/tests/test_default.py

删除文件的内容,以便您可以从头开始编写测试。

注意: 在编写测试时,请确保它们被两个新行分隔,否则它们将失败。


首先导入所需的 Python 模块:

~/ansible-apache/molecule/default/tests/test_default.py

import os
import pytest

import testinfra.utils.ansible_runner

这些模块包括:

  • os:这个内置的 Python 模块启用了依赖于操作系统的功能,使 Python 可以与底层操作系统进行交互。
  • pytestpytest 模块启用测试写入。
  • testinfra.utils.ansible_runner:这个Testinfra模块使用Ansible作为后端来执行命令。

在模块导入下,粘贴以下代码,该代码使用 Ansible 后端返回当前主机实例:

~/ansible-apache/molecule/default/tests/test_default.py

...
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')

将您的测试文件配置为使用 Ansible 后端,让我们编写单元测试来测试主机的状态。

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

~/ansible-apache/molecule/default/tests/test_default.py

...

@pytest.mark.parametrize('pkg', [
  'httpd',
  'firewalld'
])
def test_pkg(host, pkg):
    package = host.package(pkg)

    assert package.is_installed

测试从 pytest.mark.parametrize 装饰器 开始,它允许我们参数化测试的参数。 第一个测试将 test_pkg 作为参数来测试 httpdfirewalld 包的存在。

下一个测试检查 httpdfirewalld 是否正在运行和启用。 它以 test_svc 作为参数:

~/ansible-apache/molecule/default/tests/test_default.py

...

@pytest.mark.parametrize('svc', [
  'httpd',
  'firewalld'
])
def test_svc(host, svc):
    service = host.service(svc)

    assert service.is_running
    assert service.is_enabled

最后一个测试检查传递给 parametrize() 的文件和内容是否存在。 如果文件不是由您的角色创建的并且内容设置不正确,assert 将返回 False

~/ansible-apache/molecule/default/tests/test_default.py

...

@pytest.mark.parametrize('file, content', [
  ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"),
  ("/var/www/html/index.html", "Managed by Ansible")
])
def test_files(host, file, content):
    file = host.file(file)

    assert file.exists
    assert file.contains(content)

在每个测试中,assert 将根据测试结果返回 TrueFalse

完成的文件如下所示:

~/ansible-apache/molecule/default/tests/test_default.py

import os
import pytest

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')


@pytest.mark.parametrize('pkg', [
  'httpd',
  'firewalld'
])
def test_pkg(host, pkg):
    package = host.package(pkg)

    assert package.is_installed


@pytest.mark.parametrize('svc', [
  'httpd',
  'firewalld'
])
def test_svc(host, svc):
    service = host.service(svc)

    assert service.is_running
    assert service.is_enabled


@pytest.mark.parametrize('file, content', [
  ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"),
  ("/var/www/html/index.html", "Managed by Ansible")
])
def test_files(host, file, content):
    file = host.file(file)

    assert file.exists
    assert file.contains(content)

现在您已经指定了测试用例,让我们测试角色。

第 6 步 — 用分子测试角色

启动测试后,Molecule 将执行您在场景中定义的操作。 现在让我们再次运行默认的 molecule 场景,执行默认测试序列中的操作,同时更仔细地查看每个操作。

再次运行默认场景的测试:

molecule test

这将启动测试运行。 初始输出打印默认测试矩阵:

Output--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

让我们看看每个测试操作和预期的输出,从 linting 开始。

linting 动作执行 yamllintflake8ansible-lint

  • yamllint:此 linter 在角色目录中存在的所有 YAML 文件上执行。
  • flake8:这个 Python 代码 linter 检查为 Testinfra 创建的测试。
  • ansible-lint:这个用于 Ansible 剧本的 linter 在所有场景中执行。
Output...
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/sammy/ansible-apache/...
Lint completed successfully.
--> Executing Flake8 on files found in /home/sammy/ansible-apache/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/sammy/ansible-apache/molecule/default/playbook.yml...
Lint completed successfully.

下一个动作 destroy 使用 destroy.yml 文件执行。 这样做是为了在新创建的容器上测试您的角色。

默认情况下,destroy 会被调用两次:在测试运行开始时,删除所有预先存在的容器,最后,删除新创建的容器:

Output...
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Delete docker network(s)] ************************************************
    skipping: [localhost]

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

销毁操作完成后,测试将转到 dependency。 如果您的角色需要,此操作允许您从 ansible-galaxy 中提取依赖项。 在这种情况下,角色不会:

Output...
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.

下一个测试动作是 syntax 检查,在默认的 playbook.yml playbook 上执行。 它的工作方式类似于命令 ansible-playbook --syntax-check playbook.yml 中的 --syntax-check 标志:

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

    playbook: /home/sammy/ansible-apache/molecule/default/playbook.yml

接下来,测试继续进行 create 操作。 这将使用您角色的 Molecule 目录中的 create.yml 文件来创建具有您的规范的 Docker 容器:

Output...

--> Scenario: 'default'
--> Action: 'create'

    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)
    skipping: [localhost]

    TASK [Create Dockerfiles from image names] *************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image] ***************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Create docker network(s)] ************************************************
    skipping: [localhost]

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) creation to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=4    unreachable=0    failed=0

创建后,测试继续进行 prepare 操作。 此操作执行准备剧本,在运行收敛之前将主机带入特定状态。 如果您的角色需要在执行角色之前对系统进行预配置,这将非常有用。 同样,这不适用于我们的角色:

Output...
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.

准备好之后,converge 动作通过运行 playbook.yml 剧本在容器上执行你的角色。 如果在 molecule.yml 文件中配置了多个平台,Molecule 将收敛于所有这些:

Output...
--> Scenario: 'default'
--> Action: 'converge'

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

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

    TASK [ansible-apache : Ensure required packages are present] *******************
    changed: [centos7]

    TASK [ansible-apache : Ensure latest index.html is present] ********************
    changed: [centos7]

    TASK [ansible-apache : Ensure httpd service is started and enabled] ************
    changed: [centos7] => (item=httpd)
    changed: [centos7] => (item=firewalld)

    TASK [ansible-apache : Whitelist http in firewalld] ****************************
    changed: [centos7]

    PLAY RECAP *********************************************************************
    centos7                    : ok=5    changed=4    unreachable=0    failed=0

覆盖后,测试继续 幂等。 此操作测试剧本的幂等性,以确保在多次运行中不会发生意外更改:

Output...
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.

下一个测试动作是 side-effect 动作。 这使您可以产生能够测试更多事物的情况,例如 HA 故障转移。 默认情况下,Molecule 不会配置副作用剧本,并且会跳过该任务:

Output...
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.

然后,Molecule 将使用默认验证程序 Testinfra 运行 verifier 操作。 此操作执行您之前在 test_default.py 中编写的测试。 如果所有测试都成功通过,您将看到一条成功消息,并且 Molecule 将继续下一步:

Output...
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/...
    ============================= test session starts ==============================
    platform linux2 -- Python 2.7.12, pytest-3.8.0, py-1.6.0, pluggy-0.7.1
    rootdir: /home/sammy/ansible-apache/molecule/default, inifile:
    plugins: testinfra-1.14.1
collected 6 items

    tests/test_default.py ......                                             [100%]

    ========================== 6 passed in 56.73 seconds ===========================
Verifier completed successfully.

最后,Molecule destroys 在测试期间完成的实例并删除分配给这些实例的网络:

Output...
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Delete docker network(s)] ************************************************
    skipping: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0

测试操作现已完成,验证您的角色是否按预期工作。

结论

在本文中,您创建了一个 Ansible 角色来安装和配置 Apache 和 Firewalld。 然后,您使用 Testinfra 编写单元测试,Molecule 用来断言角色运行成功。

您可以对高度复杂的角色使用相同的基本方法,也可以使用 CI 管道进行自动化测试。 Molecule 是一个高度可配置的工具,可用于测试 Ansible 支持的任何提供程序的角色,而不仅仅是 Docker。 还可以针对您自己的基础架构进行自动化测试,以确保您的角色始终是最新的并且可以正常工作。 Molecule 官方文档 是学习如何使用 Molecule 的最佳资源。