如何用分子测试可疑角色

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

介绍

Ansible中的单元测试是确保角色按预期运行的关键。 分子通过允许您指定针对不同环境测试角色的方案,使此过程更加简单。 在底层使用Ansible,分子将角色卸载到在配置的环境中部署角色的调配程序,并调用验证程序(如Testinfra )来检查配置偏移。 这可确保您的角色在特定情况下对环境进行了所有预期的更改。

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

先决条件

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

第1步 - 准备环境

我们首先在主机上创建一个虚拟环境,然后在该环境中安装测试所需的包。

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

sudo apt-get update -y

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

sudo apt-get install -y python-pip

使用pip来安装virtualenv Python模块和任何更新:

pip install pip virtualenv -U

-U标志告诉pip更新任何以前安装的软件包。

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

virtualenv my_env

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

. my_env/bin/activate

使用pip安装moleculeansibledocker-py

pip install molecule ansible docker-py

以下是每个软件包将执行的操作:

  • molecule :这是主要的分子包,用于测试角色。
  • ansible :这个软件包允许使用Ansible Playbook,执行角色和相关的测试。
  • docker-py :这个Python库被Molecule用来与Docker进行交互。 我们需要这个,因为我们使用Docker作为驱动程序。

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

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

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

创建一个名为httpd的新角色:

molecule init role -r httpd -d docker

-r标志指定角色的名称,而-d指定驱动程序,该驱动程序为Molecule在测试中使用的主机提供配置。

转到新创建的角色的目录中:

cd httpd

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

molecule test

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

Output--> Validating schema /home/sammy/httpd/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

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

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

nano tasks/main.yml

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

〜/ httpd的/任务/ 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下变量文件中列出的软件包。 变量文件将位于~/httpd/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 Whitelist http in firewalld :此任务将Whitelist http in firewalld中将http服务列入白名单。 Firewalld是CentOS服务器默认存在的完整防火墙解决方案。 为了使http服务起作用,我们需要公开所需的端口。 指示firewalld将服务列入白名单可确保将该服务所需的所有端口列入白名单。

完成后保存并关闭文件。

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

mkdir templates

创建页面本身:

nano templates/index.html.j2

粘贴以下样板代码:

〜/ httpd的/模板/ index.html.j2
<div style="text-align: center">
    <h2>Managed by Ansible</h2>
</div>

保存并关闭文件。

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

nano vars/main.yml

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

瓦尔/ main.yml
---
pkg_list:
  - httpd
  - firewalld
svc_list:
  - httpd
  - firewalld

这些列表包含以下信息:

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

注意:确保变量文件没有任何空行,否则在测试过程中测试将失败。

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

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

配置分子涉及两个步骤:修改分子配置文件本身,并创建自定义yamllint文件。 Yamllint是一个YAML代码linter,用于检查语法有效性,关键重复以及线条长度,尾随空格和缩进等外观问题。

我们的修改将包括:

  • ~/httpd/molecule/default/molecule.yml添加一个选项以使用自定义yamllint配置文件并创建文件本身。 该文件将启用两个例外:大于80个字符的行和真值。 因为Ansible和Yamllint使用冲突的语法来表达真值,所以这将防止不必要的语法错误。
  • 添加平台规格。 由于我们正在测试配置和启动httpd systemd服务的角色,因此我们需要使用启用了systemd配置和特权模式的映像。 在本教程中,我们将使用Docker Hub上提供milcom/centos7-systemd映像。 特权模式允许容器运行几乎所有的主机功能。

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

nano molecule/default/molecule.yml

添加突出显示的yamllint选项和平台信息:

〜/ httpd的/分子/默认/ molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
  options:
    config-file: molecule/default/yamllint.yml
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.yml的参考文献在molecule.yml molecule/default/yamllint.ymlyamllint文件中。 我们来创建这个文件:

nano molecule/default/yamllint.yml

通过粘贴以下规则来输入测试环境的自定义配置,这些规则定义了行长度规格和真实值设置:

〜/ httpd的/分子/默认/ yamllint.yml
---
extends: default
rules:
  line-length:
    max: 120
    level: warning
  truthy: disable

我们添加了两条规则:

  • line_length :此规则指定允许的最大行长度为120个字符(最多80个字符),并且如果违反规则,linter应该发出警告。
  • truthy :由于Ansible和Yamllint使用冲突的语法来表达它们,因此此规则会禁用真值。 这将防止不必要的语法错误。

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

第5步 - 编写测试用例

在测试这个角色时,我们会检查以下条件:

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

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

要编写这些条件的测试用例,我们编辑~/httpd/molecule/default/tests/test_default.py 使用Testinfra,我们将编写测试用例作为使用Molecule类的Python函数。

打开test_default.py

nano molecule/default/tests/test_default.py

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

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

首先导入所需的Python模块:

〜/ httpd的/分子/默认/测试/ test_default.py
import os
import pytest

import testinfra.utils.ansible_runner

这些模块包括:

  • os :这个内置的Python模块支持与操作系统相关的功能,使Python有可能与底层操作系统进行交互。
  • pytestpytest模块支持测试写入。
  • testinfra.utils.ansible_runner :这个Testinfra模块使用Ansible作为命令执行的后端

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

〜/ httpd的/分子/默认/测试/ test_default.py
...
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')

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

第一个测试将确保安装httpdfirewalld

〜/ httpd的/分子/默认/测试/ 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作为参数:

〜/ httpd的/分子/默认/测试/ 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

〜/ httpd的/分子/默认/测试/ 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

完成的文件如下所示:

〜/ httpd的/分子/默认/测试/ 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 test

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

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

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

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

yamllint动作执行yamllintflake8flake8 ansible-lint

  • yamllint :该yamllint在角色目录中的所有YAML文件上执行。
  • flake8 :这个Python代码linter检查为Testinfra创建的测试。
  • ansible-lint :用于Ansible Playbook的linter在所有情况下都会执行。
Output...
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/sammy/httpd/...
Lint completed successfully.
--> Executing Flake8 on files found in /home/sammy/httpd/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/sammy/httpd/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)

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

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

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

销毁行动完成后,测试将转向依赖性 如果你的角色需要他们,这个动作可以让你从依赖关系中ansible-galaxy出来。 在这种情况下,我们的角色不会:

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

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

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

    playbook: /home/sammy/httpd/molecule/default/playbook.yml

接下来,测试转到创建操作。 这使用我们角色的Molecule目录中的create.yml文件来创建一个Docker容器,其中包含我们的规范:

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

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

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

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

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

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

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

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

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

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

创建完成后,测试转到准备操作。 此操作执行准备剧本,在运行收敛之前将主机置于特定状态。 如果您的角色需要在角色执行前预先配置系统,这非常有用。 再次,这不适用于我们的角色:

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

准备好之后, 融合操作通过运行playbook.yml手册在容器上执行您的角色。 如果在molecule.yml文件中配置了多个平台,分子将集中在所有这些平台上:

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

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

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

    TASK [httpd : Ensure required packages are present] ****************************
    changed: [centos7] => (item=[u'httpd', u'firewalld'])

    TASK [httpd : Ensure latest index.html is present] *****************************
    changed: [centos7]

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

    TASK [httpd : 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.

下一个测试操作是副作用操作。 这使您可以生成可以测试更多内容的情况,如HA故障切换。 默认情况下,Molecule不配置副作用剧本并跳过任务:

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

然后分子将使用默认验证程序Testinfra运行验证程序操作。 此操作将执行您之前在test_default.py编写的测试。 如果所有测试成功通过,您将看到成功消息,分子将进入下一步:

Output...
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/sammy/httpd/molecule/default/tests/...
    ============================= test session starts ==============================
    platform linux2 -- Python 2.7.12, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
    rootdir: /home/sammy/httpd/molecule/default, inifile:
    plugins: testinfra-1.12.0
collected 6 items

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

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

最后,分子销毁测试期间完成的实例并删除分配给这些实例的网络:

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

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

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

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

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

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

测试行动现在已经完成,验证了我们的角色按预期工作。

结论

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

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

赞(52) 打赏
未经允许不得转载:优客志 » 系统运维
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏