Site icon 8HOST.COM

Тестирование ролей Ansible с помощью Molecule в Ubuntu 18.04

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

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

Требования

1: Подготовка среды

Чтобы создать роль и тесты, давайте создадим виртуальную среду и проверим установку Python 3, venv и Docker.

Для начала войдите в систему как пользователь sudo и создайте виртуальную среду:

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

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

2: Создание роли в Molecule

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

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

molecule init role -r ansible-apache -d docker

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

Перейдите в новый каталог:

cd ansible-apache

Протестируйте новую роль, чтобы убедиться, что система Molecule настроена правильно.

molecule test

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

--> Validating schema /home/8host/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 для каждого теста и убедитесь, что ни одно из действий по умолчанию не возвращает ошибку. Например, PLAY_RECAP для действия по умолчанию ‘create’ должно выглядеть так:

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

Теперь давайте подготовим роль для настройки Apache и Firewalld.

3: Настройка Apache и Firewalld

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

В каталоге ansible-apache создайте файл задач для роли:

nano tasks/main.yml

Вы увидите, что такой файл уже существует. Удалите его содержимое и вставьте в него следующий код, который установит требуемые пакеты и включит необходимые сервисы, HTML и параметры брандмауэра:

---
- 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 задачи.

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

Теперь создайте каталог templates для шаблона index.html.j2.

mkdir templates

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

nano templates/index.html.j2

Вставьте в файл стандартный код:

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

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

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

nano vars/main.yml

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

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

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

Примечание: Убедитесь, что в файле переменных нет пустых строк, иначе файл не пройдет проверку кода.

4: Настройка роли для запуска тестов

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

Отредактируйте файл molecule.yml:

nano 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 запустит после выполнения роли.

5: Создание тестовых сценариев

Тесты для этой роли будут проверять такие условия:

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

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

Откройте test_default.py:

nano molecule/default/tests/test_default.py

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

Примечание: Все тестовые сценарии должны отделяться друг от друга пустыми строками, иначе они не будут работать.

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

import os
import pytest
import testinfra.utils.ansible_runner

Вот эти модули:

Импортировав модули, вставьте следующий код, который использует бэкэнд Ansible для возврата экземпляра текущего хоста.

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

Теперь тестовый файл настроен для использования бэкэнда Ansible и можно написать юнит-тесты для проверки состояния хоста.

Первый тест проверит установку httpd и firewalld:

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

Тест начинается с pytest.mark.parametrize, что позволяет параметризировать аргументы для теста. Этот первый тест примет test_pkg в качестве параметра для проверки наличия пакетов httpd и firewalld.

Следующий тест проверяет, запущены и включены ли сервисы httpd и firewalld. Он принимает в качестве параметра test_svc:

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

...
@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 будет возвращать True или False в зависимости от результата теста.

В результате тесты выглядят так:

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

Запустите сценарий по умолчанию еще раз:

molecule test

Это запустит тестовый прогон. Исходный вывод отобразит тестовую матрицу по умолчанию:

--> Validating schema /home/8host/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:

...
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/8host/ansible-apache/...
Lint completed successfully.
--> Executing Flake8 on files found in /home/8host/ansible-apache/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/8host/ansible-apache/molecule/default/playbook.yml...
Lint completed successfully.

Следующее действие, destroy, выполняется с помощью файла destroy.yml. Это проверяет роль на новом контейнере.

По умолчанию destroy вызывается дважды: в начале тестирования (чтобы удалить все ранее существующие контейнеры), и в конце (чтобы удалить новые контейнеры).

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

После завершения действия destroy тест переходит к действию dependency. Это действие позволяет загрузит зависимости из ansible-galaxy, если ваша роль требует их. В этом случае они не нужны.

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

Следующее действие – syntax, проверка синтаксиса, которая выполняется в плейбуке по умолчанию playbook.yml. Она работает аналогично флагу –syntax-check в команде ansible-playbook –syntax-check playbook.yml.

...
--> Scenario: 'default'
--> Action: 'syntax'
playbook: /home/8host/ansible-apache/molecule/default/playbook.yml

Затем тест переходит к действию create. Он использует файл create.yml в каталоге Molecule вашей роли, чтобы создать контейнер Docker с указанными спецификациями:

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

После create тест переходит к действию prepare. Это действие выполняет подготовительный плейбук, который переводит хост в определенное состояние перед запуском действия converge. Это полезно, если ваша роль требует предварительной настройки системы перед выполнением. Опять же, это не относится к нашей роли.

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

После подготовки действие converge выполняет роль в контейнере, запустив playbook.yml. Если в файле molecule.yml  настроено несколько платформ, Molecule объединит их.

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

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

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

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

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

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

...
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/8host/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/8host/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.

В завершение Molecule выполняет destroy, чтобы удалить все экземпляры, созданные во время тестирования, и сеть, присвоенную этим экземплярам.

...
--> 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 – лучший ресурс для изучения этого инструмента.