Как протестировать роли Ansible с помощью Molecule в Ubuntu 18.04

by moiseevrus

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

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

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

  1. python3 -m venv my_env

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

  1. source my_env/bin/activate

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

  1. python3 -m pip install wheel

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

  1. python3 -m pip install molecule docker

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

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

Далее создадим роль в Molecule.

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

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

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

  1. molecule init role -r ansible-apache -d docker

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

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

  1. cd ansible-apache

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

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

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

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

Находясь в ansible-apacheкаталоге, создайте файл задач для роли, используя nanoили ваш любимый текстовый редактор:

  1. 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страницы шаблона:

  1. mkdir templates

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

  1. nano templates/index.html.j2

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

~/ansible-apache/templates/index.html.j2
<div style="text-align: center">
    <h2>Managed by Ansible</h2>
</div>

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

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

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

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

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

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

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

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

  1. nano molecule/default/molecule.yml

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

~/ansible-apache/молекула/по умолчанию/молекула.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запущены и включены.
  • Что httpслужба включена в настройках вашего брандмауэра.
  • Он содержит те же данные, что index.htmlи в вашем файле шаблона.

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

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

Открыто test_default.py:

  1. nano molecule/default/tests/test_default.py

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

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

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

~/ansible-apache/молекула/по умолчанию/тесты/test_default.py
import os
import pytest

import testinfra.utils.ansible_runner

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

  • os: этот встроенный модуль Python обеспечивает функциональность, зависящую от операционной системы, что позволяет Python взаимодействовать с базовой операционной системой.
  • pytestpytestМодуль позволяет писать тесты.
  • testinfra.utils.ansible_runner: этот модуль Testinfra использует Ansible в качестве серверной части для выполнения команд.

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

~/ansible-apache/молекула/по умолчанию/тесты/test_default.py
...
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')

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

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

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

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

~/ansible-apache/молекула/по умолчанию/тесты/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/молекула/по умолчанию/тесты/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в зависимости от результата теста.

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

~/ansible-apache/молекула/по умолчанию/тесты/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 выполнит действия, которые вы определили в своем сценарии. Теперь давайте снова запустим moleculeсценарий по умолчанию, выполнив действия в тестовой последовательности по умолчанию и внимательно изучив каждое из них.

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

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

Давайте рассмотрим каждое тестовое действие и ожидаемый результат, начиная с линтинга.

Действие линтинга выполняет yamllintflake8и 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

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

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

Следующее тестовое действие — это проверка синтаксиса , которая выполняется в 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.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

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

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

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

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

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

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

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

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

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

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

Статья является переводом digitalocean.com

You may also like

Leave a Comment