Тестирование ролей Ansible с помощью Molecule в Ubuntu 18.04
Centos, Ubuntu | Комментировать запись
Юнит-тесты в Ansible позволяют убедиться, что роли работают должным образом. Molecule упрощает этот процесс с помощью сценариев, которые тестируют роли в разных средах. Molecule выгружает роли оркестровщику, который развертывает роль в сконфигурированной среде и вызывает верификатор (например, Testinfra) для проверки отклонения конфигурации. Это позволяет убедиться, что роль внесла все ожидаемые изменения в среду в этом конкретном сценарии.
В этом мануале мы построим роль Ansible для развертывания Apache и настройки Firewalld в CentOS 7. Для тестирования этой роли мы создадим тест Molecule, используя драйвер Docker и Testinfra, библиотеку Python для тестирования состояний сервера. Molecule оркеструет контейнеры Docker для проверки роли, а Testinfra обеспечит правильную настройку сервера. В результате вы сможете создать несколько тестов для сборок в разных средах и запускать эти тесты с помощью Molecule.
Требования
- Сервер Ubuntu 18.04, настроенный по этому мануалу.
- Установленный Docker (читайте мануал Установка и использование Docker в Ubuntu 18.04). Не забудьте добавить своего пользователя sudo в группу docker.
- Предустановленные Python 3 и venv (инструкции в мануале Установка Python 3 и настройка среды разработки на сервере Ubuntu 18.04).
- Базовые навыки работы с плейбуками Ansible. Рекомендуем ознакомиться с мануалом Создание плейбука Ansible.
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
Вот что делает каждый из этих пакетов:
- molecule: это основной пакет Molecule, который вы будете использовать для проверки ролей. Установка molecule автоматически устанавливает Ansible вместе с другими зависимостями и позволяет использовать плейбуки Ansible для запуска ролей и тестов.
- docker: эта библиотека Python используется Molecule для взаимодействия с Docker (здесь 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 задачи.
- “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, вы откроете все порты, которые требуются этому сервису.
Сохраните и закройте файл.
Теперь создайте каталог 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
Эти списки предоставляют следующую информацию:
- 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: Создание тестовых сценариев
Тесты для этой роли будут проверять такие условия:
- Что пакеты httpd и firewalld установлены.
- Что сервисы httpd и firewalld запущены и включены.
- Что сервис httpd включен в параметрах firewalld.
- Что index.html содержит те же данные, что и шаблон.
Если все эти тесты пройдены успешно, значит, роль работает правильно.
Чтобы написать тестовые сценарии для этих условий, нужно отредактировать тесты по умолчанию в ~/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
Вот эти модули:
- os: этот встроенный модуль Python обеспечивает функции, зависящие от операционной системы, что позволяет Python взаимодействовать с базовой операционной системой.
- pytest: позволяет писать тесты.
- testinfra.utils.ansible_runner: использует Ansible в качестве бэкэнда для выполнения команд.
Импортировав модули, вставьте следующий код, который использует бэкэнд 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:
- yamllint: этот линтер выполняется для всех файлов YAML, присутствующих в каталоге ролей.
- flake8: эта строка кода Python проверяет тесты, созданные для Testinfra.
- ansible-lint: этот линтер плейбуков Ansible выполняется во всех сценариях.
...
--> 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 – лучший ресурс для изучения этого инструмента.
Tags: Ansible, CentOS 7, Molecule, Python 3, Ubuntu, Ubuntu 18.04