Как использовать 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()

Для подготовки казни:

  1. Следуйте инструкциям на https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#common-environment-setup.
  2. Создать файл 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 Команды для выполнения для всех виртуальных машин на всех серверах также могут быть добавлены без проблем.

Другие вопросы по тегам