Русский
Русский
English
Статистика
Реклама

Molecule

Разработка и тестирование Ansible-ролей с использованием Molecule и Podman

17.09.2020 10:15:44 | Автор: admin
Одно из основных преимуществ Red Hat Ansible Automation Platform заключается в том, что ее язык описания автоматизаций читабелен не только для пары-тройки гуру, но и почти для всех, кто имеет отношение к ИТ. Поэтому вносить свой вклад в автоматизацию могут любые специалисты, что сильно облегчает организацию межкомандного взаимодействия и внедрение автоматизации на уровне корпоративной культуры.



Однако, когда делом занимается такая масса народа, очень важно все тщательно тестировать. При разработке Ansible-контента, вроде плейбуков, ролей или коллекций, мы очень рекомендуем предварительно проверять все в тестовой среде, прежде чем выкатывать в продакшн. Цель такого тестирования удостовериться, что все работает как надо, чтобы избежать неприятных сюрпризов на боевых системах.

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

Вот как это декларируется в документации проекта:

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

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

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

Если роль является частью коллекции, используйте этот подход для разработки и юнит-тестирования роли. В следующей статье мы покажем, как применять Molecule для выполнения интегрированных тестов в коллекции.

Molecule использует драйверы для предоставления целевых инстансов на базе различных технологий, включая Linux-контейнеры, виртуальные машины и облачных провайдеров. По умолчанию он идет с тремя предустановленными драйверами: Docker и Podman для контейнеров, а также драйвером Delegated для создания кастомных интеграций. Драйверы для других провайдеров предоставляются сообществом разработки проекта.

В этом посте мы будем использовать драйвер Podman для разработки и тестирования новой роли с использованием Linux-контейнеров. Podman это легковесный контейнерный движок для Linux, ему не нужен работающий демон и он обеспечивает запуск rootless-контейнеров, что хорошо для безопасности.

Используя Molecule с драйвером Podman, мы с нуля разработаем и протестируем новую роль Ansible, которая развертывает веб-приложение на базе веб-сервера Apache и должна работать на Red Hat Enterprise Linux (RHEL) 8 или Ubuntu 20.04.

Мы разбираем типовой сценарий, когда роль должна работать на различных версиях операционной системы. Используя Podman и Linux-контейнеры, мы можем создать несколько инстансов, чтобы протестировать роль на разных версиях ОС. Благодаря своей легковесности, контейнеры позволяют быстро итерировать функциональность роли прямо по ходу разработки. Использование контейнеров для тестирования ролей применимо в данной ситуации, поскольку роль конфигурирует только запущенные инстансы Linux. Для тестирования на других целевых системах или облачных инфраструктурах можно использовать драйвер delegated или другие драйверы, предоставляемые сообществом.

Что нам понадобится


Для примеров из этой статьи нужна физическая или виртуальная машина Linux с установленными Python 3 и Podman (мы используем RHEL 8.2). Также Podman должен был сконфигурирован для запуска rootless-контейнеров. Установка Podman выходит за рамки этой статьи, для получения соответствующей информации см. официальную документацию. Установка Podman на RHEL 8 также описывается в документации по контейнерам RHEL 8.

Приступаем


Molecule выполнен в виде Python-пакета и, соответственно, устанавливается через pip. Первым шагом мы создаем выделенное Python-окружение и устанавливаем в него наш Molecule:

$ mkdir molecule-blog$ cd molecule-blog$ python3 -m venv molecule-venv$ source molecule-venv/bin/activate(molecule-venv) $ pip install "molecule[lint]"

Обратите внимание, что мы устанавливаем Molecule с опцией lint, чтобы pip также поставил инструменты yamllint и ansible-lint, которые позволят нам использовать Molecule для статического анализа кода роли на предмет соответствия стандартам кодирования Ansible.

Установка скачивает все необходимые зависимости из интернета, включая Ansible. Теперь смотрим что мы установили:

$ molecule --versionmolecule 3.0.4   ansible==2.9.10 python==3.6

Что ж, пора использовать команду molecule, чтобы инициализировать новую роль Ansible.

Инициализируем новую роль Ansible


Вообще говоря, при разработке новой роли Ansible, она инициализируется командой ansible-galaxy role init, но мы будем использовать вместо этого команду molecule. При этом мы получим ту же структуру роли, что и с командой ansible-galaxy, а также базовую заготовку кода для запуска тестов Molecule.

По умолчанию Molecule использует для выполнения тестов драйвер Docker. Поскольку мы хотим использовать вместо него podman, то при инициализации роли командой molecule надо указать соответствующий драйвер с помощью опции --driver-name=podman.

Переключаемся обратно в каталог molecule-blog и инициализируем новую роль mywebapp следующей командой:

$ molecule init role mywebapp --driver-name=podman--> Initializing new role mywebapp...Initialized role in /home/ricardo/molecule-blog/mywebapp successfully.

Molecule создает структуру нашей роли в папке mywebapp. Переключаемся в эту папку и смотрим, что там:

$ cd mywebapp$ tree. defaults    main.yml files handlers    main.yml meta    main.yml molecule    default        converge.yml        INSTALL.rst        molecule.yml        verify.yml README.md tasks    main.yml templates tests    inventory    test.yml vars     main.yml 10 directories, 12 files

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

Проверим базовую конфигурацию в файле molecule/default/molecule.yml:

$ cat molecule/default/molecule.yml ---dependency:  name: galaxydriver:  name: podmanplatforms:  - name: instance    image: docker.io/pycontribs/centos:7    pre_build_image: trueprovisioner:  name: ansibleverifier:  name: ansible

Как мы и просили, в этом файле указано, что для тестов применяется драйвер Podman. Здесь же задается платформа по умолчанию для тестового инстанса, через контейнерный образ docker.io/pycontribs/centos:7, который мы потом поменяем.

В отличие Molecule v2, Molecule v3 не задает линтер по умолчанию. Поэтому откроем конфигурационный файл molecule/default/molecule.yml и допишем в конце конфигурацию lint:

$ vi molecule/default/molecule.yml...verifier:  name: ansiblelint: |  set -e  yamllint .  ansible-lint .

Сохраним и закроем файл, и запустим команду molecule lint из корневой папки нашего проекта, чтобы прогнать линтер по всему проекту:

$ molecule lint

На выходе получаем несколько ошибок, поскольку в файле meta/main.yml нет ряда требуемых значений. Исправим это: отредактируем файл meta/main.yml, добавив author, company, license, platforms и удалив пустую строку в конце. Для краткости обойдемся без комментариев, и тогда наш meta/main.yaml будет выглядеть так:

$ vi meta/main.ymlgalaxy_info:  author: Ricardo Gerardi  description: Mywebapp role deploys a sample web app   company: Red Hat    license: MIT    min_ansible_version: 2.9   platforms:  - name: rhel    versions:    - 8   - name: ubuntu    versions:    - 20.04   galaxy_tags: [] dependencies: []

Еще раз прогоним по проекту линтер и убедимся, что ошибок больше нет.

$ molecule lint--> Test matrix     default     dependency     lint    --> Scenario: 'default'--> Action: 'dependency'Skipping, missing the requirements file.Skipping, missing the requirements file.--> Scenario: 'default'--> Action: 'lint'--> Executing: set -eyamllint .ansible-lint . 

Итак, наша роль инициализирована и базовая конфигурация molecule тоже на месте. Теперь создадим тестовый инстанс.

Создаем тестовый инстанс


По умолчанию Molecule задает только один инстанс, которые называется instance и создается из образа Centos:7. Наша роль, если помните, должна работать на RHEL 8 и Ubuntu 20.04. Кроме того, поскольку она запускает веб-сервер Apache в качестве системной службы, нам нужен контейнерный образ с включенным systemd.

У Red Hat есть официальный Universal Base Image для RHEL 8 с включенным systemd:

registry.access.redhat.com/ubi8/ubi-init

Для Ubuntu нет официального образа с systemd, поэтому мы воспользуемся образом, который поддерживается силами Джефа Джирлинга (Jeff Geerling) из сообщества Ansible:

geerlingguy/docker-ubuntu2004-ansible

Чтобы получить инстансы с systemd, подправим конфигурационный файл molecule/default/molecule.yml, убрав из него инстанс centos:7 и добавив два новых инстанса:

$ vi molecule/default/molecule.yml---dependency:  name: galaxydriver:  name: podmanplatforms:  - name: rhel8    image: registry.access.redhat.com/ubi8/ubi-init    tmpfs:      - /run      - /tmp    volumes:      - /sys/fs/cgroup:/sys/fs/cgroup:ro    capabilities:      - SYS_ADMIN    command: "/usr/sbin/init"    pre_build_image: true  - name: ubuntu    image: geerlingguy/docker-ubuntu2004-ansible    tmpfs:      - /run      - /tmp    volumes:      - /sys/fs/cgroup:/sys/fs/cgroup:ro    capabilities:      - SYS_ADMIN    command: "/lib/systemd/systemd"    pre_build_image: trueprovisioner:  name: ansibleverifier:  name: ansiblelint: |  set -e  yamllint .  ansible-lint .

С помощью этих параметров мы монтируем для каждого инстанса временную файловую систему /run и /tmp, а также том cgroup. Кроме того, мы включаем функцию SYS_ADMIN, необходимую для запуска контейнеров с Systemd.

Если делать все по уму и выполнять этот пример на машине RHEL 8 с включенным SELinux, то еще надо установить в true логический параметр container_manage_cgroup, чтобы контейнеры могли запускать Systemd, (подробнее см. документацию RHEL 8):

sudo setsebool -P container_manage_cgroup 1

Для инициализации этих инстансов Molecule использует Ansible Playbook. Изменим и добавим параметры инициализации, модифицировав словарь provisioner в конфигурационном файле molecule/default/molecule.yml.

Он принимает те же самые опции конфигурации, что прописаны в конфигурационном файле ansible.cfg. Например, обновим конфигурацию провайдера (provisioner), добавив секцию defaults. Установим интерпретатор Python в auto_silent, чтобы деактивировать предупреждения. Включим callback-плагины profile_tasks, timer и yaml, чтобы профайлерская информация включалась в вывод Playbook. И наконец, добавим секцию ssh_connection и отключим SSH pipelining, поскольку он не работает с Podman:

provisioner:  name: ansible  config_options:    defaults:      interpreter_python: auto_silent      callback_whitelist: profile_tasks, timer, yaml    ssh_connection:      pipelining: false

Сохраним этот файл и создадим инстанс командой molecule create из корневого каталога нашей роли:

$ molecule create

Molecule выполнит инициализационный плейбук и создаст оба наших инстанса. Проверим их командой molecule list:

$ molecule listInstance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged---------------  -------------  ------------------  ---------------  ---------  -----------rhel8            podman         ansible             default          true       falseubuntu           podman         ansible             default          true       false

Также проверим, что оба контейнера запущены в Podman:

$ podman psCONTAINER ID  IMAGE                                                   COMMAND               CREATED             STATUS                 PORTS  NAMES2e2f14eaa37b  docker.io/geerlingguy/docker-ubuntu2004-ansible:latest  /lib/systemd/syst...  About a minute ago  Up About a minute ago         ubuntu2ce0a0ea8692  registry.access.redhat.com/ubi8/ubi-init:latest         /usr/sbin/init        About a minute ago  Up About a minute ago         rhel8

При разработке роли Molecule использует запущенные инстансы для ее тестирования. Если тест проваливается или какая-то ошибка приводит к необратимым изменениям, из-за которых все надо начинать сначала, вы можете в любое время убить эти инстансы командой molecule destroy и создать их заново командной molecule create.

Заключение


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

Подробнее..

Перевод Проверка ролей Ansible через делегированный драйвер Molecule

12.01.2021 10:14:24 | Автор: admin


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


К сожалению, документация по делегированному драйверу в основном состоит всего из нескольких строк в официальном документе, тогда как более четкое объяснение и несколько примеров могут оказать огромную помощь тем разработчикам, которые хотят использовать Molecule таким образом.


Этот пост основан на моем опыте разработки простой роли Ansible от 0 до galaxy и фокусирует внимание на использовании делегированного драйвера, интегрированного с Google Cloud Platform. В качестве отправной точки я взял следующие полезные ссылки для своего проекта:



Делегированный драйвер: что говорит документация Molecule?


Одна из причин, которая заставила меня написать это руководство, это заявление в официальной документации Molecule:


Разработчик должен придерживаться instance-config API. Пособие по созданию разработчика должно содержать следующие данные instance-config, а руководство по уничтожению разработчика должно сбрасывать instance-config.


Возникает вопрос: что такое instance-config и какие данные должен предоставить разработчик?


Instance-config это факт Ansible, хранящийся в файле YAML в кэше Molecule ( $HOME/.cache/molecule/<role-name>/<scenario-name>/instance_config.yml), который имеет следующую структуру:


- address: 10.10.15.17 identity_file: /home/fabio/.ssh/id_rsa # mutually exclusive with                                        # password instance: millennium_falcon port: 22 user: hansolo# password: ssh_password # mutually exclusive with identity_file become_method: sudo # optional# become_pass: password_if_required # optional

Для тех, кому нужно иметь дело с узлами Windows, документация также предоставляет эквивалентную структуру для WinRM.


Файл create.yml


После того, как мы прояснили, что такое instance-config, мы можем переходить к следующему шагу. К счастью, Molecule также помогает нам сделать дополнительный шаг вперед, предоставляя файлы шаблонов сценариев с помощью команды molecule init, например:


molecule init scenario -driver-name=delegated

который создает следующую структуру каталогов:


. INSTALL.rst converge.yml create.yml destroy.yml molecule.yml verify.yml

  • molecule.yml это файл конфигурации Molecule, который определяет переменные, устанавливает фазовую последовательность и конфигурацию для каждой из них.
  • create.yml код Ansible для создания экземпляров на облачной платформе и хранения данных в instance-config.
  • destroy.yml код Ansible для уничтожения экземпляров на облачной платформе и удаления их из instance-config
  • converge.yml исполнение роли
  • verify.yml набор проверочных тестов
  • INSTALL.rst инструкции по установке необходимых зависимостей для запуска тестов Molecule

Теперь давайте сосредоточимся на файле create.yml, созданном Molecule:


---- name: Create hosts: localhost connection: local gather_facts: false no_log: "{{ molecule_no_log }}" tasks: # Developer must implement. # Developer must map instance config. # Mandatory configuration for Molecule to function.  name: Populate instance config dict set_fact: instance_conf_dict: { 'instance': "{{ }}", 'address': "{{ }}", 'user': "{{ }}", 'port': "{{ }}", 'identity_file': "{{ }}", } with_items: "{{ server.results }}" register: instance_config_dict when: server.changed | bool  name: Convert instance config dict to a list set_fact: instance_conf: {{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" when: server.changed | bool  name: Dump instance config copy: content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}" dest: "{{ molecule_instance_config }}" when: server.changed | bool

Три задачи: заполнение, преобразование и дамп, создание в конце файла instance-config.yml. Прокомментированный раздел является заполнителем для кода Ansible, который должен создавать облачные ресурсы и возвращать массив серверов (содержащий детали экземпляра) в качестве зарегистрированной переменной или факта. Следующий фрагмент кода, взятый из этой проблемы с github, предоставляет пример того, что указано выше для контекста VMWare:


 7     - name: Create molecule instance(s) 8      vmware_guest: 9        hostname: "{{ molecule_yml.driver.hostname }}"10        esxi_hostname: "{{ molecule_yml.driver.esxi_hostname }}"11        username: "{{ molecule_yml.driver.username }}"12        password: "{{ molecule_yml.driver.password }}"13        datacenter: "{{ molecule_yml.driver.datacenter }}"14        validate_certs: "{{ molecule_yml.driver.validate_certs }}"15        resource_pool: "{{ molecule_yml.driver.resource_pool }}"16         folder: "{{ molecule_yml.driver.folder }}"17         name: "{{ item.name }}"18         template: "{{ item.template }}"19         hardware:20           memory_mb: "{{ item.memory | default(omit) }}"21           num_cpus: "{{ item.cpu | default(omit) }}"22         wait_for_ip_address: "yes"23         state: poweredon24       register: server25       with_items: "{{ molecule_yml.platforms }}"26     27     - name: Populate instance config dict28       set_fact:29         instance_conf_dict: {30           'instance': "{{ item.instance.hw_name }}",31           'address': "{{ item.instance.ipv4 }}",32           'user': "vagrant",33           'port': "22",34           'identity_file': 'identity_file': "{{                     molecule_yml.driver.ssh_identity_file }}"35         }36       with_items: "{{ server.results }}"37       register: instance_config_dict38       when: server is changed

Код вызывает модуль vmware_guest (строки 723) для создания виртуальной машины на сервере VMWare. Это делается для каждого элемента массива платформ, определенного в файле molecule.yml (строка 25). Как видите, переменные, определенные в файле molecule.yml, доступны через факт molecule_yml.


Значения, возвращаемые каждым вызовом vmware_guest, регистрируются как элементы массива сервера (строка 24), который, в свою очередь, используется для заполнения конфигурации экземпляра (instance-config) (строки 27 и далее). Обратите внимание, что обновление факта конфигурации экземпляра пропускается, если переменная сервера не изменяется.


Работа с Google Cloud Platform (GCP)


Теперь, когда я разъяснил, что и как должен делать разработчик при работе с делегированным драйвером, я собираюсь поделиться работой, проделанной для моей роли docker-secured Ansible. Для этой роли я решил использовать GCP в качестве облачного сервера для делегированного драйвера. Ansible предоставляет семейство модулей GCP для работы с таким облачным провайдером, и я надеюсь, что вы легко сможете адаптировать мой код, если вам нужно сменить семейство модулей и облачного провайдера.


Для этого проекта я использовал следующие версии инструментов:


  • python 2.7
  • ansible 2.9.6
  • molecule 3.0.2
  • ansible-lint 4.2.0
  • yamllint 1.20.0
  • flake8 3.7.9 (mccabe: 0.6.1, pycodestyle: 2.5.0, pyflakes: 2.1.1) CPython 2.7.17 на Linux

где yamllint, ansible-lint и flake8 инструменты для проверки кода, которые включены в фазы молекулы.


Роль docker-secured


Роль для тестирования устанавливает докер на узел, предоставляет API-интерфейсы докеров и защищает их с помощью ssl. Процедура, которой я следовал, описана в этих двух ссылках из документации Docker:



Репозиторий содержит готовые к использованию файлы сертификатов ssl для целей тестирования, но есть также возможность предоставить свои собственные, если они у вас есть.


Вы можете посмотреть мой проект, клонировав репозиторий GitHub:


git clone https://github.com/fabiomarinetti/fmarinetti.docker-secured.git

Предварительные шаги для GCP


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


Для этой роли я создал проект ansible-272015 и службу учетной записи service, ее ключ хранится в файле secret.json.


Файл molecule.yml


В этом разделе я покажу и прокомментирую соответствующий раздел моего файла molecule.yml.


Проект, тип аутентификации и секретный ключ вставляются в файл molecule.yml в разделе driver. Кроме того, я также добавил в тот же раздел все другие параметры, которые остаются постоянными на этапах создания и уничтожения, например, регион и зона GCP, пользователь ssh и файл идентификатора, а также параметры сети, поскольку предполагается, что виртуальные машины находятся в той же сети, в которой создан ad-hoc на время теста. Ко всем этим значениям можно получить доступ из сценария через molecule_yml (например, molecule_yml.driver.region для доступа к региону).


20 driver:21   name: delegated22   gcp_service_account_key: ${GOOGLE_APPLICATION_CREDENTIALS}23   gcp_project_id: ansible-27201524   region: us-east125   zone: us-east1-c26   ssh_user: ${SSH_USER}27   ssh_pub_key_file: "${SSH_ID_FILE}.pub"28   ssh_key_file: "${SSH_ID_FILE}"29   network_name: ansible-network30   subnet_name: ansible-subnet31   firewall_name: ansible-firewall32   ip_cidr_range: 172.16.0.0/28

Раздел платформы в файле molecule.yml содержит массив, содержащий параметры (имя, изображение, тип, размер) для экземпляров, которые я хочу проверить. Мое тестовое покрытие включает CentOS 7, Ubuntu Xenial 16.04 и Ubuntu Bionic 18.04. Эти машины сгруппированы по типу ОС (например, CentOS или Ubuntu), чтобы использовать группы инвентаря при выполнении Ansible.


41 platforms:42   - name: "ds-centos7-${TRAVIS_BUILD_ID}"43     image_family: projects/centos-cloud/global/images/family                     /centos-744     machine_type: n1-standard-145     size_gb: 20046     groups:47       - centos48   - name: "ds-ubuntu-bionic-${TRAVIS_BUILD_ID}"49     image_family: projects/ubuntu-os-cloud/global/images/family                     /ubuntu-1804-lts50     machine_type: n1-standard-151     size_gb: 20052     groups:53       - ubuntu54   - name:  "ds-ubuntu-xenial-${TRAVIS_BUILD_ID}"55     image_family: projects/ubuntu-os-cloud/global/images/family                     /ubuntu-1604-lts56     machine_type: n1-standard-157     size_gb: 20058     groups:59       - ubuntu

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


Фаза создания и файл create.yml


Как уже говорилось ранее, create.yml это сценарий, который управляет этапом создания. Здесь я широко использовал модули семейства gcp для управления ресурсами облачного провайдера (GCP). Модули GCP нуждаются в каком-то фиксированном параметре, таком как идентификатор проекта, тип аутентификации и путь к секретному ключу, и, чтобы избежать повторения этих значений в коде при каждом вызове модуля, я установил их как module_defaults для всего семейства gcp.


 7   module_defaults: 8     group/gcp: 9       project: "{{ molecule_yml.driver.gcp_project_id }}"10       auth_kind: serviceaccount11       service_account_file: "{{               molecule_yml.driver.gcp_service_account_key }}"

В отличие от того, что мы видели ранее в случае VMWare, создание экземпляра в GCP это не просто вопрос использования одного модуля, а процесс, состоящий из нескольких шагов: создание загрузочного диска, назначение IP-адреса и создание самого экземпляра. Это означает, что для цикла по платформам мне нужно было поместить задачи создания в отдельный файл и включить его в цикл:


16  name: create instances17   include_tasks: tasks/create_instance.yml18   loop: "{{ molecule_yml.platforms }}"

Файл create_instance.yml содержит задачи по резервированию IP-адреса, созданию загрузочного диска и созданию экземпляра. То, как я вызвал связанные модули, довольно стандартно и их можно менять, если вы хотите переключиться на другого облачного провайдера, поэтому я не буду их обсуждать дальше, а лишь хочу сказать несколько слов о том, как возвращать данные экземпляра для подачи задач заполнения instance-config.


7 - name: initialize instance facts 8   set_fact: 9     instance_created:10       instances: []11   when: instance_created is not defined... create the instance and return instance variable ...56 - name: update instance facts57   set_fact:58     instance_created:59       changed: instance.changed | bool60       instances: "{{ instance_created.instances + [ instance ]}}"

Затем после цикла платформы для заполнения isntance-config используется факт instance_create:


20     - name: Populate instance config dict21       set_fact:22         instance_conf_dict: {23           'instance': "{{ item.name }}",24           'address': "{{               item.networkInterfaces[0].accessConfigs[0].natIP }}",25           'user': "{{ molecule_yml.driver.ssh_user }}",26           'port': "22",27           'identity_file': "{{ molecule_yml.driver.ssh_key_file               }}", }28       with_items: "{{ instance_created.instances }}"29       register: instance_config_dict30       when: instance_created.changed

Здесь эта задача выполняется, только если один из экземпляров был изменен, как это произошло в случае VMWare, когда было указано предложение servers is changed


Наконец, я протестировал этап создания, введя команду:


molecule create --scenario-name=gcp

Убедившись, что результаты созданы правильно, я перешел к конвейеру и выполнил / протестировал фазы:


  • lint, которая выполняет проверку кода
  • prepare, которая подготавливает экземпляр для применения роли. В данном случае это просто обновление исходных кодов пакетов для группы ubuntu.
  • converge, которая просто применяет роль
  • idempotence, которая применяет роль во второй раз для обеспечения ее идемпотентности
  • verify, которая подтверждает, что результаты применения роли соответствуют ожиданиям

molecule <phase> --scenario-name=gcp

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


На последнем шаге я написал destroy.yml для удаления созданных ресурсов из проекта (а также моего счета ). Код для уничтожения ресурсов следует той же философии, что и тот, который их создает. Очевидно, проверка проводилась путем выдачи:


molecule destroy --scenario-name=gcp

Как только все этапы были правильными и не выдали ошибок, я мог протестировать весь процесс с помощью команды:


molecule test --scenario-test=gcp

Выводы


В этом посте я объяснил, как использовать делегированный драйвер Molecule, и показал, как я реализовал его с помощью GCP. Этот же код легко адаптировать к другому облачному провайдеру: AWS, Azure, Digital Ocean и я надеюсь, что вы наверняка получите выгоду от использования Molecule. Пожалуйста, оставьте отзыв.

Подробнее..

Тестирование ansible роли для RabbitMQ кластера с помощью molecule

17.05.2021 20:19:35 | Автор: admin

Molecule это фреймворк, предназначенный для тестирования ролей в Ansible. На хабре довольно много статей про тестирование с помощью molecule и почти во всех статьях говорится о неких "сложных сценариях тестирования для ansible", и далее в примерах обычно идут какие-то простенькие роли и тесты. Мне стало интересно протестировать более сложную роль, например роль для создания RabbitMQ кластера.

Используемые версии программ на момент написания статьи. Не гарантируется корректная работа для molecule версии ниже 3.3

debian 10 Buster

ansible-3.4.0

molecule-3.3.0

docker-ce-20.10.6

yamllint-1.26.1

ansible-lint-5.0.8

Устанавливаем ansible и molecule.

pip3 install --user ansible (как именно устанавливать не столь важно, в приведенном примере установка идет в хоумдир пользователя).

pip3 install --user molecule[docker] (мы будем использовать драйвер докера)

Устанавливаем линтеры

pip3 install --user ansible-lint yamllint

Установка докера выходит за рамки этой статьи, стоит отметить только что докер вы можете установить на эту же машину, где будете запускать molecule или же установить докер на любую другую машину в сети (например если мощности локальной машины не хватает) или же использовать уже существующий докер сервер.

Во втором случае на локальную машину нужно установить только докер клиент и выставить переменную DOCKER_HOST="ssh://ansible@адрес_вашего_докер_сервера", где ansible - аккаунт, который имеет ssh доступ на сервер и под которым будут создаваться докер контейнеры. Аккаунт также должен состоять в группе docker на докер сервере.

Описание ансибл роли тоже выходит за рамки статьи, можно взять любую похожую роль, где есть логика создания кластера.

Переходим в нашу условную роль

cd roles/role_rabbitmq

Создаем конфиги для линтеров в текущей директории (дефолтные конфиги будут генерить много лишних алертов) или же создаем линки на общие конфиги.

.ansible-lint

---exclude_paths:  - .cache/  - .git/  - molecule/skip_list:  - command-instead-of-module  - git-latest  - no-handler  - package-latest  - empty-string-compare  - command-instead-of-shell  - meta-no-info  - no-changed-when  - no-relative-paths  - risky-shell-pipe  - role-name  - unnamed-task

.yamllint

---extends: defaultignore: |  templates/  sites/  files/  old/  README.md  LICENSErules:  braces:    min-spaces-inside: 0    max-spaces-inside: 1  brackets:    min-spaces-inside: 0    max-spaces-inside: 1  comments:    require-starting-space: false    level: error  indentation:    spaces: 2    indent-sequences: consistent  line-length: disable  truthy: disable

Создаем директорию molecule/cluster для нашего сценария.

mkdir molecule/cluster

Открываем в редакторе файл molecule/cluster/Dockerfile.j2. Данный конфиг будет использоваться при создании докер контейнеров. Опять же ничто не ограничивает вашу фантазию - можно использовать уже готовый имидж с ансибл на борту или создать свой.

FROM registry.company.net/debian/buster:latestENV DEBIAN_FRONTEND noninteractiveENV pip_packages "ansible"ENV http_proxy "http://10.10.0.1:8888"ENV https_proxy "http://10.0.0.1:8888"ENV no_proxy "127.0.0.1,localhost,*.company.net,10.0.0.0/8,192.168.0.0/16,172.0.0.0/8"# Install dependencies.RUN apt update \  && apt-get install -y --no-install-recommends \      sudo systemd systemd-sysv \      build-essential wget libffi-dev libssl-dev \      python3-apt python3-cryptography python3-pip python3-dev python3-setuptools python3-wheel \      procps passwd curl lsof netcat gnupg ca-certificates openssh-client less vim iputils-ping iproute2 \      debian-archive-keyring dnsutils \  && rm -rf /var/lib/apt/lists/* \  && rm -Rf /usr/share/doc && rm -Rf /usr/share/man \  && apt-get clean# Create ansible userRUN groupadd --system ansible \  && useradd --system --comment "Ansible remote management" --home-dir /home/ansible --create-home --gid ansible --shell /bin/bash --password "*" ansible && echo "%ansible ALL = (ALL) NOPASSWD:ALL" > /etc/sudoers.d/ansible# Add company repoRUN curl -k "https://certs.company.net/ca.pem" > /usr/local/share/ca-certificates/ca.crt && update-ca-certificates \  && curl -k "https://company.net/repos/keys/company_repo_key.gpg" | apt-key add \  && echo "deb https://company.net/repos/buster buster-local main > /etc/apt/sources.list.d/company.list && apt-get update && pip3 install $pip_packages# Install Ansible inventory file.RUN mkdir -p /etc/ansible && echo "[local]\nlocalhost ansible_connection=local" > /etc/ansible/hosts# Exclude /usr/share/doc# Если программа использует файлы из /usr/share/doc, то следует добавить диру в игнор для dpkg, иначе файлы будут удаленыRUN sed -i 's/path-exclude \/usr\/share\/doc/#path-exclude \/usr\/share\/doc/' /etc/dpkg/dpkg.cfg.d/docker# Make sure systemd doesn't start agettys on tty[1-6].RUN rm -f /lib/systemd/system/multi-user.target.wants/getty.targetVOLUME ["/sys/fs/cgroup"]CMD ["/lib/systemd/systemd"]

Создаем файл molecule/cluster/prepare.yml. Данный файл используется молекулой также как и в ансибле - для различных pre-tasks. В данном случаем мы обновляем дебиан пакеты и устанавливаем питон модуль pika для RabbitMQ.

---- name: prepare  hosts: all  gather_facts: no  # не используем сбор фактов для ускорения выполнения  tasks:    - name: update apt cache      block:        - name: update apt cache          apt:            update_cache: yes        - name: perform upgrade of all packages to the latest version          apt:            upgrade: dist            force_apt_get: yes    - name: install python pika      pip:        name:          - pika        executable: pip3

Создаем файл molecule/cluster/converge.yml. В данном файле мы непосредственно указываем ансибл роль для тестирования. Обратите внимание на hosts, имя должно совпадать с именем группы в molecule.yml

---- name: Converge  hosts: rabbitmq_cluster  roles:    - role: role_rabbitmq

Создаем файл molecule/cluster/molecule.yml. По сути это главный файл, где мы описываем все необходимые параметры для запуска наших тестов. В данном случае мы создаем докер сеть cluster 192.168.0.0/24 и создаем три докер контейнера в этой сети - node01, node01, node03 с заранее заданными айпи адресами 192.168.0.1/2/3. Это нужно для создания RabbitMQ кластера из трех нод, где ноды должны видеть друг друга.

Инвентори мы определяем как

inventory:  links:    group_vars: ../../../../files/molecule/group_vars/

и

groups:  - rabbitmq_cluster

Поэтому создаем в структуре ансибл файл files/molecule/group_vars/rabbitmq_cluster.yml где описываем все необходимые параметры нашей ансибл роли role_rabbitmq

---rabbitmq_cluster: yescerts_dir: /etc/rabbitmq/sslrabbitmq_ssl: yesrabbitmq_ssl_certs:  - "_.company.net"rabbitmq_cookie: NJWHJPAOPYKSGTRGDLTN# обратите внимание, здесь мы указываем короткие имена нод, заданные в нашем molecule.yml# все ноды из списка должны узнавать друг друга по этим коротким именамrabbitmq_nodes:  - node01  - node02  - node03rabbitmq_master: rabbit@node01rabbitmq_master_node: node01rabbitmq_vhosts:  - name: /testrabbitmq_users:  - user: test    password: test    vhost: /testrabbitmq_exchanges:  - name: test    type: direct    durable: yes    vhost: /testrabbitmq_queues:  - name: test    durable: yes    vhost: /testrabbitmq_bindings:  - name: test    destination: test    destination_type: queue    vhost: /testrabbitmq_policies:  - name: ha-replica    vhost: /test    tags:      ha-mode: exactly      ha-params: 2      ha-sync-mode: automatic

molecule.yml

---dependency:  name: galaxy  options:    ignore-certs: Truedriver:  name: dockerplatforms:  - name: node01    image: registry.company.net/debian/buster:latest    # pre_build_image: true    privileged: True    tmpfs:      - /run      - /tmp    volumes:      - /sys/fs/cgroup:/sys/fs/cgroup:ro      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro    capabilities:      - SYS_ADMIN    command: "/lib/systemd/systemd"    dns_servers:      - 10.0.0.1    groups:      - rabbitmq_cluster    docker_networks:      - name: cluster        ipam_config:          - subnet: "192.168.0.0/24"            gateway: "192.168.0.254"    networks:      - name: cluster        ipv4_address: "192.168.0.1"    network_mode: default  - name: node02    image: registry.company.net/debian/buster:latest    privileged: True    tmpfs:      - /run      - /tmp    volumes:      - /sys/fs/cgroup:/sys/fs/cgroup:ro      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro    capabilities:      - SYS_ADMIN    command: "/lib/systemd/systemd"    dns_servers:      - 10.0.0.1    groups:      - rabbitmq_cluster    networks:      - name: cluster        ipv4_address: "192.168.0.2"    network_mode: default  - name: node03    image: registry.company.net/debian/buster:latest    privileged: True    tmpfs:      - /run      - /tmp    volumes:      - /sys/fs/cgroup:/sys/fs/cgroup:ro      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro    capabilities:      - SYS_ADMIN    command: "/lib/systemd/systemd"    dns_servers:      - 10.0.0.1    groups:      - rabbitmq_cluster    networks:      - name: cluster        ipv4_address: "192.168.0.3"    network_mode: defaultprovisioner:  name: ansible  config_options:    defaults:        interpreter_python: auto_silent      host_key_checking: False      gathering: smart      callback_whitelist: profile_tasks, timer, yaml    ssh_connection:      pipelining: True  inventory:    links:      group_vars: ../../../../files/molecule/group_vars/  ansible_args:    - -e molecule_run=True    - -e use_proxy=False  env:    MOLECULE_NO_LOG: 0    ANSIBLE_VERBOSITY: 1verifier:  name: ansiblelint: |  set -e  ansible-lint .scenario:  name: cluster  test_sequence:    - dependency    - lint    - cleanup    - destroy    - syntax    - create    - prepare    - converge    - idempotence    - side_effect    - verify    - cleanup    - destroy

Через ansible_args можно добавлять различные переменные для ансибл роли

  ansible_args:    - -e molecule_run=True    - -e use_proxy=False

Через env можно устанавливать различные переменные environment. В данном случае мы увеличиваем дебаг для ансибла (аналогично опции -v) через ANSIBLE_VERBOSITY.

MOLECULE_NO_LOG незадокументированная опция молекулы, позволяет ставить no_log=no для удобства отладки (по дефолту no_log в молекуле всегда yes). При этом в роли можно использовать такую конструкцию no_log: "{{ molecule_no_log|d(False)|ternary(False, True) }}". Если molecule_no_log=0, то выставить no_log: no, иначе no_log: yes. Так как используются тестовые аккаунты и пароли, то запись этих данных в лог некритична.

  env:    MOLECULE_NO_LOG: 0    ANSIBLE_VERBOSITY: 1

В последних версиях ansible-lint сам вызывает yamllint, поэтому можно указать линтер только ansible-lint

lint: |  set -e  ansible-lint .

В scenario мы определяем наш сценарий cluster и все шаги, необходимые для тестирования. Просмотреть все шаги можно через команду molecule matrix test.

Обратите внимание на сценарии side_effect и verify, если их непосредственно не указать, то у меня они почему-то не вызывались, хотя показаны в выводе molecule matrix.

scenario:  name: cluster  test_sequence:    - dependency    - lint    - cleanup    - destroy    - syntax    - create    - prepare    - converge    - idempotence    - side_effect    - verify    - cleanup    - destroy

Попробуем запустить сценарий cluster, если не указать -s то молекула запустит сценарий default

molecule test -s cluster > /tmp/log 2>&1

Молекула начинает прогонять указанные в test_sequence шаги, причем делает это дважды для соблюдения idempotence. Если во втором тесте будут отличия от первого теста, то молекула завершит работу с ошибкой. Не всегда это работает как нужно (например если конфиг меняется динамически самим сервисом, как в случает с редис), хотя это всегда можно обойти директивой ансибла changed_when: no

В конце в логе /tmp/log должен появиться отчет с финальным сообщением "Idempotence completed successfully", то есть ошибок не найдено и роль можно смело использовать в продакшн ;). При возникновении ошибки на любом шаге, молекула прекращает работу и останавливает свои докер-контейнеры.

Если нужно посмотреть что же вызывает ошибку или проверить состояние конфига, то можно вызывать молекулу с опцией converge, molecule converge -s cluster. В этом случае молекула прогоняет все таски, указанные в converge.yml и не запускает destroy. Можно зайти в контейнер через "docker exec -it container_id /bin/bash" и просмотреть логи или проверить конфиги.

Самое интересное у молекулы на мой взгляд это side-effect и verify. Через side-effect таски можно задавать различные деструктивные действия (что-то вроде chaos monkey). А через verify таски можно проверять финальное состояние системы.

Например скажем молекуле сделать рестарт сервису rabbitmq на каждой ноде после создания кластера (но ничто не ограничивает вас в фантазии).

Создаем файл molecule/cluster/side_effect.yml

---- name: Side Effect  serial: 1  hosts: all  gather_facts: no  # факты нам не нужны  tasks:    - name: restart rabbitmq service      block:        - name: stop rabbitmq service          systemd:            name: rabbitmq-server            state: stopped          failed_when: no        - name: pause          pause:            seconds: 15        - name: start rabbitmq service          systemd:            name: rabbitmq-server            state: started          failed_when: no

Создаем файл molecule/cluster/verify.yml и добавим различные базовые проверки для нашего кластера (опять же ничто не ограничивает вашу фантазию).

---- name: Verify  hosts: all  gather_facts: no  tasks:  - name: cluster status    block:      - name: get cluster status        command: "rabbitmqctl cluster_status --formatter json"        register: output      - name: set facts        set_fact:          cluster_output: "{{ output.stdout|from_json }}"      - name: print nodes        debug:          var: cluster_output.disk_nodes      - name: verify fail        fail:          msg: "FAIL: number of nodes is less than 3"        when:          - cluster_output.disk_nodes | length < 3    run_once: yes  - name: check vhosts    block:      - name: get vhosts        command: "rabbitmqctl list_vhosts --formatter json"        register: output      - name: set facts        set_fact:          vhost_output: "{{ output.stdout|from_json }}.name"      - name: print vhosts        debug:          var: vhost_output      - name: verify fail        fail:          msg: "FAIL: vhost is missing"        when:          - "'/test' not in vhost_output"    run_once: yes  - name: check users    block:      - name: get users        command: "rabbitmqctl list_users --formatter json"        register: output      - name: set facts        set_fact:          user_output: "{{ output.stdout|from_json }}.user"      - name: print users        debug:          var: user_output      - name: verify fail        fail:          msg: "FAIL: user is missing"        when:          - "'test' not in user_output"    run_once: yes  - name: check queues    block:      - name: get queues        command: "rabbitmqctl -p /test list_queues --formatter json"        register: output      - name: set facts        set_fact:          queue_output: "{{ output.stdout|from_json }}.name"      - name: print queues        debug:          var: queue_output      - name: verify fail        fail:          msg: "FAIL: queue is missing"        when:          - "'test' not in queue_output"    run_once: yes  - name: check exchanges    block:      - name: get exchanges        command: "rabbitmqctl -p /test list_exchanges --formatter json"        register: output      - name: set facts        set_fact:          exchange_output: "{{ output.stdout|from_json }}.name"      - name: print exchanges        debug:          var: exchange_output      - name: verify fail        fail:          msg: "FAIL: exchange is missing"        when:          - "'test' not in exchange_output"    run_once: yes  - name: check bindings    block:      - name: get bindings        command: "rabbitmqctl -p /test list_bindings --formatter json"        register: output      - name: set facts        set_fact:          binding_output: "{{ output.stdout|from_json }}.source_name"      - name: print bindings        debug:          var: binding_output      - name: verify fail        fail:          msg: "FAIL: binding is missing"        when:          - "'test' not in binding_output"    run_once: yes  - name: check policies    block:      - name: get policies        command: "rabbitmqctl -p /test list_policies --formatter json"        register: output      - name: set facts        set_fact:          policy_output: "{{ output.stdout|from_json }}.name"      - name: print policies        debug:          var: policy_output      - name: verify fail        fail:          msg: "FAIL: policy is missing"        when:          - "'ha-replica' not in policy_output"    run_once: yes  - name: check publish    block:      - name: install consumer script        copy:          src: ../../../../files/molecule/scripts/consumer.py          dest: /usr/local/bin/consumer.py          owner: root          mode: 0755      - name: publish a message to a queue        rabbitmq_publish:          url: "amqp://test:test@localhost:5672/%2Ftest"          queue: test          body: "Test message"          content_type: "text/plain"          durable: yes      - name: receive a message from the queue        command: /usr/local/bin/consumer.py    run_once: yes

Так как ansible lookup не очень хорошо работает в докер-контейнере, создаем files/molecule/scripts/consumer.py, небольшой скрипт на питоне, который печатает сообщения из очереди test.

#!/usr/bin/python3import pika, sysurl = 'amqp://test:test@localhost/%2ftest'params = pika.URLParameters(url)params.socket_timeout = 1connection = pika.BlockingConnection(params)channel = connection.channel()channel.queue_declare(queue='test', durable=True)method_frame, header_frame, body = channel.basic_get(queue = 'test')if method_frame is None:    connection.close()    sys.exit('Queue is empty!')else:    channel.basic_ack(delivery_tag=method_frame.delivery_tag)    connection.close()    print(body)

Проверяем side-effect

molecule converge -s clustermolecule side-effect -s cluster

Проверяем verify

molecule verify -s cluster

Если все хорошо, запускаем полный тест и проверяем лог.

molecule test -s cluster >/tmp/log 2>&1tail -f /tmp/log
Подробнее..
Категории: Devops , Ansible , Molecule

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru