Автор выбралMozilla Foundation для получения пожертвования в рамках программыWrite for DOnations.
Вступление
Модульное тестирование вAnsible является ключом к проверке правильности работы ролей. Molecule упрощает этот процесс, позволяя указывать сценарии для тестирования ролей в различных средах. Используя Ansible под капотом, Molecule передает роли провайдеру, который развертывает роль в настроенной среде и вызывает верификатор (например,Testinfra) для проверки отклонения конфигурации. Это гарантирует, что ваша роль внесла все ожидаемые изменения в среду в этом конкретном сценарии.
В этом руководстве вы создадите роль Ansible, которая развертываетApache на хосте и настраиваетfirewalld на CentOS 7. Чтобы проверить правильность работы этой роли, вы создадите тест в Molecule, используяDocker в качестве драйвера и Testinfra, библиотеку Python для тестирования состояния серверов. Molecule предоставит контейнеры Docker для проверки роли, а Testinfra проверит, что сервер настроен так, как задумано. Когда вы закончите, вы сможете создавать несколько тестовых случаев для сборок в разных средах и запускать эти тесты, используя Molecule.
Предпосылки
Прежде чем начать это руководство, вам потребуется следующее:
-
Один сервер Ubuntu 18.04. Следуйте инструкциям в руководствеInitial Server Setup with Ubuntu 18.04, чтобы создать пользователя sudo без полномочий root, и убедитесь, что вы можете подключиться к серверу без пароля.
-
Docker установлен на вашем сервере. Выполните шаги 1 и 2 вHow To Install and Use Docker on Ubuntu 18.04, включая добавление пользователя без полномочий root в группу
docker
. -
Python 3 и
venv
установлены и настроены на вашем сервере. СледуйтеHow To Install Python 3 and Set Up a Programming Environment on an Ubuntu 18.04 Server за руководством. -
Знакомство с Ansible playbooks. Для обзора см.Configuration Management 101: Writing Ansible Playbooks.
[[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_wheel
setuptools
, которое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-systemd
available 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.