Подход «инфраструктура как код» упрощает создание и управление инфраструктурой, но это всё ещё код, и относится к нему надо как к любому коду. А значит, нам нужно внедрять практики SDLC. О реализации одной из них и будет эта статья. А точнее, про тестирование инфраструктурного кода. 

Мы пользуемся Docker-провайдером, но это накладывает некоторые ограничения на запуск. Например, там нельзя редактировать файлы hostname, нельзя поставить нормальный файрвол и т. д. Эти ограничения иногда довольно болезненны, когда то же самое отправляешь в эксплуатационную среду, которая хоть и чуть-чуть, но отличается. Мы в проекте используем Ansible, и перед настройкой production-окружение хотим протестировать наши playbook. И для этого используем Molecule совместно с TestInfra. Как? Сейчас расскажем.

Что такое Molecule?

Molecule — это инструмент тестирования Ansible-ролей. Что-то вроде модульных тестов для инфраструктуры. В официальной документации сказано:

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

Преимущества использования Ansible Molecule:

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

  2. Автоматизация тестирования. Molecule позволяет автоматизировать создание, настройку и тестирование ролей Ansible, что упрощает и ускоряет разработку.

  3. Поддержка различных платформ, таких как Docker, Vagrant, OpenStack и других, что позволяет тестировать роли Ansible в различных окружениях.

  4. Интеграция с CI/CD. Molecule может быть встроен в системы непрерывной интеграции и доставки (CI/CD), такие как Jenkins, GitLab CI, Travis CI и другие, что позволяет автоматизировать тестирование и развёртывание ролей Ansible.

  5. Поддержка различных тестовых фреймворков. Molecule поддерживает Testinfra, Serverspec и другие фреймворки, что позволяет тестировать роли Ansible на различных уровнях (интеграционные, функциональные, модульные тесты и т. д.).

Давайте подробнее рассмотрим тестовую матрицу Molecule. По умолчанию она выглядит так: dependency, lint, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy.

Рассмотрим каждый шаг подробно:

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

  2. lint: проверка ролей не соответствие правилам различных линтеров, например yamllint, ansible-lint, flake8.

  3. destroy: удаление тестового окружения.

  4. syntax: проверка синтаксиса ваших playbook.

  5. create: создание экземпляров для дальнейшей работы. Это могут быть Docker-контейнеры или виртуальные машины, в зависимости от используемого вами драйвера.

  6. prepare: подготовка ваших экземпляров, например, добавление дополнительных пользователей, или установка пакетов.

  7. converge: выполнение playbook, которые вы хотите протестировать.

  8. idempotence: проверка на идемпотентность, заключается в выполнении фазы converge ещё раз. И проверка, что все задачи вернули статус “OK”, а не “changed”.

  9. side_effect: выполнение дополнительных playbook для ваших ролей. Как вариант, перед выполнением каких-либо тестов добавьте данные в тестируемые системы.

  10. verify: выполнение тестов, проверка, что ваши playbook не просто выполнились, а дают ожидаемые результаты. Можно использовать модуль Ansible-assert или Framework-testinfra.

Подробнее о возможностях Molecule можно узнать из этого видео.

Использование Molecule

Одно из главных преимуществ Molecule — поддержка различных платформ. Подробнее об этом мы поговорим ниже. 

Molecule создаёт и управляет экземпляры, на которые «накатывает» playbook и прогоняет тесты. Проще всего разворачивать экземпляры с помощью Docker-драйвера, он позволяет создавать в для тестов Docker-контейнеры. В большинстве случаев этого вполне достаточно, но есть одно «но»: в production-окружении ваши роли устанавливаются на физические серверы или виртуальные машины, и образы ОС могут отличаться от ОС в образе Docker. 

Решение есть! На помощь приходит delegated driver и, конечно же, VK Cloud :) Весь исходный код, используемый в статье, можно найти тут.

Для начала установим необходимые для работы Molecule пакеты, в том числе OpenSDK, с которым мы будем работать:

# Установка ansible

python3 -m pip install --user ansible

# Установка последней версии molecule

python3 -m pip install -U git+https://github.com/ansible-community/molecule

# Установка testinfra

python3 -m pip install --user pytest-testinfra

# Установка openstacksdk.

pip3 install openstacksdk 

И снова обратимся к документации:

Delegated не является драйвером по умолчанию, используемым в Molecule. В соответствии с этим драйвером разработчики несут ответственность за реализацию сreate и destroy playbook. Однако разработчик должен придерживаться API-интерфейса конфигурации экземпляра.

Тут всплывает новое понятие в Molecule — «конфигурация экземпляра» (instance-config). Давайте разберёмся что это такое. На самом деле это обычный YAML-файл, который находится в директории $HOME/.cache/molecule/<role-name>/<scenario-name>/instance_config.yml и имеет такую структуру:

address: IP-адрес;

identity_file: файл с приватным ключом;

instance: имя экземпляра;

port: порт для подключения по SSH;

user: пользователь для подключения по SSH;

password: gароль для подключения по SSH.

Чтобы заполнить эту структуру, нужно сначала сгенерировать сам файл. Для этого воспользуемся командой для инициализации сценария с delegated driver:

molecule init scenario -driver-name=delegated

Молекула создаст для подготовленные шаблоны основных файлов: 

  • converge.yml

  • create.yml

  • destroy.yml

  • molecule.yml

  • prepare.yml 

Начнём с файла create.yml, который отвечает за создание экземпляров для Molecule. Как мы видим, за нас уже сделана часть работы по генерированию файла instance_config, достаточно немного его отредактировать и добавить специфичные для VK Cloud опции:

- name: Populate instance config dict
      ansible.builtin.set_fact:
        instance_conf_dict: {
          'address': "{{ item.server.addresses['ext-net'][0].addr }}",
          'instance': "{{ item.server.metadata.hostname }}",
          'name': "{{ item.server.name }}",
          'user': "{{ molecule_yml.driver.options.ansible_connection_options.ansible_ssh_user }}",
          'port': "22",
          'identity_file': "{{ ssh_key_file }}",
        }
      with_items: "{{ instance_created.instances }}"
      register: instance_config_dict
      when: instance_created.changed

Внимательный читатель заметит использование переменной molecule_yml — тут находятся переменные, определённые в файле molecule.yml. Теперь дело за малым: написать задачу для создания виртуальной машины. Для этого воспользуемся OpenStack Ansible-модулем:

- name: Create a new instance
  openstack.cloud.server:
    state: present
    auth:
      auth_url: "{{ auth_url }}"
      username: "{{ username }}"
      password: "{{ password }}"
      project_id: "{{ project_id }}"
      user_domain_name: "{{ user_domain_name }}"
    availability_zone: "{{ item.availability_zone }}"
    name: "{{ item.name }}-{{ 50 | random | to_uuid }}"
    image: "{{ item.image }}"
    key_name: "{{ key_name }}"
    timeout: 200
    config_drive: true
    region_name: RegionOne
    flavor: "{{ item.flavor }}"
    security_groups: "{{ item.security_group }}"
    nics:
      - net-id: "{{ net_id }}"
    boot_from_volume: true
    volume_size: "{{ item.volume_size }}"
    terminate_volume: true
    meta:
      hostname: "{{ item.hostname }}"
  register: instance

В файле снова появляются неизвестные нам переменные, например, переменная security_group отвечает за группу, которая будет использоваться в самом Ansible. Но если посмотреть в задачу, которая вызывает создание виртуальной машины, то всё становится на свои места:

- name: Create instances
      ansible.builtin.include_tasks: tasks/create_instance.yml
      loop: "{{ molecule_yml.platforms }}"

 Опишем наши виртуальные машины в molecule.yml:

platforms:
  - name: web01
    hostname: web01
    image: "CentOS-7.9-202107"
    flavor: Basic-1-1-10
    volume_size: 10
    security_group: default,ssh
    availability_zone: GZ1
    groups:
      - webservers

Так как создание виртуальных машин — это не мгновенный процесс, какое-то время необходимо подождать, поэтому мы добавляем ожидание её доступности по SSH, и только после подтверждения её создания продолжает дальнейшие операции:

- name: Wait for sshd to come up on {{ item.name }}
  ansible.legacy.wait_for:
    host: "{{ instance_created.instances[0].server.addresses['ext-net'][0].addr }}"
    port: 22
    timeout: 90

Не стоит забывать, что после того, как мы использовали виртуальную машину, её нужно удалить — убрать за собой. Для этого давайте заглянем в playbook destroy, он отвечает за удаление виртуальных машин. Нам нужно получить список созданных виртуалок, он есть в instance_config:

- name: Set VM for destroy
      ansible.builtin.set_fact:
        instance_content: "{{ lookup('file', '{{ molecule_instance_config }}') }}"

 А далее создаём задачу по циклу, которая удалит наши виртуальные машины:

- name: Destroy an instance
  openstack.cloud.server:
    name: "{{ item.name }}"
    auth:
      auth_url: "{{ auth_url }}"
      username: "{{ username }}"
      password: "{{ password }}"
      project_id: "{{ project_id }}"
      user_domain_name: "{{ user_domain_name }}"
    state: absent

Осталось совсем немного. Регистрируемся на платформе VK Cloud, если у вас ещё нет учетной записи. При регистрации вы получите на баланс 3000 рублей для тестов. Для активации API необходимо добавить двухфакторную аутентификацию. Далее заполняем специфичные для проекта переменные:

auth_url  - адрес API openstack

username — ваш email;

password — ваш пароль;

project_id;

user_domain_name;

net_id — идентификатор вашей сети;

key_name — ключ SSH, предварительно добавленный в «ключевые пары»;

ssh_key_file — ~/.ssh/id_rsa.
Настройки проекта.
Настройки проекта.
Добавление ключевой пары.
Добавление ключевой пары.
Виртуальные сети.
Виртуальные сети.

В примере из репозитория есть роль, которая установит веб-сервер и MySQL-сервер, можете потом попробовать.

Наш основной playbook:

---
- name: Apply for common configuration to all the nodes
  hosts: all
  become: yes
  remote_user: centos
  roles:
   - common
 

- name: Deploy MySQL and configure databases
  hosts: dbservers
  become: yes
  remote_user: centos
  roles:
   - db
 

- name: Deploy Apache, PHP and configure website code
  hosts: webservers
  become: yes
  remote_user: centos
  roles:
   - web

Добавляем в converge playbook запуск нашего основного playbook:

- name: Run Full playbook
  ansible.builtin.import_playbook: ../../site.yml 

И не забудем про тесты, ради чего мы всё это делаем. Запускаем Molecule и ждём результат:

def test_service_httpd(host):
    s = host.service("mysqld")
    assert s.is_enabled
    assert s.is_running
 

def test_service_firewalld(host):
    s = host.service("firewalld")
    assert s.is_enabled
    assert s.is_running
 

def test_httpd_port(host):
    host.socket("tcp://:::3306").is_listening

Итоги

Благодаря интеграции VK Cloud и Molecule мы в своём проекте уменьшили длительность тестирования ролей. Тестирование с Docker Driver заняло у нас 1 ч 50 минут. При этом было много тестов, которые просто падали: где-то Docker отвалился, где-то ещё какая-то ерунда случилась. То есть были инфраструктурные причины, на которые очень трудно повлиять. А вот с delegated driver мы получили чистые 40 минут времени, но стабильно, и всегда всё проходило хорошо. 

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

Комментарии (0)