我有几台服务器,每台都运行着几个虚拟机。在每台服务器上,我都使用 Vagrant 创建和控制虚拟机。现在我想找一些工具(想到了 Ansible,因为我对它有点了解)来控制所有服务器上的所有虚拟机。
例如,我有一个 Git 仓库,Vagrantfile
并且所有服务器都有它的克隆。现在,如果我更改了某些内容,我会在每台服务器上手动执行git pull
,并且我不仅想自动化这种情况,还想自动化所有需要在所有服务器上的所有虚拟机上执行的与 Vagrant 相关的操作。vagrant provision
Vagrantfile
谷歌搜索了一下,但所有链接都是关于使用 Ansible 作为 Vagrant 配置器,而不是相反。我知道我可以在所有服务器上使用 Ansible 运行 shell 命令,但我不认为这是一个真正好的解决方案,它是一种“bashsible”,但我需要更通用、多功能的解决方案。
答案1
我知道我可以在所有服务器上使用 Ansible 运行 shell 命令,但我不认为这是一个好的解决方案
这意味着需要某种 Vagrant 感知型 Ansible 模块。我不知道 Ansible 是否附带任何模块;您可以编写一个。或者,使用 shell 命令并使用 Ansible 命令模块运行 Vagrant。
ansible-pull
是一个如何实现类似于您现在所做的拉取样式的示例。从 cron 中,每个主机都会拉取并运行一个剧本,该剧本会拉取 Vagrant 存储库并运行它。
或者,使用 ansible-playbook
管理主机的推送方式。
答案2
经过一番研究,我解决了这个问题。以下是我解决的方法。
在编写代码之前,先简单介绍一下它所创建的架构 - 我有几个运行无头 VirtualBox 的服务器(例如,文件ci_vm_servers
中的部分),每个服务器都运行 Vagrant 来创建和控制一些虚拟机(例如,文件中的部分)。hosts
192.168.3.1
ci_vm_nodes
hosts
192.168.3.11
现在,代码(基于https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html)。以下示例代码显示了可以同时在所有 VM 服务器上执行命令(例如 pull Vagrant repo)的模块。
#!/usr/bin/python
# Copyright: (c) 2019, Dmitriy Vinokurov <[email protected]>
# 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
- 在克隆的 repo 中创建文件
lib/ansible/modules/cloud/vagrant/vagrant.py
,内容如上所示
最后,你可以在所有服务器上使用拉取 Vagrant repo ansible ci_vm_servers -m vagrant -a 'command=pull remote_host={{ inventory_hostname }}' -u vbox --ask-pass -i hosts
,假设:1)如上所示,文件ci_vm_servers
中有一个包含 IP 列表的部分;2)所有服务器上的用户都设置为拉取而不指定密码。hosts
vbox
示例中只有针对服务器的命令,没有针对虚拟机的命令,但可以轻松改进。up/halt/reboot
还可以毫无问题地添加针对所有服务器上的所有虚拟机执行的命令。