Как протестировать ансамблевые роли с помощью молекулы в Ubuntu 18.04

Автор выбралMozilla Foundation для получения пожертвования в рамках программыWrite for DOnations.

Вступление

Модульное тестирование вAnsible является ключом к проверке правильности работы ролей. Molecule упрощает этот процесс, позволяя указывать сценарии для тестирования ролей в различных средах. Используя Ansible под капотом, Molecule передает роли провайдеру, который развертывает роль в настроенной среде и вызывает верификатор (например,Testinfra) для проверки отклонения конфигурации. Это гарантирует, что ваша роль внесла все ожидаемые изменения в среду в этом конкретном сценарии.

В этом руководстве вы создадите роль Ansible, которая развертываетApache на хосте и настраиваетfirewalld на CentOS 7. Чтобы проверить правильность работы этой роли, вы создадите тест в Molecule, используяDocker в качестве драйвера и Testinfra, библиотеку Python для тестирования состояния серверов. Molecule предоставит контейнеры Docker для проверки роли, а Testinfra проверит, что сервер настроен так, как задумано. Когда вы закончите, вы сможете создавать несколько тестовых случаев для сборок в разных средах и запускать эти тесты, используя Molecule.

Предпосылки

Прежде чем начать это руководство, вам потребуется следующее:

[[step-1 -—- prepare-the-environment]] == Шаг 1. Подготовка окружающей среды

Если вы выполнили предварительные требования, у вас должны быть установлены и правильно настроены Python 3,venv и Docker. Давайте начнем с создания виртуальной среды для тестирования Ansible с Molecule.

Начните с входа в систему как пользователь без полномочий root и создания новой виртуальной среды:

python3 -m venv my_env

Активируйте его, чтобы убедиться, что ваши действия ограничены этой средой:

source my_env/bin/activate

Затем в активированной среде установите пакетwheel, который предоставляет расширениеbdist_wheelsetuptools, котороеpip использует для установки Ansible:

python3 -m pip install wheel

Теперь вы можете установитьmolecule иdocker с помощьюpip. Ansible будет автоматически установлен как зависимость для Molecule:

python3 -m pip install molecule docker

Вот что будет делать каждый из этих пакетов:

  • molecule: это основной пакет Molecule, который вы будете использовать для тестирования ролей. Установкаmolecule автоматически устанавливает Ansible вместе с другими зависимостями и позволяет использовать сценарии Ansible для выполнения ролей и тестов.

  • docker: эта библиотека Python используется Molecule для взаимодействия с Docker. Это понадобится вам, поскольку вы используете Docker в качестве драйвера.

Далее, давайте создадим роль в Молекуле.

[[шаг-2 -—- создание-роль-в-молекуле]] == Шаг 2 - Создание роли в молекуле

После настройки среды вы можете использовать Molecule для создания основной роли, которую вы будете использовать для тестирования установки Apache. Эта роль создаст структуру каталогов и некоторые начальные тесты и определит Docker в качестве драйвера, чтобы Molecule использовал Docker для выполнения своих тестов.

Создайте новую роль с именемansible-apache:

molecule init role -r ansible-apache -d docker

Флаг-r указывает имя роли, а-d указывает драйвер, который подготавливает хосты для использования Molecule в тестировании.

Перейдите в каталог вновь созданной роли:

cd ansible-apache

Проверьте роль по умолчанию, чтобы проверить, правильно ли настроена Молекула:

molecule test

Вы увидите вывод, в котором перечислены все тестовые действия по умолчанию. Перед запуском теста Molecule проверяет файл конфигурацииmolecule.yml, чтобы убедиться, что все в порядке. Он также печатает эту тестовую матрицу, которая определяет порядок тестовых действий:

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
...

Мы подробно обсудим каждое действие теста после того, как вы создали свою роль и настроили свои тесты. А пока обратите внимание наPLAY_RECAP для каждого теста и убедитесь, что ни одно из действий по умолчанию не возвращает статусfailed. Например,PLAY_RECAP для действия по умолчанию'create' должно выглядеть так:

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

Давайте перейдем к изменению роли для настройки Apache и firewalld.

[[step-3 -—- configuring-apache-and-firewalld]] == Шаг 3. Настройка Apache и Firewalld

Чтобы настроить Apache и firewalld, вы создадите файл задач для роли с указанием устанавливаемых пакетов и включаемых служб. Эти данные будут извлечены из файла переменных и шаблона, которые вы будете использовать для замены индексной страницы Apache по умолчанию.

По-прежнему в каталогеansible-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 и вставит ее в индексный файл по умолчанию/var/www/html/index.html, созданный Apache. На этом шаге вы также создадите новый шаблон.

  • "Ensure httpd service is started and enabled": эта задача запустит и включит службы, перечисленные вsvc_list в файле переменных.

  • "Whitelist http in firewalld": эта задача внесет службуhttp в белый список вfirewalld. Firewalld - это законченное решение брандмауэра, присутствующее по умолчанию на серверах CentOS. Чтобы службаhttp работала, вам необходимо предоставить необходимые порты. Указаниеfirewalld внести службу в белый список гарантирует, что она занесет в белый список все порты, которые требуются службе.

Сохраните и закройте файл, когда вы закончите.

Затем давайте создадим каталогtemplates для страницы шаблонаindex.html.j2:

mkdir templates

Создайте саму страницу:

nano templates/index.html.j2

Вставьте следующий шаблонный код:

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

Managed by Ansible

Сохраните и закройте файл.

Последний шаг в завершении роли - это запись файла переменных, в котором указаны имена пакетов и сервисов для нашей основной ролевой книги:

nano vars/main.yml

Вставьте содержимое по умолчанию с помощью следующего кода, который определяетpkg_list иsvc_list:

~/ansible-apache/vars/main.yml

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

Эти списки содержат следующую информацию:

  • pkg_list: содержит имена пакетов, которые роль установит:httpd иfirewalld.

  • svc_list: содержит имена служб, которые роль будет запускать и включать:httpd иfirewalld.

[.note] #Note: Убедитесь, что в вашем файле переменных нет пустых строк, иначе ваш тест завершится неудачно во время линтинга.
#

Теперь, когда вы закончили создавать свою роль, давайте настроим Molecule, чтобы проверить, работает ли она как задумано.

[[step-4 -—- modifying-the-role-for-running-tests]] == Шаг 4 - изменение роли для запуска тестов

В нашем случае настройка Molecule включает изменение файла конфигурации Moleculemolecule.yml для добавления спецификаций платформы. Поскольку вы тестируете роль, которая настраивает и запускает службу systemdhttpd, вам потребуется использовать образ с настроенной системой systemd и включенным привилегированным режимом. В этом руководстве вы будете использовать изображениеmilcom/centos7-systemdavailable on Docker Hub. Привилегированный режим позволяет контейнерам работать практически со всеми возможностями своего хост-компьютера.

Давайте отредактируем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 будет запускать для вашего контейнера после выполнения роли.

[[step-5 -—- writing-test-case]] == Шаг 5. Написание тестовых примеров

В тесте на эту роль вы будете проверять следующие условия:

  • Пакетыhttpd иfirewalld установлены.

  • Что службыhttpd иfirewalld запущены и включены.

  • Службаhttp включена в настройках вашего брандмауэра.

  • Этотindex.html содержит те же данные, которые указаны в вашем файле шаблона.

Если все эти тесты пройдены, то роль работает так, как задумано.

Чтобы написать тестовые примеры для этих условий, давайте отредактируем тесты по умолчанию в~/ansible-apache/molecule/default/tests/test_default.py. Используя Testinfra, мы напишем контрольные примеры как функции Python, которые используют классы Molecule.

Открытьtest_default.py:

nano molecule/default/tests/test_default.py

Удалите содержимое файла, чтобы вы могли писать тесты с нуля.

[.note] #Note: При написании тестов убедитесь, что они разделены двумя новыми строками, иначе они завершатся ошибкой.
#

Начните с импорта необходимых модулей Python:

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

import os
import pytest

import testinfra.utils.ansible_runner

Эти модули включают в себя:

  • os: этот встроенный модуль Python обеспечивает функциональные возможности, зависящие от операционной системы, что позволяет Python взаимодействовать с базовой операционной системой.

  • pytest: модульpytest позволяет писать тесты.

  • testinfra.utils.ansible_runner: этот модуль Testinfra используетAnsible as the backend для выполнения команд.

Под импортом модуля добавьте следующий код, который использует бэкэнд 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, давайте напишем модульные тесты, чтобы проверить состояние хоста.

Первый тест гарантирует, чтоhttpd иfirewalld установлены:

~/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 decorator, что позволяет нам параметризовать аргументы для теста. В этом первом тестеtest_pkg будет параметром для проверки наличия пакетовhttpd иfirewalld.

Следующий тест проверяет, запущены и включены лиhttpd иfirewalld. В качестве параметра принимает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", ""),
  ("/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 будет возвращатьTrue илиFalse в зависимости от результата теста.

Готовый файл выглядит так:

~/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", ""),
  ("/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 -—- тестирование-роль-с-молекулой]] == Шаг 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 выполняетyamllint,flake8 иansible-lint:

  • yamllint: этот линтер выполняется для всех файлов YAML, присутствующих в каталоге роли.

  • flake8: этот анализатор кода Python проверяет тесты, созданные для Testinfra.

  • ansible-lint: этот линтер для плейбуков Ansible выполняется во всех сценариях.

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, которая выполняется в playbookplaybook.yml по умолчанию. Он работает аналогично флагу--syntax-check в командеansible-playbook --syntax-check playbook.yml:

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

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

Далее тест переходит к действиюcreate. Здесь используется файлcreate.yml в каталоге Molecule вашей роли для создания контейнера 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

После покрытия тест переходит кidempotence. Это действие проверяет книгу на предмет идемпотентности, чтобы убедиться, что в нескольких прогонах не было внесено неожиданных изменений:

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

Следующее тестовое действие - это действиеside-effect. Это позволяет создавать ситуации, в которых вы сможете тестировать больше вещей, например, отработка отказа HA. По умолчанию Molecule не конфигурирует playbook с побочными эффектами, и задача пропускается:

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

Затем Molecule выполнит действиеverifier, используя верификатор по умолчанию, Testinfra. Это действие выполняет тесты, которые вы написали ранее вtest_default.py. Если все тесты пройдены успешно, вы увидите сообщение об успехе, и Молекула перейдет к следующему шагу:

Output...
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/...
    ============================= test session starts ==============================
    platform linux -- Python 3.6.5, pytest-3.7.3, py-1.5.4, 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 41.05 seconds ===========================
Verifier completed successfully.

Наконец, Moleculedestroysустанавливает экземпляры, завершенные во время теста, и удаляет сеть, назначенную этим экземплярам:

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, которые Молекула использовала, чтобы утверждать, что роль успешно выполнена.

Вы можете использовать один и тот же базовый метод для очень сложных ролей, а также автоматизировать тестирование с использованием конвейера CI. Molecule - это настраиваемый инструмент, который можно использовать для тестирования ролей с любыми провайдерами, которые поддерживает Ansible, а не только с Docker. Также возможно автоматизировать тестирование в соответствии с собственной инфраструктурой, следя за тем, чтобы ваши роли всегда были современными и функциональными. Вы можете интегрировать непрерывное тестирование в свой рабочий процесс с помощью Molecule и Travis CI с помощью руководстваHow To Implement Continuous Testing of Ansible Roles Using Molecule and Travis CI on Ubuntu 18.04.

ОфициальныйMolecule documentation - лучший ресурс для изучения использования Molecule.

Related