Vagrant 简介与常用操作及配置

前方许多有关于 Kafka, Docker, Python 和 Kubernates 的文章都是在 Vagrant 虚拟机中做的 Demo,经常用到的一些 Vagrant 命令是时候有必要写篇日志记录下来。Vagrant 是 HashiCorp 家族中的一员,HashiCorp 旗下著名的工具还有  Terraform, ConsulVault, Boundary, Packer, NomadWaypoint

说起 Vagrant,不得不提起与之仿佛类似的 Docker,其实它们相差还是比较大的,只因它们给人的外在感觉都是命令行控制 Linux。Vagrant 实质是一个虚拟机的外挂,让我们更方便的用 Vagrant 命令与虚拟机交互,而不用在宿主机与虚拟机间来回切换,管理多个虚拟机就更得心应手了; 而 Docker 是一个容器,容器的本质是宿主机上的一个进程,只是用命名空间与该进程的文件系统,进程,网络等进行了隔离,使得该容器进程看似一个虚拟  OS。

Vagrant 是开发环境的部署工具, 而 Docker 是运行环境的部署工具; Vagrant 操作的是一个标准的 Linux 或  Windows 操作系统,而 Docker 的镜像考虑到发布服务的个头,通常是一个裁剪的系统,去除了服务器非必要的命令。既然 Vagrant 对应的是虚拟机,那么在 Vagrant 中的操作,安装的软件在 Vagrant 退出后都会保留下来,而 Docker 中操作的都是当前容器(copy-on-write),并不影响所对应的镜像, 除非用 docker commit 固化为新的镜像.

明白了 Vagrant 只是一个虚拟机的皮,那他在不同的硬件平台或操作系统下需要与不同的 Provider, 如 VirtualBox, Hyper-V, VMware 等配合工作,还能用 Vagrant 来操作 Docker。

有了 Vagrant, 从此不再需要下载不同操作系统的 ISO 安装镜像文件,耗时的逐步安装操作系统,也不用手工的下载别人安装好并导出的虚拟机文件,一切有点类似 Docker 一样从远程公共仓库中选择系统即可。

因此在 Mac OS X 下要能使用 Vagrant 的话,我们先安装免费的 Virtualbox, 然后是 Vagrant,可用 brew 进行安装

$ brew install --cask virtualbox
$ brew install vagrant

安装好后就能使用 Vagrant 命令了,当前版本是

$ VirtualBoxVM --help
Oracle VM VirtualBox VM Runner v6.1.20
$ vagrant --version
Vagrant 2.2.15

这时候用 vagrant 的 up, halt 等子命令就可以看作是在和 VirtualBoxVM, VBoxManage 等命令通信。下面来看一下 vagrant 命令 如何创建, 启动, ssh 及关闭一个虚拟机。

Docker 有 Dockerfile, 与 Vagrant 相应的是 Vagrantfile,在同一个宿主机上我们要创立多个虚拟机的话,通常为每一个 Vagrantfile  创建一个独立的目录。比如我们想创建一个 Ubuntu 20.04 LTS 的虚拟机,首先创建一个目录 ubuntu-20.04, 然后在其下创建文件 Vagrantfile

执行 vagrant 命令时定位 Vagrantfile 文件的顺序是从当前一直上升到根目录下, 如 ./Vagrantfile, ../Vagrantfile, ../../Vagrantfile, ... 一直到  /Vagrantfile。可通过修改环境变量 VAGRANT_CWD 改变起始搜索位置。一个 Vagrantfile 文件中还能定义多个虚拟机,下面有说明。

再搜索到官方的 ubuntu 20.04 的 box(不再叫做镜像了) https://app.vagrantup.com/boxes/search?utf8=%E2%9C%93&sort=downloads&provider=virtualbox&q=ubuntu+20.04, 进到 https://app.vagrantup.com/ubuntu/boxes/focal64 页面拷贝

为 Vagrantfile 的内容,演示前面操作的命令系列如下

~$ mkdir ubuntu-20.04 && cd ubuntu-20.04
ubuntu-20.04$ cat << EOF > Vagrantfile
Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/focal64"
end
EOF

也可用下面的 vagrant init 命令

ubuntu-20.04$ vagrant init ubuntu/focal64

达成与前面的 cat 命令的基本等价的结果,只不过 vagrant init 生成的 Vagrantfile 文件中有详细的注释说明。Vagrantfile 是一个采用了 ruby 语法的文件, 因为 Vagrant 是用 ruby 写成的。

接下来介绍的 vagrant 虚拟机的操作指令默认都是针对当前目录下的 Vagrantfile 文件所指引的虚拟机。如果想在任意目录下针对某个虚拟机进行操作,需要指定虚拟机的名称或 ID, 在 Vagrantfile 中不指定名称的话所有虚拟机的名称都是 default, ID 是不一样的。如何获得虚拟机的 Name 或  ID 可用 vagrant global-status 命令,后面会讲到。

现在开始用 up, ssh, halt 等命令来启动虚拟机,ssh 连接到虚拟,以及关闭虚拟机

下面从命令行提示符可识别出当前是在宿主机还是虚拟机中的 shell, 类似的 $ 或 ubuntu-20.04$ 是在宿主机上,vagrant@ubuntu-focal:~$ 是在虚拟机中。vagrant ssh 的用户名是 vagrant

vagrant up 启动一个虚拟机

启动后直接对应到 VirtualBox 中一个虚拟机

如果创建一个新的目录,放个  Vagrantfile 文件再用 vagrant up 启动又会在 VirtualBox 中看到对应的新虚拟机。同时注意 Vagrant 所创建的虚拟机内存只有 1024 MB, 它的 Settings 中可看到其他的配置,如网络是 NAT 等,这些都可以通过 Vagrantfile 来配置,尽量不要在 VirtualBox 中直接修改虚拟机的设置。

虚拟机与宿主机间文件共享

注意到 vagrant  启动过程中显示了挂载共享目录,如上面的

也就是在虚拟机上进到 /vagrant 目录,内容就是宿主机上 /Users/yanbin/vagrant/ubuntu-20.04 目录,也就是 Vagrantfile 文件所在的目录。在虚拟机中挂载的是可读写的文件系统,通过这个连接就能在虚拟机与宿主机之间共享文件了。

vagrant ssh 登陆虚拟机

此时进入了那个 VirtualBox 虚拟机 ubuntu-2004_default_1619...中,现在可以做任何 ssh 可做的事情。

vagrant halt: 关闭当前虚拟机,在 VirtualBox 中该虚拟机的状态变为 Powered Off

vagrant status: 查看当前虚拟机的状态, running, poweroff, not created(从未 up 过),saved(执行了 vagrant suspend 后的状态)

vagrant destroy 销毁虚拟机

ubuntu-20.04$ vagrant destroy
default: Are you sure you want to destroy the 'default' VM? [y/N] y
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives..

vagrant destroy 操作会把对应的虚拟机从 VirutalBox 中删除掉。

vagrant 的命令可以用 vagrant --help 列出来, 下面列一些其他 vagrant 常用的操作

vagrant global-status -- 列出所有虚拟机的状态

有了以上的内容,前面的 vagrant 虚拟机相关命令就可以带上 id 或 name 作针对性操作, 当 name 不是唯一时只会作用到第一可用的虚拟上

vagrant up cef6e15 会启动 ubuntu-18.04 的虚拟机

vagrant ssh b9988a1 会 ssh 连接到 fedora30 虚拟机

有了 id 或 name, 每次 vagrant  操作就不必跑到 Vagrantfile 所在的目录去执行命令。

vagrant cloud search -- 搜索 box

除了打开 https://app.vagrantup.com/boxes/search 网页搜索外,我们也可以用命令来搜索

会列出关键字对应的所有 box, 及版本和 providers, 本人用这个命令搜索比网页中还来得慢,倒不如在网页中找到相应的 box 再加到 Vagrantfile 中或 vagrant init, 或 vagrant box add。

vagrant box add -- 添加 box 到本地仓库

$ vagrant box add generic/fedora30 --provider virtualbox

如果没有指定 provider 会列出可用的 provider 进行选择。vagrant box add 还能从一个 url 或文件路径中添加一个 box. 在 Mac OS X 下 vagrant box add 的 box 存储在 ~/.vagrant.d/boxes 目录中

vagrant box list -- 列出本地的 box

$ vagrant box list
generic/fedora30 (virtualbox, 3.2.18)
ubuntu/bionic64 (virtualbox, 20210415.0.0)
ubuntu/focal64 (virtualbox, 20210415.0.0)

vagrant box remove -- 移除相应的 box

$ vagrant box remove ubuntu/bionic64
Removing box 'ubuntu/bionic64' (v20210415.0.0) with provider 'virtualbox'...

当然它会从 ~/.vagrant.d/boxes 目录中清除掉。

其他的 vagrant box 命令还有

vagrant box outdated: 检查所有的 box 是否有更新

vagrant box update: 更新 box

vagrant 的 share 命令

vagrant 不能直接使用 vagrant share 命令,需要先安装 vagrant-share 插件和 ngrok 组件

$ vagrant plugin install vagrant-share
$ brew install ngrok

vagrant share --ssh, vagrant share --http 80 等命令使用起来没觉得有多大的意义。

以下是关于 Vagrantfile 配置的一些知识,包括机器名, 内存, CPU, 网络, 及端口的配置, 还能初始化虚拟机时预装软件。

查看我们用像 vagrant init ubuntu/focal64 命令生成的 Vagrantfile 文件,可以看到非常详尽的 Vagranfile 配置说明。官方说明文档在这里 https://www.vagrantup.com/docs/vagrantfile, 本人一直很喜欢 HashiCorp 的在线文档,其中数 Terraform 的在线文档查阅的最多。

配置虚拟机的 hostname

默认的话, 虚拟机的 hostname 是不太确定的,来自 box 中预设的机器名。当我们配置多个 Vagrant 虚拟机进行集群的时候,就有必要为每个虚拟机指定一个有意义的机器名,这时需要配置 Vagrantfile 如下

然后用命令 vagrant reload 重新启动虚拟机(相当于 vagrant halt; vagrant up),再 vagrant ssh 登陆后看到 hostname 变成了 k8s-master

ubuntu-20.04$ vagrant ssh
vagrant@k8s-master:~$ hostname
k8s-master 

配置虚拟机的标识名

此处所说的虚拟机标识名和上面虚拟机的 hostname 是不同的概念,而是指在 vagrant global-status 中看到的 name

记得前面的与虚拟机操作相关的命令都可以带个参数 [id|name], 就是上面的 id 或 name,但因为 name 默认总是 default,所以只能用 id 来标识虚拟机,如

$ vagrant ssh f284d36
$ vagrant halt d3f48b8

如果我们给虚拟机一个 name, 比如 ubuntu-server-1, 那么操作时可以是

$ vagrant up ubuntu-server-1

要配置虚拟机的 name, Vagrantfile 要调用 config.vm.define 函数,并传入一个 name

再重启虚拟机,用 vagrant global-status 看到的就是

这里就能使用 name 来操作了。不过本人认为能用 id 操作就足够了,从 vagrant global-status 中的 directory 可知操作的是哪个虚拟机,不管是用 id 还是 name, 总需频繁的用 vagrant global-status 来查看所有虚拟机的 id 和 name。所以这里的 name 的实际意义并不大。

注:在更新的虚拟机 name 后,需要用 vagrant global-status --prune 清理掉垃圾条目。

配置虚拟机的名称

怎么又来了一个名称,我们这里之所以说虚拟机名称是因为......,先配置再看运行后的效果

vagrant destroy 掉再 vagrant up 重新启动,这时候配置的虚拟机名称对应到 VirtualBox 中的

ubuntu-server-x, 而不是 xxx_default 后面跟一串带纳秒的时间戳。当我们选择使用 vagrant 后大概很少会去查看 VirtualBox 中对应虚拟机长什么样了。

配置虚拟机内存和 CPU

对虚拟机内存的配置显然是很重要的,试想我们能拿默认的 1G 内存做什么开发工作?前面我们已经来到了 config.vm.provider 块的配置,对内存和 CPU 的配置也在当中进行,比如我们配置 2G 内存和 3 个 CPU

vagrant reload 后,vagrant ssh 进到虚拟机,查看内存和 CPU

内存从默认的 1G 变成了 2G, 并且 CPU 有了 3 个。

网络的配置

Vagrant 虚拟机默认的网络配置是 NAT, 它允许虚拟机访问外部网络,并与宿主机或本地其他虚拟机之间通信,但外部无法直接访问虚拟机。我们可以配置从 DHCP 上获得 IP(它将与宿主机处于同一个网络,相当于 VirutalBox 中的 bridge 方式), 或用静态 IP 地址,这样更方便与外部机器交互。

再执行 vagrant reload 时就会询问桥接到哪个网络接口(只会询问一次),选择本机连接外部网络的接口就行,或者配置 public_network 时指定外部网络接口,像

如此 vagrant upvagrant reload 就不会提示选择外部网络接口

vagrant ssh 连接后就能看到

其中 192.168.86.47 是从我的 wifi 路由器上分配的 IP 地址, 而不仅仅是 NAT 上分配的 10.0.2.15 这个地址。

config.vm.network "private_network", ip: "172.28.1.100"

以上静态 IP 地址的方式会促使 VirtualBox 的 OS 上创建一个虚拟网络 172.28.1.1/24,比如 vboxnet1, 或  vboxnet2(如果  vboxnet1 已使用)。所以在虚拟机中分配的静态 IP 总是可以宿主机和该宿主机上的其他虚拟机访问。

如果在 Vagrantfile 中配置了太多的静态 IP 段会在宿主机操作系统中产生太多的 vboxnet1, vboxnet2, vboxnet3... 那样的网络设备。用 ifconfig 可以看到它们

如果因为在 Vagrantfile 中配置静态 IP 产生太多的 vboxnetX 网络接口的话,需用 VBoxMange 进行清理

$ VBoxManage hostonlyif remove vboxnet1
$ VBoxManage hostonlyif remove vboxnet2

给 config.vm.network 加上 auto_config: false 参数将不再自动配置 vboxnetX,需手工创建,可避免产生一大堆垃圾,完整的配置如下

config.vm.network "private_network", ip: "172.28.1.100", auto_config: false

比如可用 VBoxManger 来创建一个网络

$ VBoxManage natnetwork add --netname myvboxnet1 --network "172.28.1.100/24" --enable --dhcp on

在 VirtualBox 中会是这样

端口转发配置

特别是配置为默认 NAT 的网络或静态 IP 时,外部机器(非宿主机或其上的其他虚拟机)无法直接访问,这时候就要用到端口转发的功能,由宿主机上一个端口指向到虚拟机内部的服务端口。

然后 vagrant reload, 启动的时候我们可以看到输出

或用 vagrant port 列出当前的端口映射

$ vagrant port
22 (guest) => 2200 (host)
80 (guest) => 8080 (host)

宿主机上看到由 VBoxHead1 启动的 8080 监听端口

测试它之前,需要 vagrant ssh 进到虚拟机安装一个 apache 启动虚拟机中的 80 服务,然后访问宿主机的 8080 端口

config.vm.network "forward_port", guest: <port>, host: <port> 可以在 Vagrantfile 中出现多次用以配置多个端口映射,所以它是一个 ruby 函数调用,而赋值语名。

预安装软件(provision)

如果我们需要在虚拟机安装应用的话,在第一次 vagrant up (创建虚拟机后) 用 vagrant ssh 进行安装,而后在 vagrant destroy 了再 vagrant up 时又得相同的操作。欲避免重复安装相同软件的操作,可以使用别人已安装相应软件的 box,或者在手动安装软件后用 vagrant package --base name --output /path/to/name.box 命令保存为新的 box, --base name 中的 name 可用 VBoxManage list vms 列出来。

vagrant package 不指定 --output 的话会保存在当前目录中,文件名为 package.box, 以后想要用本地保存的 box 就可用  vagrant box add /path/to/name.box 来添加。

除以上两种办法外,就是这里要说的  provision, 先看配置

这里配置的 config.vm.provision 只在第一次创建虚拟机的时候执行,以后 vagrant up 不会再执行它,所以用 vagrant destroy 后也不怕,下回用 vagrant up 时又会重新创建新的虚拟机并安装上 Apache2。当然在虚拟机已创建好后,再修改 config.vm.provision 中的内容后只单纯用 vagrant up 是不会发生作用的。

除非用  vagrant provision 对已启动的虚拟机强制执行 config.vm.provision 中的内容。或 vagrant [up|reload] --provision 启动/重载时强制执行 provision.

provision 除了可用 inline 执行脚本的方式,还能执行外部脚本文件,或调用 Ansible, Chef, puppet 等,详情请见 https://www.vagrantup.com/docs/provisioning

一个  Vagrantfile 中配置多个虚拟机

每创建一个虚拟机都新建一个 Vagrantfile 文件,还要把它们安置到不同的目录下也有点儿麻烦,特别是一组相关的虚拟机,有些配置还是共享的情况下。单文件多个虚拟机帮我们解决了这一问题,如

然后 vagrant up 就会启动这两个虚拟机,分别显示 webdb 的启动过程, 查看状态

要单独控制的话加上 id 或 name, 如 vagrant up web, vagrant ssh db 等, 共同的配置用 config.vm.xxx.

不使用默认的 Vagrantfile 配置文件

前面无论是配置一个还是多个虚拟机都是在 Vagrantfile 文件中进行的,如果需要更多的 Vagrant 虚拟机的话,不得不创建多个目录来存放 Vagrantfile 文件,那些目录显得有些多余。那能不能用不同的文件名来定义 Vagrant 虚拟机呢,既然提到了,答案就是肯定的, 比如 Vagrantfile-Redis, Vagrantfile-Jenkins。这样我们就能把所有的 Vagrant 配置文件放在同一个工作目录中。

那么接下来就要告诉 Vagrant 命令使用非默认的 Vagrantfile 文件,要用到环境变量 VAGRANT_VAGRANTFILE

VAGRANT_VAGRANTFILE=Vagrantfile-redis vagrant up

或用 export VAGRANT_VAGRANTFILE=Vagrantfile-redis 给当前窗口设置好环境变量,再 vagrant up

假设 Vagrantfile-redis 文件内容为

vagrant up 第一次启动就能看到 provision shell 的输出

test $ VAGRANT_VAGRANTFILE=Vagrantfile-redis vagrant up
......
==> default: Mounting shared folders...
default: /vagrant => /Users/yanbin/test
==> default: Running provisioner: shell...
default: Running: inline script
default: initialized by Vagrantfile-redis

vagrant global-status  看下

vagrant global-status 中只能看到 Vagrantfile-redis 所在的目录,而不知道配置文件的名称。这样做有一个好处就是同一个目录中的不同 Vagrantfile-xxx 配置的虚拟机能够共享同一个 /vagrant 目录。

ZSH 的 Vagrant 插件

Zsh 有对 Vagrant 的插件,需要在 ~/.zshrc 中加到  plugins 列表中

plugins=(git docker vagrant)

它对 vagrant 的常用命令定义了别名,如

  1. vup: vagrant up
  2. vgs: vagrant global-status
  3. vssh: vagrant ssh
  4. ......

更多命令别名请查看 https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/vagrant

所遇见的问题

  1. 没有挂载共享目录 /vagrant
    在配置文件中加了 config.vm.synced_folder ".", "/vagrant", 启动时报 "Vagrant was unable to mount VirtualBox shared folders. This is usually
    because the filesystem "vboxsf" is not available...."  错误
    解决办法,对  VirtualBox 6.1.xx 安装插件 "vagrant plugin install vagrant-vbguest",解决。或有说降级到 VirtualBox 5.1.18。
  2. 多个虚拟机只获得一个相同的 IP 地址 "10.0.2.15", 造成彼此之间不能通信,配置中加上 config.vm.network "private_network", type: "dhcp" 解决,必要时检查 VirtualBox 的网络。

链接:

  1. 超详细的 Vagrant 上手指南
  2. How to change Vagrant 'default' machine name?

本文链接 https://yanbin.blog/vagrant-intro-config-commands/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。