QEMU太复杂?流浪者(Vagrant)助你一臂之力

简介

Vagrant[1]是一个开源的虚拟化开发环境管理工具,采用Ruby开发。它自身并不提供任何虚拟化能力,而是通过封装VirtualBox、QEMU、KVM等Hypervisor提供一套更加方便使用的接口。Vagrant的角色类似于剥离了底层containerd及runC的Docker客户端及守护进程,它创造了“box”的概念,每一个box都是一个打包好的Vagrant环境,类似于容器镜像,一经发布,任何人都可以拿到这个box并在自己的机器上运行。

在已安装配置好Vagrant的机器上,启动一个配置好网络的虚拟机最少只需要两行命令:

vagrant init hashicorp/bionic64
vagrant up

与传统虚拟机创建工具相比,Vagrant显然方便得多。

简洁、优雅、开箱即用、一键部署,这些也是Metarget[2]的追求和理念。借助Vagrant实现对虚拟机的管理,则不必再带着复杂的参数调用QEMU等工具。本文记录了Vagrant的基本用法。官方也提供了一些入门教程[3],推荐阅读。

某种程度上来说,Vagrant与Docker是在不同架构层次解决相同的问题。抛开我们在Metarget项目上的需求不谈,Vagrant本身也是一款能够提高研究、研发人员工作效率的利器,值得我们收入兵器谱,应用在后续工作中。

安装部署

最新的安装方法可参考Vagrant官方说明[4],本文记录的是2021年5月24日可用的安装步骤。

部署环境为一台内核版本为4.15.0-142-generic的Ubuntu 18.04虚拟机(需开启硬件虚拟化支持)。

我们计划使用QEMU-KVM作为虚拟机管理器,整个安装过程包含三个步骤(未特殊说明则默认均以root权限执行),下面依次进行说明。

注意,QEMU、KVM等组件在不同版本的Ubuntu系统上的安装方式存在细节差异,具体还请参考官方文档。

TL; DR

在开启硬件虚拟化的Ubuntu 18.04系统上,执行以下命令(后面各步骤的总结)来安装部署Vagrant:

apt-get install qemu
apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
adduser `id -un` libvirt
adduser `id -un` kvm

curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
apt-get update && apt-get install vagrant

apt-get build-dep vagrant ruby-libvirt
apt-get install qemu libvirt-bin ebtables dnsmasq-base
apt-get install libxslt-dev libxml2-dev libvirt-dev zlib1g-dev ruby-dev
vagrant plugin install vagrant-libvirt

1. 安装QEMU-KVM

参考QEMU[5]和KVM[6]的文档,步骤如下:

# qemu
apt-get install qemu
# kvm
apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
adduser `id -un` libvirt
adduser `id -un` kvm
# 安装完成后执行以下命令,无报错则安装成功
virsh list --all
# 确认libvirtd服务处于运行状态
systemctl status libvirtd

2. 安装Vagrant

参考Vagrant的官方说明[7],步骤如下:

curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
apt-get update && apt-get install vagrant

3. 安装vagrant-libvirt插件

参考vagrant-libvirt的仓库说明[8],步骤如下:

# 安装依赖项
apt-get build-dep vagrant ruby-libvirt
apt-get install qemu libvirt-bin ebtables dnsmasq-base
apt-get install libxslt-dev libxml2-dev libvirt-dev zlib1g-dev ruby-dev
# 安装vagrant-libvirt插件
vagrant plugin install vagrant-libvirt

基本用法

本节,我们以创建Ubuntu 18.04虚拟机为例展示如何使用Vagrant管理虚拟机。大家可以将这个过程与“使用Docker创建Ubuntu 18.04容器”的过程对比,它们有一些相似的设计理念。

1. 添加Box

Vagrant创造了“box”概念,每一个box都是一个打包好的Vagrant环境,类似于容器镜像,一经发布,任何人都可以拿到这个box并在自己的机器上运行。就像创建容器镜像一样,我们当然可以创建自己的box,但是初学阶段还没有这个必要。我们直接拿别人创建好的box来创建虚拟机。

考虑到网络情况,我们最好先将要使用的box下载下来。

需要注意的是,不同box支持的底层Hypervisor(即下图中的providers)可能不同,要选择那些支持自己实际环境Hypervisor的box来使用。

首先,从Vagrant Cloud[9](类似于Docker Hub)上下载“generic/ubuntu1804”[10] box到安装好Vagrant的宿主机,假设该文件的名称是ubuntu1804.box。我们在Vagrant中添加该box,并将其命名为ubuntu-bionic

vagrant box add --provider=libvirt --name ubuntu-bionic ./ubuntu1804.box

根据官方文档[11],添加后的box存放在$HOME/.vagrant.d/boxes/目录下(仅供了解,日常使用不需要涉及)。

2. 编写Vagrantfile

Vagrant使用基于Ruby的Vagrantfile文件(是不是在Docker中见过类似的东西?)来描述一个虚拟机。

首先创建一个目录,如vagrant_getting_started,然后在这个目录中创建一个Vagrantfile文件,内容如下:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu-bionic"
  config.vm.provider :libvirt do |libvirt|
    libvirt.cpus = 2
    libvirt.memory = 1024
  end
end

内容非常好理解,大意是基于之前添加的ubuntu-bionic box创建一个拥有2核CPU、1G内存的虚拟机。

事实上,Vagrantfile中可以加入许多复杂的设定,来满足具体场景的需求。对此,本篇教程不再过多展开,感兴趣的朋友可以参考官方文档[12]。

3. 启动和使用虚拟机

万事俱备,在前述vagrant_getting_started目录下执行vagrant up启动虚拟机。如果一切正常,过程将类似下面这样:

root@metarget-test:~/vagrant_getting_started# vagrant up
Bringing machine 'default' up with 'libvirt' provider...
==> default: Creating image (snapshot of base box volume).
==> default: Creating domain with the following settings...
==> default:  -- Name:              vagrant_getting_started_default
==> default:  -- Domain type:       kvm
==> default:  -- Cpus:              2
...
==> default:  -- Memory:            1024M
...
==> default:  -- Base box:          ubuntu-bionic
==> default:  -- Storage pool:      default
==> default:  -- Image:             /var/lib/libvirt/images/vagrant_getting_started_default.img (128G)
...
==> default: Creating shared folders metadata...
==> default: Starting domain.
==> default: Waiting for domain to get an IP address...
==> default: Waiting for SSH to become available...
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...

接着,我们可以在前述目录下执行vagrant status查看虚拟机状态:

root@metarget-test:~/vagrant_getting_started# vagrant status
Current machine states:

default                   running (libvirt)

The Libvirt domain is running. To stop this machine, you can run
`vagrant halt`. To destroy the machine, you can run `vagrant destroy`.

可以看到,虚拟机已经正常运行。我们可以执行vagrant ssh进入该虚拟机:

root@metarget-test:~/vagrant_getting_started# vagrant ssh
Last login: Mon May 24 07:56:51 2021 from 192.168.121.1
vagrant@ubuntu1804:~$ whoami
vagrant
vagrant@ubuntu1804:~$ sudo su
root@ubuntu1804:/home/vagrant# whoami
root

至此,就可以像通过SSH使用传统服务器和虚拟机一样开始工作了。

其他的一些常用操作如下:

  • vagrant halt:关机
  • vagrant destroy:关机并销毁
  • vagrant suspend:暂停虚拟机
  • vagrant resume:恢复运行
  • vagrant reload:重启虚拟机并重载Vagrantfile配置

常见问题

宿主机与虚拟机之间如何共享文件?

根据官方文档[13],默认情况下虚拟机内的/vagrant目录与宿主机上的Vagrantfile所在目录同步。然而,在笔者前述测试过程中,Vagrant并未自动帮我们配置共享文件夹。经查官方手册[14],我们可以在Vagrantfile中手动配置共享目录:

Vagrant.configure("2") do |config|
  config.vm.synced_folder ".", "/vagrant"
  # ...
end

如何访问虚拟机内的服务?

根据官方文档[15],Vagrant提供了三种高层次的网络配置方式:端口转发、私有网络、公有网络。

1. 端口转发

顾名思义,在宿主机与虚拟机端口之间做转发。配置十分简单:

Vagrant.configure("2") do |config|
  config.vm.network "forwarded_port", guest: 2003, host: 12003
end

更多配置参数可参考官方文档[16]。

2. 私有网络

顾名思义,为虚拟机配置一个私有网络IP。方式有两种:DHCP和静态IP。

DHCP

Vagrant.configure("2") do |config|
  config.vm.network "private_network", type: "dhcp"
end

在DHCP的情况下,我们事先无法知道该虚拟机的IP,只能在虚拟机创建后通过vagrant ssh连接上去执行ifconfig之类的命令查询获得IP。

静态IP

Vagrant.configure("2") do |config|
  config.vm.network "private_network", ip: "192.168.50.4"
end

更多配置参数可参考官方文档[17]。

3. 公有网络

参考官方文档[18],这里不再介绍。

如何对虚拟机执行自动化操作?

根据官方文档[19],Vagrant允许我们在虚拟机创建后自动化执行一些操作。例如,我们可以配置命令虚拟机在启动后自动执行一个Bash脚本:

Vagrant.configure("2") do |config|
  # ...
  config.vm.provision :shell, path: "bootstrap.sh"
end

更多功能可参考官方文档[20]。

参考文献

  1. https://github.com/hashicorp/vagrant
  2. https://github.com/brant-ruan/metarget
  3. https://learn.hashicorp.com/tutorials/vagrant/getting-started-index?in=vagrant/getting-started
  4. https://www.vagrantup.com/downloads
  5. https://www.qemu.org/download/
  6. https://help.ubuntu.com/community/KVM/Installation
  7. https://www.vagrantup.com/downloads
  8. https://github.com/vagrant-libvirt/vagrant-libvirt#installation
  9. https://vagrantcloud.com/boxes/search
  10. https://app.vagrantup.com/generic/boxes/ubuntu1804
  11. https://docs-v1.vagrantup.com/v1/docs/boxes.html
  12. https://www.vagrantup.com/docs/vagrantfile
  13. https://learn.hashicorp.com/tutorials/vagrant/getting-started-synced-folders?in=vagrant/getting-started
  14. https://www.vagrantup.com/docs/synced-folders/basic_usage
  15. https://www.vagrantup.com/docs/networking
  16. https://www.vagrantup.com/docs/networking/forwarded_ports
  17. https://www.vagrantup.com/docs/networking/private_network
  18. https://www.vagrantup.com/docs/networking/public_network
  19. https://learn.hashicorp.com/tutorials/vagrant/getting-started-provisioning?in=vagrant/getting-started
  20. https://www.vagrantup.com/docs/provisioning
Per Aspera Ad Astra