Как использовать Ansible для управления серверами виртуальных машин, на которых работает Vagrant?
У меня есть пара серверов, на каждой из которых работают несколько виртуальных машин. На каждом сервере я использую Vagrant для создания и управления виртуальными машинами. Теперь я хочу найти какой-нибудь инструмент (подумал об Ansible, потому что я немного его знаю) для управления всеми виртуальными машинами на всех серверах.
Например, у меня есть репозиторий Git с
Vagrantfile
и на всех серверах есть его клоны. Теперь вручную выполняю
git pull
и
vagrant provision
на каждом сервере, если я что-то поменяю в
Vagrantfile
и я хочу автоматизировать не только этот случай, но и все действия, связанные с Vagrant, которые необходимо выполнить на всех виртуальных машинах на всех серверах.
Погуглил об этом, но все ссылки посвящены использованию Ansible в качестве поставщика Vagrant, а не наоборот. Я понимаю, что мог бы просто запускать команды оболочки с помощью Ansible на всех серверах, но я не думаю, что это действительно хорошее решение, это своего рода "bashsible", но мне нужно более универсальное и многофункциональное решение.
2 ответа
Я понимаю, что могу просто запускать команды оболочки с помощью Ansible на всех серверах, но я не думаю, что это действительно хорошее решение.
Это подразумевает какой-то модуль Ansible с поддержкой бродяг. Мне неизвестно о каких-либо поставках с Ansible; вы могли бы написать один. Или согласитесь на команды оболочки и запустите Vagrant с командным модулем Ansible.
ansible-pull
это пример того, как реализовать стиль вытягивания, аналогичный тому, что вы делаете сейчас. Из cron каждый хост извлекает и запускает playbook, который извлекает репозиторий Vagrant и запускает его.
Или сделайте это в стиле push с помощью
ansible-playbook
от хоста управления.
После небольшого исследования я решил свою проблему. Вот как я это сделал.
Перед написанием кода небольшая заметка об архитектуре того, для чего она была создана - у меня есть пара серверов, на которых запущены виртуальные боксы без управления (ci_vm_servers
раздел в
hosts
файл, например
192.168.3.1
), каждый из них запускает Vagrant для создания и управления несколькими виртуальными машинами (ci_vm_nodes
раздел в
hosts
файл, например
192.168.3.11
).
Теперь код (на основе https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html). В следующем примере кода показан модуль, который может выполнять команды, например, pull Vagrant repo, на всех серверах виртуальных машин одновременно.
#!/usr/bin/python
# Copyright: (c) 2019, Dmitriy Vinokurov <gim6626@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {
'metadata_version': '0.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: vagrant
short_description: Ansible Vagrant module
version_added: "2.9"
description:
- "Module to control remote servers running Vagrant"
options:
command:
description:
- This is the command to send to the remote servers
required: true
author:
- Dmitriy Vinokurov (@gim6626)
'''
RETURN = r''' # '''
from ansible.module_utils.basic import AnsibleModule
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
command=dict(type='str', required=True),
remote_host=dict(type='str', required=True),
)
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
original_message='',
message=''
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
module.exit_json(**result)
command = module.params['command']
remote_host = module.params['remote_host']
vagrant_manager = VagrantManager(module, remote_host, command, result)
if command == 'pull':
vagrant_manager.pull_vagrant_repo_on_vm_server()
elif command == 'provision':
vagrant_manager.vagrant_provision_on_vm_server()
else:
module.fail_json(msg='Unsupported command "{}"'.format(command),
**vagrant_manager.result)
# use whatever logic you need to determine whether or not this module
# made any modifications to your target
result['changed'] = True
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
class VagrantManager:
SUBCOMMAND_ERROR_MSG_TEMPLATE = 'Subcommand "{}" failed while trying to execute "{}" command'
INVALID_REMOTE_HOST_TYPE_ERROR_MSG_TEMPLATE = 'Invalid remote host "{}" type, expected {}'
VM_SERVER_TYPE = 'VM Server'
VM_NODE_TYPE = 'VM Node'
def __init__(self, module, remote_host, command, result):
self.module = module
self.remote_host = remote_host
self.command = command
self.result = result
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
self.result['original_message'] = 'Execute "{}"'.format(self.command)
self.result['command'] = self.command
self.result['message'] = 'OK'
self.result['remote_host'] = self.remote_host
def pull_vagrant_repo_on_vm_server(self):
self._check_if_remote_host_is_vm_server()
self._run_sub_command('git pull', '/home/vbox/ci-vagrant')
def vagrant_provision_on_vm_server(self):
self._check_if_remote_host_is_vm_server()
self._run_sub_command('vagrant provision', '/home/vbox/ci-vagrant')
def _run_sub_command(self, sub_command, cwd=None):
rc, stdout, stderr = self.module.run_command(sub_command, cwd=cwd)
if rc != 0:
self.result['stdout'] = stdout
self.result['stderr'] = stderr
self.module.fail_json(msg=self.SUBCOMMAND_ERROR_MSG_TEMPLATE.format(sub_command, self.command),
**self.result)
def _check_if_remote_host_is_vm_server(self):
remote_host_type = self._guess_remote_host_type()
if remote_host_type != VagrantManager.VM_SERVER_TYPE:
module.fail_json(msg=INVALID_REMOTE_HOST_TYPE_ERROR_MSG_TEMPLATE(module.params['remote_host'], VM_SERVER_TYPE),
**result)
def _check_if_remote_host_is_vm_node(self):
remote_host_type = self._guess_remote_host_type()
if remote_host_type != VagrantManager.VM_NODE_TYPE:
module.fail_json(msg=INVALID_REMOTE_HOST_TYPE_ERROR_MSG_TEMPLATE(module.params['remote_host'], VM_NODE_TYPE),
**result)
def _guess_remote_host_type(self):
if '192.168.3.' not in self.remote_host:
self.module.fail_json(msg='Wrong remote host "{}", it looks like neither VM server nor VM node'.format(self.remote_host),
**self.result)
elif len(self.remote_host.split('.')[-1]) == 1:
return VagrantManager.VM_SERVER_TYPE
else:
return VagrantManager.VM_NODE_TYPE
def main():
run_module()
if __name__ == '__main__':
main()
Для подготовки казни:
- Следуйте инструкциям на https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#common-environment-setup.
- Создать файл
lib/ansible/modules/cloud/vagrant/vagrant.py
в клонированном репо с содержимым, показанным выше
Наконец, вы можете вытащить репозиторий Vagrant на всех серверах, используя
ansible ci_vm_servers -m vagrant -a 'command=pull remote_host={{ inventory_hostname }}' -u vbox --ask-pass -i hosts
, предполагая, что: 1) у вас есть
ci_vm_servers
раздел со списком IP-адресов в
hosts
файл, как указано выше; 2) пользователь
vbox
на всех серверах настроено тянуть без указания пароля.
Например, есть команды только для серверов, а не для виртуальных машин, но их можно легко улучшить.
up/halt/reboot
Команды для выполнения для всех виртуальных машин на всех серверах также могут быть добавлены без проблем.