Как протестировать Ansible роли с молекулой в Ubuntu 16.04

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

Вступление

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

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

Предпосылки

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

  • Один сервер Ubuntu 16.04. Следуйте инструкциям в руководстве Initial Server Setup с Ubuntu 16.04, чтобы создать пользователя sudo без полномочий root, и убедитесь, что вы можете подключиться к серверу без пароля.

  • Docker установлен на вашем сервере. Выполните шаги 1 и 2 в Как установить и использовать Docker в Ubuntu 16.04 и обязательно добавьте своего пользователя без полномочий root в группу + docker +.

  • Знакомство с Ansible playbooks. Для просмотра см. Https://www.digitalocean.com/community/tutorials/configuration-management-101-writing-ansible-playbooks[Configuration Management 101: Написание Ansible Playbooks].

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

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

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

sudo apt-get update

Это обеспечит включение в ваш репозиторий последней версии пакета + python-pip +, который установит + pip + и Python 2.7. Мы будем использовать + pip + для создания виртуальной среды и установки дополнительных пакетов. Чтобы установить + pip +, запустите:

sudo apt-get install -y python-pip

Используйте + pip + для установки модуля Python `+ virtualenv +:

python -m pip install virtualenv

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

python -m virtualenv

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

source my_env/bin/activate

Установите + молекулу и` + docker` с помощью + pip:

python -m pip install molecule docker

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

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

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

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

Шаг 2 - Создание роли в молекуле

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

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

molecule init role -r ansible-apache -d docker

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

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

cd ansible-apache

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

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

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

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

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

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

Шаг 3 - Настройка Apache и Firewalld

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

Создайте файл задач для роли, используя + nano + или ваш любимый текстовый редактор:

nano tasks/main.yml

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

~ / Анзибль-Apache / задачи / 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 задания:

  • " Убедитесь, что необходимые пакеты присутствуют ": Эта задача установит пакеты, перечисленные в файле переменных в + pkg_list +. Файл переменных будет расположен в + ~ / ansible-apache / vars / main.yml +, и вы создадите его в конце этого шага.

  • " Убедитесь, что последний index.html присутствует ": эта задача скопирует страницу шаблона + index.html.j2 + и вставит ее в файл индекса по умолчанию, `+ / var / www / html / index.html + `, сгенерированный Apache. Вы также создадите шаблон на этом шаге.

  • " Убедитесь, что служба httpd запущена и включена ": эта задача запустится и включит службы, перечисленные в + svc_list + в файле переменных.

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

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

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

mkdir templates

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

nano templates/index.html.j2

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

~ / Анзибль-апаш / шаблоны / index.html.j2

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

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

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

nano vars/main.yml

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

~ / Анзибль-апаш / вары / main.yml

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

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

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

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

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

Шаг 4 - Изменение роли для запуска тестов

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

Давайте отредактируем + молекулу.имл +, чтобы отразить эти изменения:

nano molecule/default/molecule.yml

Добавьте выделенную информацию о платформе:

~ / Анзибль-апаш / молекула / по умолчанию / molecule.yml

---
dependency:
 name: galaxy
driver:
 name: docker
lint:
 name: yamllint
platforms:



provisioner:
 name: ansible
 lint:
   name: ansible-lint
scenario:
 name: default
verifier:
 name: testinfra
 lint:
   name: flake8

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

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

Шаг 5 - Написание тестовых случаев

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

  • Установлены пакеты + httpd + и + firewalld +.

  • Службы + httpd + и + firewalld + работают и включены.

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

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

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

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

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

nano molecule/default/tests/test_default.py

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

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

~ / Анзибль-апаш / молек / по умолчанию / тесты / test_default.py

import os
import pytest

import testinfra.utils.ansible_runner

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

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

  • + pytest +: модуль https://docs.pytest.org/en/latest/ [+ pytest +] позволяет писать тесты.

  • + testinfra.utils.ansible_runner +: этот модуль Testinfra использует Ansible в качестве серверной части для выполнения команды.

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

~ / Анзибль-апаш / молек / по умолчанию / тесты / test_default.py

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

С вашим тестовым файлом, настроенным для использования бэкэнда Ansible, давайте напишем модульные тесты для проверки состояния хоста.

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

~ / Анзибль-апаш / молек / по умолчанию / тесты / test_default.py

...

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

   assert package.is_installed

Тест начинается с https://docs.pytest.org/en/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions [+ pytest.mark.parametrize + decorator], который позволяет нам параметризировать аргументы для теста. Этот первый тест примет + test_pkg + в качестве параметра для проверки наличия пакетов + httpd + и + firewalld +.

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

~ / Анзибль-апаш / молек / по умолчанию / тесты / 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

Последний тест проверяет, существуют ли файлы и содержимое, переданные в + parametertrize () +. Если файл не создан вашей ролью, а содержимое не настроено должным образом, + assert + вернет + False +:

~ / Анзибль-апаш / молек / по умолчанию / тесты / 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 возвращает` + True` или + False в зависимости от результата теста.

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

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

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

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 +: этот linter для Ansible playbooks выполняется во всех сценариях.

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. Это действие позволяет вам извлекать зависимости из https://galaxy.ansible.com/ [+ ansible-galaxy +], если ваша роль требует их. В этом случае роль не:

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

Следующим тестовым действием является проверка syntax, которая выполняется в playbook по умолчанию + playbook.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 + playbook.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 +. Если все тесты пройдены успешно, вы увидите сообщение об успехе, и Molecule перейдет к следующему шагу:

Output...
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/...
   ============================= test session starts ==============================
   platform linux2 -- Python 2.7.12, pytest-3.8.0, py-1.6.0, 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 56.73 seconds ===========================

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

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 документация - лучший ресурс для изучения, как использовать Molecule.

Related