Привет, Хабр! Статья будет посвящена любимому мной IaC. Чтобы ввести в курс дела, кратко расскажу про VMmanager и текущую реализацию продукта. Затронем варианты, как можно работать с VMmanager с подходом Infrastructure as Code, а основная часть — про развертывание платформы VMmanager и управление виртуальными машинами в ней с помощью Ansible.
О платформе VMmanager
Наверняка вы уже знаете, что такое VMmanager. Это платформа управления виртуализацией. Она имеет версии VMmanager Hosting и VMmanager Infrastructure — под разные цели использования.
Версия |
Hosting |
Infrastructure |
Назначение |
— ориентирована на потребности хостинг-провайдеров |
— ориентирована на потребности владельцев IT-инфраструктур |
Состав |
— базовая функциональность платформы |
— базовая функциональность платформы |
Дополнительно |
— |
— регистрация в Едином реестре российских программ для электронных вычислительных машин и баз данных |
VMmanager — один из продуктов ISPsystem, он имеет интеграцию с другими продуктами, на схеме кратко и просто представлена их взаимосвязь.

Я буду работать с VMmanager Infrastructure.
IaC-решения
Для реализации подхода Infrastructure as Code в работе с VMmanager у нас есть сценарий развертывания платформы с помощью Ansible, примеры управления инфраструктурой через провайдер Terraform и работа с платформой с использованием VMmanager API. Все это представлено в документации.
По работе с Terraform уже была подробная статья. Поэтому сосредоточимся на Ansible. С помощью готового сценария мы развернем платформу, посмотрим, что из себя представляет сценарий (спойлер: набор плейбуков с задачами, использующими модуль ansible.builtin.uri). А для управления виртуальными машинами на платформе я погружусь в VMmanager API и напишу собственный модуль на Python.
Управление инфраструктурой через VMmanager API и Ansible
Развертывание платформы с помощью Ansible
Начнем с подготовки серверов для платформы VMmanager, понадобится Astra Linux версии 1.8.1 уровня защиты «Орел». У меня есть такой образ, его я и буду разворачивать в Yandex Cloud. Узел кластера для тестирования можно не разворачивать: в VMmanager для таких целей можно настроить кластер-заглушку, это нужно сделать уже после создания кластера.
Настройку серверов для платформы и узла кластера я не буду приводить в статье по двум причинам: во-первых, в документации очень подробно и точно описаны шаги — можно смело по ним идти, все получится; во-вторых, тема статьи о другом и будет неуместно сюда из документации репостить объемный материал. Но приведу несколько замечаний, которые при аналогичном развертывании на виртуальной машине Yandex Cloud могут быть полезны.
Примечание к настройке сервера
Из всех пунктов мне понадобилось только отредактировать /etc/apt/sources.list. Остальное я оставил по умолчанию.
Раздел «Настройка подключения к узлам кластера» можно пропустить, если настраиваем кластер-заглушку.
Переходим к развертыванию VMmanager. В документации есть step-by-step по установке VMmanager с помощью Ansible. Но, поскольку шагов не так много и это уже про IaC, я перепишу в статью все шаги, немного добавив пояснений.
Для начала стоит сказать, что для активации платформы понадобится лицензия. У меня есть триал, получил за красивые глаза, но это не обязательный навык для работы с VMmanager.
Все продукты ISPsystem доступны для бесплатного тестирования. По запросу на сайте получите бесплатный триал или доступ к демостенду. Также можно заказать демонстрацию интересующих платформ на сайте: DCImanager, VMmanager, BILLmanager, DNSmanager.
Нас все еще интересует триал для VMmanager.
Переходим к установке платформы.
Установим Ansible на ПК, с которого будет запускаться установка платформы. Порядок установки в официальной документации Ansible.
-
На ПК с Ansible:
Если на ПК не установлена утилита curl, установите ее:
dnf install curl || apt install curl
Скачайте сценарии установки:
curl
https://download.ispsystem.com/extras/ansible/vmmanager6_common.tar.gz
Создайте SSH-ключ и скопируйте его на сервер платформы. Подробнее — в статье про SSH-протокол.
-
Создадим директорию для сценариев установки:
mkdir vm6_ansible
-
Распакуем в директорию архив со сценариями:
tar xzf vmmanager6_common.tar.gz -C vm6_ansible/
-
Перейдем в созданную директорию:
cd vm6_ansible
Укажем параметры установки в секции vars файла vmmanager6.yml:
vars:
vmi_first_username: "admin@example.com"
vmi_first_password: "q1w2e3r4"
vmmanager6_license_token: "..........:..................."
vmi_domain: "{{ ansible_ssh_host }}"
# Поскольку я разворачивал виртуальную машину в Yandex Cloud,
# параметры сети берем по факту, в которой разворачиваем.
vmi_network: "10.177.91.0/24"
vmi_network_gateway: "10.177.91.1"
vmi_network_note: "some network notes"
vmi_pool_name: "some_pool"
vmi_pool_note: "testing pool"
vmi_cluster_name: "new_cluster"
vmi_time_zone: "UTC"
vmi_cluster_note: "some cluster note"
vmi_domain_template: ".example.com"
# Я указал для check ip из настроек dns, но он нам не понадобится в рамках теста.
vmi_node_check_ip: "10.177.181.142"
# Мы далее закомментируем все, что касается ssl и бэкапа, поэтому следующие
# настройки можно не трогать.
vmi_certificate: "-----BEGIN CERTIFICATE-----\nMIIDkT……7s=\n-----END CERTIFICATE-----\n"
vmi_certificate_key: "-----BEGIN PRIVATE KEY-----\nMI….BlXDeTd\n-----END PRIVATE KEY-----\n"
vmi_certificate_ca: ""
vmi_backup_ip: "10.177.91.71"
vmi_backup_user: "root"
vmi_backup_password: "q1w2e3p4"
vmi_backup_path: "/backup"
-
Мы не планируем подключать SSL-сертификат, закомментируем в файле vmmanager6.yml строку:
# - include_tasks: cert.yml
-
Резервное копирование платформы мы также не планируем, закомментируем в файле vmmanager6.yml строку:
# - include_tasks: backup.yml
-
Запустим установку:
ansible-playbook -i <IP>, -u root vmmanager6.yml
Вместо <IP> укажите ip своей виртуальной машины.
Посмотрим немного глубже в Ansible-сценарий установки VMmanager. В распакованном архиве мы видим набор плейбуков.

Основной плейбук vmmanager6.yml, в который подключаются по порядку остальные плейбуки для установки.
---
- name: Install VMmanager 6 playbook
hosts: all
debugger: on_failed
vars:
vmi_first_username: "admin@example.com"
vmi_first_password: "q1w2e3r4"
vmmanager6_license_token: ".............:................"
vmi_domain: "{{ ansible_ssh_host }}"
vmi_network: "10.177.91.0/24"
vmi_network_gateway: "10.177.91.1"
vmi_network_note: "some network notes"
vmi_pool_name: "some_pool"
vmi_pool_note: "testing pool"
vmi_cluster_name: "new_cluster"
vmi_time_zone: "UTC"
vmi_cluster_note: "some cluster note"
vmi_domain_template: ".example.com"
vmi_node_check_ip: "10.177.181.142"
vmi_certificate: "-----BEGIN CERTIFICATE-----\nMIIDkT……7s=\n-----END CERTIFICATE-----\n"
vmi_certificate_key: "-----BEGIN PRIVATE KEY-----\nMI….BlXDeTd\n-----END PRIVATE KEY-----\n" vmi_certificate_ca: ""
vmi_backup_ip: "10.177.91.71"
vmi_backup_user: "root"
vmi_backup_password: "q1w2e3p4"
vmi_backup_path: "/backup"
tasks:
- include_tasks: remove_and_install.yml
- name: First time token
uri:
url: "https://{{ vmi_domain }}/auth/v4/public/token"
method: POST
body_format: json
status_code: [200,201,503]
return_content: yes
body:
email: "{{ vmi_first_username }}"
password: "{{ vmi_first_password }}"
validate_certs: no
register: first_token
retries: 3
delay: 30
until: first_token.status == 201
- name: make ses6 a fact
set_fact:
ses6: "{{ (first_token.content|from_json).token }}"
- include_tasks: first_run.yml
# - include_tasks: cert.yml
- include_tasks: setup_ip.yml
- include_tasks: cluster.yml
- include_tasks: storage.yml
# - include_tasks: backup.yml
Посмотрим на один из плейбуков cluster.yml, чтобы разобраться, что происходит при установке. Мы видим, что сценарий построен на использовании модуля ansible.builtin.uri.
---
- name: Set up cluster
uri:
# В документации API можем посмотреть url для создания кластера, и другие url,
# а также параметры, которые мы можем передать.
url: "https://{{ vmi_domain }}/vm/v3/cluster"
method: POST
body_format: json
status_code: [200, 401, 409, 500, 503]
headers:
Cookie: "ses6={{ ses6 }}"
x-xsrf-token: "{{ ses6 }}"
body:
name: "{{ vmi_cluster_name }}"
virtualization_type: "kvm"
time_zone: "{{ vmi_time_zone }}"
dns_servers:
- "1.1.1.1"
comment: "{{ vmi_cluster_note }}"
os:
- 1
iso_enabled: false
manage_disk_enabled: false
domain_template: "{{ vmi_domain_template }}"
domain_change_allowed: false
overselling: 1
host_per_node_limit: -1
host_distribution_policy: "spread"
host_filter: []
backup_locations: []
image_storage_path: "/image"
os_storage_path: "/share"
datacenter_type: "common"
interfaces:
- interface: 0
ippool: [ 1 ]
node_network:
gateway: "{{ vmi_node_check_ip }}"
timeout: 300
vxlan_mode: "disabled"
validate_certs: no
register: cluster
retries: 3
delay: 10
until: cluster.status == 200
С помощью переменных мы ранее задали все нужные значения, которые подтягиваются при выполнении задач. ansible.builtin.uri отправляет запросы для скачивания установочных файлов VMmanager API и инициализации установки. А далее происходит магия… Обращение к VMmanager API для создания кластера и его настройки.
Посмотрев на все задачи из сценария, можно увидеть, что работа с VMmanager API несложная, документации соответствует, такой сценарий вполне можно составить самостоятельно.
Далее нужно создать кластер в VMmanager — в документации пошагово представлено, как это сделать через веб-интерфейс, а затем настроить заглушку для кластера. Эти шаги я тоже оставляю за пределами статьи — по тем же причинам, что и настройка серверов.
По заглушке кластера есть небольшое примечание
Команда должна быть:
update vm_cluster set virtualization_type = 'dummy' where id = <cluster_id>;
Где ‘dummy’ в одинарных кавычках.
Управление ВМ с помощью модуля ansible.builtin.uri
У меня готова платформа VMmanager, кластер настроен и подключен, самое время попробовать в IaC — будем создавать виртуальные машины. Научимся с простого варианта — с использованием модуля ansible.builtin.uri. Поскольку VMmanager API позволяет довольно гибко работать с платформой, будет полезно отработать быстрый вариант работы с API, если захочется использовать разные возможности управления.
Отмечу, что данный способ имеет определенный нюанс: мы не получаем контроль идемпотентности нашего Infrastructure as Code. VMmanager API обеспечивает идемпотентность только по id, что может быть удобно, когда мы не хотим иметь ограничение в именах виртуальных машин, но модуль ansible.builtin.uri это не обеспечит. В POST-запросе нельзя указать id, то есть создать виртуальную машину с конкретным id.
С точки зрения подхода идеально, когда за идемпотентность отвечает выбранный нами инструмент, а это Ansible. В нашем случае можно выбрать между скоростью разработки инфраструктурного кода и каноничностью принципов.
Для управления виртуальными машинами нам будет достаточно переиспользовать файл с переменными и создать сценарий. Сеть и пул IP-адресов уже созданы при развертывании платформы, учетную запись рекомендую создать отличную от админской, пользователя создадим из веб-интерфейса. А далее обратимся к документации API.
Заполним основной файл сценария — с переменными, авторизацией и вызовом сценария создания виртуальной машины.
---
- name: Install VMmanager 6 playbook
hosts: all
debugger: on_failed
vars:
vmi_first_username: "vm@example.com"
vmi_first_password: "uA2sW2nT9czV"
vmi_domain: "{{ ansible_ssh_host }}"
tasks:
- name: First time token
uri:
url: "https://{{ vmi_domain }}/auth/v4/public/token"
method: POST
body_format: json
status_code: [200,201,503]
return_content: yes
body:
email: "{{ vmi_first_username }}"
password: "{{ vmi_first_password }}"
validate_certs: no
register: first_token
retries: 3
delay: 30
until: first_token.status == 201
- name: make ses6 a fact
set_fact:
ses6: "{{ (first_token.content|from_json).token }}"
- include_tasks: vm.yml
И напишем сам сценарий работы с виртуальными машинами.
–
# Для начала посмотрим, какие же виртуальные машины уже развернуты,
# указанный url выдаст все, без фильтрации.
- name: Get up vm
uri:
url: "https://{{ vmi_domain }}/vm/v3/host"
method: GET
body_format: json
status_code: [200, 401, 409, 500, 503]
headers:
Cookie: "ses6={{ ses6 }}"
x-xsrf-token: "{{ ses6 }}"
validate_certs: no
# Для тестирования мы передаем минимальный набор параметров.
- name: Set up vm
uri:
url: "https://{{ vmi_domain }}/vm/v3/host"
method: POST
body_format: json
status_code: [200, 401, 409, 500, 503]
headers:
Cookie: "ses6={{ ses6 }}"
x-xsrf-token: "{{ ses6 }}"
body:
name: My_vm
password: vmsecret
# Для cluster, ipv4_pool, os передаем id, в веб-интерфейсе все они указаны,
# можно посмотреть.
cluster: 1
os: 1
ram_mib: 512
hdd_mib: 5000
cpu_number: 1
ipv4_pool: [1]
validate_certs: no
Управление ВМ с помощью своего модуля Ansible
Настало время перейти к технически интересному варианту, полностью соответствующему подходу Infrastructure as Code. Я напишу собственный модуль на Python для создания виртуальных машин в VMmanager. Грамотно написанный модуль обеспечит на стороне Ansible идемпотентность нашего инфраструктурного кода.
Если быть точными, то мы напишем два модуля vm_info, vm и два вспомогательных модуля auth_utils и info_utils. Это будет полностью соответствовать рекомендациям Ansible, а именно выносить сбор информации, не вносящий изменения в инфраструктуру, в модуль info, общий код модулей — во вспомогательные модули.
Начнем с модуля auth_utils. Он нужен для авторизации, при вызове других модулей мы получаем токен и возвращаем его в вызвавший авторизацию модуль.
# -*- coding: utf-8 -*-
import json
import time
import urllib3
from ansible.module_utils.urls import open_url, SSLValidationError
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def get_auth_token(base_url, email, password, retries=3, delay=30, validate_certs_param=None, module=None):
"""
Получение токена авторизации через API VMI.
:param base_url: Базовый URL (например, https://vmi.example.com)
:param email: Логин пользователя
:param password: Пароль
:param retries: Количество попыток
:param delay: Задержка между попытками
:param validate_certs_param: строка 'no' или None (если не передан параметр)
:param module: ссылка на AnsibleModule для вывода ошибок
:return: токен сессии
"""
url = f"{base_url}/auth/v4/public/token"
headers = {
'Content-Type': 'application/json'
}
body = json.dumps({
"email": email,
"password": password
})
last_exception = None
for attempt in range(1, retries + 1):
try:
kwargs = {
"url": url,
"method": "POST",
"headers": headers,
"data": body,
}
if validate_certs_param == "no":
kwargs["validate_certs"] = False
response = open_url(**kwargs)
if response.getcode() in (200, 201):
result = json.loads(response.read())
return result.get("token")
except (SSLValidationError) as e:
last_exception = e
if module:
module.warn(f"Попытка {attempt} не удалась: {e}")
time.sleep(delay)
if module:
module.fail_json(msg="Не удалось получить токен после нескольких попыток", exception=str(last_exception))
else:
raise Exception(f"Ошибка получения токена: {last_exception}")
Далее vm_info. Мы могли бы ограничиться только вспомогательным модулем, но будет более показательно реализовать модуль info полностью, тем более что он будет содержать минимальное количество базового кода.
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.auth_utils import get_auth_token
from ansible.module_utils.info_utils import get_vmi_info
DOCUMENTATION = r'''
---
module: vmi_info
short_description: Получение информации о хосте по имени из VMI API
description:
- Получает токен через auth_utils.get_auth_token, затем делает GET запрос через info_utils.get_vmi_info
options:
vmi_domain:
description: Домен VMI без https://
required: true
type: str
vmi_first_username:
description: Email пользователя
required: true
type: str
vmi_first_password:
description: Пароль пользователя
required: true
type: str
name:
description: Имя хоста для фильтрации
required: true
type: str
validate_certs:
description:
- Отключает проверку SSL-сертификатов ('no' для отключения).
required: false
type: str
'''
EXAMPLES = r'''
- name: Получить информацию о хосте
vm_info:
vmi_domain: vmi.example.com
vmi_first_username: admin@example.com
vmi_first_password: secret
name: my-host
validate_certs: no
register: result
'''
def main():
module = AnsibleModule(
argument_spec=dict(
vmi_domain=dict(required=True, type='str'),
vmi_first_username=dict(required=True, type='str'),
vmi_first_password=dict(required=True, type='str'),
name=dict(required=True, type='str'),
validate_certs=dict(required=False, type='str'),
)
)
base_url = f"https://{module.params['vmi_domain']}"
email = module.params['vmi_first_username']
password = module.params['vmi_first_password']
name = module.params['name']
validate_certs = module.params.get('validate_certs')
# Получаем токен
token = get_auth_token(
base_url=base_url,
email=email,
password=password,
validate_certs_param=validate_certs,
module=module
)
# Формируем endpoint
endpoint = f"/vm/v3/host?where=(name+EQ+'{name}')"
# Получаем информацию
data = get_vmi_info(base_url, token, endpoint, validate_certs, module)
module.exit_json(changed=False, finder=data.get('size', []))
if __name__ == '__main__':
main()
Теперь перейдем к вспомогательному модулю info_utils. Он нужен для реализации идемпотентности на стороне Ansible-модуля создания виртуальных машин. Через этот модуль мы будем получать информацию о текущей инфраструктуре.
# -*- coding: utf-8 -*-
from ansible.module_utils.urls import open_url
import json
def get_vmi_info(base_url, token, endpoint, validate_certs, module):
"""
Делает GET-запрос к API по endpoint с использованием токена.
:param base_url: базовый URL с https
:param token: токен авторизации ses6
:param endpoint: путь API начиная с /, например /vm/v3/host?where=(name+...
:param validate_certs: 'no' или None
:param module: объект AnsibleModule для fail_json
:return: распарсенный JSON из ответа
"""
url = f"{base_url}{endpoint}"
headers = {
'Accept': 'application/json',
'Cookie': f'ses6={token}',
'x-xsrf-token': token,
}
kwargs = {
'url': url,
'method': 'GET',
'headers': headers,
}
if validate_certs == 'no':
kwargs['validate_certs'] = False
try:
response = open_url(**kwargs)
return json.loads(response.read())
except Exception as e:
module.fail_json(msg=f"Ошибка при GET-запросе к {url}", error=str(e))
Последним модулем будет vm. Запрашивая информацию о текущей инфраструктуре, мы будем сравнивать ее параметры со входными параметрами при вызове модуля и в случае совпадения текущего состояния и желаемого — не производить изменения, отдавая changed False.
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.auth_utils import get_auth_token
from ansible.module_utils.info_utils import get_vmi_info
from ansible.module_utils.urls import open_url
import ssl
import http.client
import json
from urllib.parse import urlparse
DOCUMENTATION = r'''
---
module: vm
short_description: Управление виртуальной машиной в VM
description:
- Получает токен через get_auth_token.
- Проверяет наличие VM по имени через get_vmi_info.
- Создает VM если state=present и VM не существует.
options:
vmi_domain:
description: Домен VMI без https://
required: true
type: str
vmi_first_username:
description: Email пользователя
required: true
type: str
vmi_first_password:
description: Пароль пользователя
required: true
type: str
state:
description: Состояние VM
required: false
default: present
choices: [present, absent]
type: str
name:
description: Имя виртуальной машины
required: true
type: str
password:
description: Пароль для VM
required: true
type: str
cluster:
description: ID кластера
required: true
type: int
os:
description: ID операционной системы
required: true
type: int
ram_mib:
description: RAM в Мибибайтах
required: true
type: int
hdd_mib:
description: HDD в Мибибайтах
required: true
type: int
cpu_number:
description: Количество CPU
required: true
type: int
ipv4_pool:
description: Список ID пулов IPv4
required: true
type: list
elements: int
validate_certs:
description:
- Отключает проверку SSL-сертификатов ('no' для отключения).
required: false
type: str
'''
EXAMPLES = r'''
- name: Создать VM если отсутствует
vm:
vmi_domain: vmi.example.com
vmi_first_username: admin@example.com
vmi_first_password: secret
name: My_vm
password: vmsecret
cluster: 1
os: 1
ram_mib: 512
hdd_mib: 5000
cpu_number: 1
ipv4_pool: [1]
validate_certs: no
state: present
'''
def create_vm(base_url, token, vm_spec, validate_certs, module):
url = f"{base_url}/vm/v3/host"
body = json.dumps(vm_spec)
headers = {
'Content-Type': 'application/json',
'Cookie': f'ses6={token}',
'x-xsrf-token': token,
}
body = json.dumps(vm_spec)
kwargs = {
'url': url,
'method': 'POST',
'headers': headers,
'data': body,
}
if validate_certs == 'no':
kwargs['validate_certs'] = False
try:
response = open_url(**kwargs)
response_body = response.read().decode('utf-8')
if response.getcode() in (200, 201):
return json.loads(response_body)
else:
module.fail_json(msg=f"Ошибка создания VM. HTTP {response.getcode()}", response_body=response_body)
except Exception as e:
module.fail_json(msg="Ошибка при создании VM, проверьте статус узла кластера", kwargs=kwargs, error=str(e))
def run(base_url, token, state, validate_certs, module):
vm_name = module.params['name']
vm_password = module.params['password']
cluster = module.params['cluster']
os_id = module.params['os']
ram_mib = module.params['ram_mib']
hdd_mib = module.params['hdd_mib']
cpu_number = module.params['cpu_number']
ipv4_pool = module.params['ipv4_pool']
endpoint = f"/vm/v3/host?where=(name+EQ+'{vm_name}')"
vmi_info = get_vmi_info(base_url, token, endpoint, validate_certs, module).get('size', 0)
if state == 'present':
if vmi_info != 0:
# VM существует, ничего не меняем
module.exit_json(changed=False, finder=vmi_info)
else:
# Создаем VM
vm_spec = {
"name": vm_name,
"password": vm_password,
"cluster": cluster,
"os": os_id,
"ram_mib": ram_mib,
"hdd_mib": hdd_mib,
"cpu_number": cpu_number,
"ipv4_pool": ipv4_pool,
}
created_vm = create_vm(base_url, token, vm_spec, validate_certs, module)
module.exit_json(changed=True, vm=created_vm)
else:
# Поддержка state=absent можно сделать позже
module.exit_json(changed=False, msg="state=absent пока не реализован")
def main():
module = AnsibleModule(
argument_spec=dict(
vmi_domain=dict(required=True, type='str'),
vmi_first_username=dict(required=True, type='str'),
vmi_first_password=dict(required=True, type='str'),
state=dict(required=False, default='present', choices=['present', 'absent']),
name=dict(required=True, type='str'),
password=dict(required=True, type='str'),
cluster=dict(required=True, type='int'),
os=dict(required=True, type='int'),
ram_mib=dict(required=True, type='int'),
hdd_mib=dict(required=True, type='int'),
cpu_number=dict(required=True, type='int'),
ipv4_pool=dict(required=True, type='list', elements='int'),
validate_certs=dict(required=False, type='str'),
),
supports_check_mode=False,
)
base_url = f"https://{module.params['vmi_domain']}"
email = module.params['vmi_first_username']
password_user = module.params['vmi_first_password']
state = module.params['state']
validate_certs = module.params.get('validate_certs')
# Получаем токен
token = get_auth_token(
base_url=base_url,
email=email,
password=password_user,
validate_certs_param=validate_certs,
module=module
)
# Вызов основной функции
run(base_url, token, state, validate_certs, module)
if __name__ == '__main__':
main()
Завершаем работу переработкой созданного ранее сценария управления виртуальными машинами vm.yml, заменим ansible.builtin.uri на свой модуль. Прошу не ругать за FQCN: все-таки модули мы не упаковывали в коллекцию и подгрузим их через указанные пути в переменных.
---
- name: Install VMmanager 6 playbook
hosts: all
debugger: on_failed
tasks:
- name: Get up vm
vm_info:
vmi_domain: "{{ ansible_ssh_host }}"
vmi_first_username: vm@example.com
vmi_first_password: uA2sW2nT9czV
name: My_vm
validate_certs: no
- name: Создать VM если отсутствует
vm:
vmi_domain: "{{ ansible_ssh_host }}"
vmi_first_username: vm@example.com
vmi_first_password: uA2sW2nT9czV
name: My_vm
password: vmsecret
cluster: 1
os: 1
ram_mib: 512
hdd_mib: 5000
cpu_number: 1
ipv4_pool: [1]
validate_certs: no
state: present
Для использования локальных модулей добавим пути через переменные:
export ANSIBLE_LIBRARY=/home/my_user/vm6_ansible/plugins/modules
export ANSIBLE_MODULE_UTILS=/home/my_user/vm6_ansible/plugins/module_utils
И протестируем — запустим сценарий дважды, чтобы убедиться, что модуль действительно идемпотентен. Запускаем с помощью команды:
ansible-playbook --private-key /root/.ssh/private_key -i IP, -u root vm.yml
Смотрим результат:

В веб-интерфейсе платформы проверяем — действительно, модуль идемпотентен, создана одна виртуальная машина.

На этом наша работа с Infrastructure as Code, знакомство с платформой VMmanager и VMmanager API завершены. Желаю успехов в экспериментах, а также освоении новых инструментов и технологий.
AntonSurikov
Интересное решение проблемы с автообновлением при кастомизации конфигов — часто сталкивался с тем, что при ручных правках всё ломается после следующего обновления. Systemd-сервисы для кастомной логики выглядят надёжнее, чем городить свои демоны. Единственное, хотелось бы подробнее про проверку целостности и откат. Например, что именно сравнивается и как хендлится частичное несоответствие. В остальном респект, vmmanager выглядит всё более зрелым решением.