超詳細的 Vagrant 上手指南

搭建 Linux 虛擬機,別再用 VirtualBox 從 .iso 文件安裝了。

概述

2020 年了,也許你已經習慣了 docker,習慣了在 XX 雲上快速創建雲主機,但是如果你想在個人電腦上安裝虛擬機來搭建開發/測試環境,Vagrant 仍然不失最佳選擇。

文末有和 docker 的對比說明。

安裝軟件

安裝 VirtualBox

進入 VirtualBox 的主頁,點擊大大的下載按鈕,即可進入下載頁面。

VirtualBox 是一個跨平台的虛擬化工具,支持多個操作系統,根據自己的情況選擇對應的版本下載即可。

注意,除了主程序,還要把對應的擴展包程序也一併下載了。有些高級特性,比如 USB 3.0 等需要擴展包的支持。

download_virtualbox
download_virtualbox

在安裝完主程序後,直接雙擊擴展包文件即可安裝擴展包。

下載頁面先別關,後面還要用到。

安裝 Vagrant

Vagant 網站下載最新的版本,根據自己的操作系統選擇對應的版本下載即可。

注意,Vagrant 是沒有圖形界面的,所以安裝完成後也沒有桌面快捷方式。具體使用方法,接下來會詳細說明。

Vagrant 的安裝程序會自動把安裝路徑加入到 PATH 環境變量,所以,這時候可以通過命令行執行 vagrant version 檢查是否安裝成功:

> vagrant version
Installed Version: 2.2.7
Latest Version: 2.2.8

配置虛機存放位置

創建虛擬機會佔用較多的磁盤空間,在 Windows 系統下默認的虛機創建位置是在 C 盤,所以最好配置到其它地方。

配置 VirtualBox

啟動 VirtualBox 後,通過菜單 管理 -> 全局設定,或者按下快捷鍵 Ctrl + g,在全局設定對話框中,修改 默認虛擬電腦位置,指定一個容量較大的磁盤。

VirtualBox 默認虛機位置
VirtualBox 默認虛機位置

配置 Vagrant

通過 Vagrant 創建虛機需要先導入鏡像文件,也就是 box,它們默認存儲的位置在用戶目錄下的 .vagrant.d 目錄下,對於 Windows 系統來說,就是 C:\Users\用戶名\.vagrant.d

如果後續可能會用到較多鏡像,或者你的 C 盤空間比較緊缺,可以通過設置環境變量 VAGRANT_HOME 來設置該目錄。

在 Windows 系統中,可以這樣操作:新建系統環境變量,環境變量名為 VAGRANT_HOME,變量值為 E:\VirtualBox\.vagrant.d

Vagrant_Home 環境變量
Vagrant_Home 環境變量

注意,最後這個 .vagrant.d 目錄名稱不是必須的,但是建議保持一致,這樣一眼看上去就能知道這個目錄是做什麼用處的了。

下載虛機鏡像

使用 Vagrant 創建虛機時,需要指定一個鏡像,也就是 box。開始這個 box 不存在,所以 Vagrant 會先從網上下載,然後緩存在本地目錄中。

Vagrant 有一個鏡像網站,裏面列出了都有哪些鏡像可以用,並且提供了操作文檔。

但是這裡默認下載往往會比較慢,所以下面我會介紹如何在其它地方下載到基礎鏡像,然後按照自己的需要重置。如果網速較好,下載順利的朋友可以選擇性地跳過部分內容。

下面我給出最常用的兩個 Linux 操作系統鏡像的下載地址:

CentOS

CentOS 的鏡像下載網站是: //cloud.centos.org/centos/

在其中選擇自己想要下載的版本,列表中有一個 vagrant 目錄,裏面是專門為 vagrant 構建的鏡像。選擇其中的 .box 後綴的文件下載即可。這裡可以使用下載工具,以較快的速度下載下來。

這裡我們選擇下載的是 CentOS 7 的最新版本

Ubuntu

Ubuntu 的鏡像下載網站是: //cloud-images.ubuntu.com/

同樣先選擇想要的版本,然後選擇針對 vagrant 的 .box 文件即可。

如果這裡官網的速度較慢,還可以從 清華大學的鏡像站 下載。

下面的例子以 CentOS 7 為例,使用其它版本操作系統的也可以參考。

添加 box

接下來我們需要將下載後的 .box 文件添加到 vagrant 中。

Vagrant 沒有 GUI,只能從命令行訪問,先啟動一個命令行,然後執行:

$ vagrant box list
There are no installed boxes! Use `vagrant box add` to add some.

提示現在還沒有 box。如果這是第一次運行,此時 VAGRANT_HOME 目錄下會自動生成若干的文件和文件夾,其中有一個 boxes 文件夾,這就是要存放 box 文件的地方。

執行 vagrant box add 命令添加 box:

$ vagrant box add e:\Downloads\CentOS-7.box --name centos-7
==> box: Box file was not detected as metadata. Adding it directly...
==> box: Adding box 'centos-7' (v0) for provider:
    box: Unpacking necessary files from: file:///e:/Downloads/CentOS-7.box
    box:
==> box: Successfully added box 'centos-7' (v0) for 'virtualbox'!

命令後面跟着的是下載的文件路徑,並且通過 --name centos-7 為這個 box 指定一個名字。

後面創建虛機都需要指定這個名字,所以盡量把名字取得簡短一點,同時也要能標識出這個鏡像的信息(我們後面會定製自己的基礎鏡像,所以這裡可以簡單點)。

再次查詢,可以看到有了一個 box:

$ vagrant box list
centos-7 (virtualbox, 0)

Vagrant 基本操作

新建虛機

創建一個目錄,先執行 vagrant init

$ mkdir demo
$ cd demo
$ vagrant init centos-7
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

其中的 centos-7 就是我們要使用的 box 名字。

這個命令只是為我們生成一個 Vagrantfile,所以,這裡的名字沒指定或者寫錯了都沒關係,後面會介紹如何編輯這個 Vagrantfile 來修改。

啟動虛機

我們等會再來細看這個文件,現在直接按照提示執行 vagrant up

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'centos-7'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: demo_default_1588406874156_65036
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key

正常的情況下,不到一分鐘應該就能啟動成功了。

這裡我遇到點問題,2222 端口轉發出現未知錯誤,造成 vagrant 的啟動超時。檢查了這個端口並沒被佔用,同時更改大一點的端口可以轉發成功,所以應該是系統哪裡有點問題。在後面我介紹了如何處理,如果你也遇到和我一樣的情況,可先跳到下面查看。

注意到這裡包含的信息:

  • 虛機名稱:demo_default_1588406874156_65036(想改?最後有提)
  • 網卡:Adapter 1: nat,第一塊網卡,NAT 模式,這是固定的
  • 端口轉發:22 (guest) => 2222 (host) (adapter 1),把虛機的 22 端口,映射到宿主機的 2222 端口上,這樣就可以通過 127.0.0.1:2222 訪問虛擬機了
  • SSH 用戶名:vagrant,這裡使用 private key 登錄

密碼也是 vagrant,但是密碼方式僅供直接登錄,是不能通過 SSH 登錄的。

查看虛機狀態

執行下面的命令可以查看虛機的狀態:

vagrant status

Current machine states:

default                   running (virtualbox)

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.

該命令還提示了如何操作虛機,我們繼續一一介紹

連接虛機

如果啟動沒問題,接下來執行 vagrant ssh 就能以 vagrant 用戶直接登入虛機中。

root 用戶沒有默認密碼,也不能直接登錄。需要 root 權限的命令可以通過在命令前添加 sudo 來執行,也可以執行 sudo -i 直接切換到 root 用戶。

這時候打開 VirtualBox 程序,可以看到自動創建的虛機:

Vagrant 創建的虛機
Vagrant 創建的虛機

我們也可以在 VirtualBox 的終端上登錄系統,默認的登錄用戶名和密碼都是 vagrant

當然還可以使用其它的 SSH 連接工具例如 XShell,SecureCRT 連接,但是這裡默認網卡使用的是 NAT 模式,沒有指定 IP,實際應用並不方便,我們在後面介紹網絡配置時再詳細介紹如何連接虛機。

停止虛機

執行下面的命令可以關閉虛機:

vagrant halt

直接在 VirtualBox 上關閉虛機,或者直接在虛機內部執行 poweroff 命令也都是可以的。

暫停虛機

執行下面的命令可以暫停虛機:

vagrant suspend

恢復虛機

執行下面的命令把暫停狀態的虛機恢復運行:

vagrant resume

注意: 不管虛機是關閉還是暫停狀態,甚至是 error 狀態,都可以執行 vagrant up 來讓虛機恢復運行。

重載虛機

執行下面的命令會重啟虛機,並且重新加載 Vagrantfile 中的配置信息:

vagrant reload

刪除虛機

最後,執行下面的命令可以徹底刪除虛機,包括整個虛機文件:

vagrant destroy

注意: 在當前這個小例子中,上面所有的 vagrant 命令都需要在 Vagrantfile 所在的目錄下執行。

初識 Vagrantfile

先來認識一下默認的 Vagrantfile 文件,使用帶語法高亮的文本編輯器(例如 VSCode) 打開:

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # //docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at //vagrantcloud.com/search.
  config.vm.box = "centos-7"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Enable provisioning with a shell script. Additional provisioners such as
  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
end

這是一個 Ruby 語法的文件,因為 Vagrant 就是用 Ruby 編寫的。如果編輯器沒有語法高亮可以手動設置文件類型為 Ruby。

這個缺省文件內容幾乎都是注釋,提示有哪些配置項可以修改,我們不需要去學 Ruby 編程也可以照葫蘆畫瓢的完成基本的配置。

當然,如果會 Ruby 編程的可以在此實現更高級的作用,但是絕大多數人用不着。

刨除注釋,這個文件的實際生效內容實際只有 3 行:

Vagrant.configure("2") do |config|
  config.vm.box = "centos-7"
end

首尾兩行組成一個代碼塊結構,不要去動它,除非你知道自己在幹什麼。我們平常只需要編輯這其中的配置項。

這裡的 config.vm.box 對應的就是虛機的鏡像,也就是 box 文件,這是唯一必填的配置項。

特別提醒,Vagrantfile 文件名是固定的寫法,大小寫也要完全一樣,修改了就不認識了。

自定義配置 Vagrantfile

下面我將針對這份默認的 Vagrantfile 內容,逐個講解其中的配置含義和如何根據實際情況修改。

配置端口轉發

端口轉發(Port forward)又叫端口映射,就是把虛機的某個端口,映射到宿主機的端口上。這樣就能在宿主機上訪問到虛擬機中的服務。

例如啟動虛機時,默認的 22 (guest) => 2222 (host) (adapter 1) 就是把虛機的 SSH 服務端口(22)映射到宿主機的 2222 端口,這樣直接在宿主機通過 ssh 客戶端訪問 127.0.0.1:2222 端口就等價於訪問虛擬機的 22 端口。

下面這兩段配置就是教我們如何配置額外的端口轉發規則,例如把 Web 服務也映射出來:

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

實際上設置端口轉發這個功能並不實用,一個很明顯的問題就是如果啟動多個虛機,很容易就出現宿主機上端口衝突的問題。即使沒有端口衝突,使用起來也不方便,我個人不推薦使用的,可以把這部分配置直接刪掉。直接使用下面的私有網絡。

這個功能是虛擬機軟件提供的,可以在虛機的網卡設置中展開高級選項,找到相關的配置:

還有個地方需要注意,默認的 SSH 端口映射在這裡沒法直接修改。比如像我這樣,2222 端口出現莫名問題,如果想要把 22 端口轉發到其它端口如 22222,直接添加下面這樣的配置是沒用的:

  config.vm.network "forwarded_port", guest: 22, host: 22222

它會在原來的基礎上新加一個端口轉發規則,而不是替代原來的,必須要先強制關閉掉默認的那條規則:

  config.vm.network "forwarded_port", guest: 22, host: 2222, id: "ssh", disabled: "true"
  config.vm.network "forwarded_port", guest: 22, host: 22222

配置私有網絡

下面這段配置用來配置私有網絡,實際上對應的是 VirtualBox 的主機網絡,也就是 HostOnly 網絡。

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

取消注釋最下面一行,就可以為虛機設置指定的私有網絡地址:

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

如果這個網段的主機網絡在 VirtualBox 中不存在,Vagrant 會在啟動虛機時自動創建。所以,如果你想要利用已有的網絡,請查看現有主機網絡配置:

查看主機網絡
查看主機網絡

最好這個網絡也不要啟用 DHCP,完全由自己來分配地址,這樣更加清楚。

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

修改完成後,執行 vagrant reload 命令重建虛機,就能看到多出來的網卡了。

私有網絡實際也可以直接使用 DHCP,但是並不推薦:

  config.vm.network "private_network", type: "dhcp"

配置公共網絡

下面這條配置用來配置公共網絡:

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

正如注釋所說,這裡通常對應的就是橋接網絡。實際開發場景下,我們極少會需要把虛機暴露到公共網絡上,這樣既不安全,也沒有必要。

默認所起的第 1 個 NAT 網絡已經保證了虛機可以上互聯網,而私有網絡保證了宿主機和虛機,以及虛機和虛機之間的通信。如果有對外暴露服務的需求,還可以使用端口轉發。我實在想不出什麼情況下是必須要用橋接網絡的。

所以這部分配置可以直接刪除,如確有使用的,可以參考 官方文檔

配置同步文件夾

下面的配置項用來配置同步文件夾:

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

在啟動虛機的時候,我們可以看到這樣的提示:

==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: No guest additions were detected on the base box for this VM! Guest
    default: additions are required for forwarded ports, shared folders, host only
    default: networking, and more. If SSH fails on this machine, please install
    default: the guest additions and repackage the box to continue.
    default:
    default: This is not an error message; everything may continue to work properly,
    default: in which case you may ignore this message.
==> default: Configuring and enabling network interfaces...
==> default: Rsyncing folder: /cygdrive/c/Users/Davy/demo/ => /vagrant

先注意最後一行的提示:Rsyncing folder: /cygdrive/c/Users/Davy/demo/ => /vagrant

  • /cygdrive/c/Users/Davy/demo/ 這是宿主機的本地目錄,也就是 Vagrantfile 所在的目錄。
  • /vagrant 是虛擬機內部的路徑
  • Rsyncing 表示同步的方式是 Rsync

宿主機目錄中出現 /cygdrive 是因為 Vagrant 程序用到了 Cygwin,它是在 Windows 系統中兼容 Linux/POSIX 的模擬層。可以把 /cygdrive 看成是虛擬的根目錄。

這是 Vagrant 默認的同步文件夾設置,別忘了 Vagrant 的作用是用來搭建開發環境的。所以它假定了當前目錄是我們的開發項目所在目錄,自動把本地的項目目錄同步到虛機中,就可以快速的開始開發調試工作了。

  1. demo 目錄下創建一些文件,例如 hello.py
  2. 執行 vagrant reload,重啟虛機
  3. 在虛機啟動完成後登錄到虛機內,操作如下:
$ vagrant ssh
Last login: Sat May  2 16:25:00 2020 from 10.0.2.2
[vagrant@localhost ~]$ cd /vagrant/
[vagrant@localhost vagrant]$ ls
hello.py  Vagrantfile
[vagrant@localhost vagrant]$ python hello.py
helloworld

這種同步方式在大多數情況下都能提供便利,不過也有不足之處:

  • 同步是一次性的,即只有啟動虛機的時候執行,也就是說改了代碼必須要重啟一次虛機
  • 單向的,即只能從宿主機同步到虛擬機,也就是說在虛機內的改動不會同步到外面
  • 需要拷貝文件,如果要同步的文件數量較多,會佔用更多的磁盤空間

讓我們按照默認配置的提示來新加一條同步文件夾配置試試:

config.vm.synced_folder "../data", "/vagrant_data"

注意,別忘了先在宿主機上創建 data 文件夾,重啟虛機可能看到下面的錯誤提示:

==> default: Rsyncing folder: /cygdrive/c/Users/Davy/demo/ => /vagrant
==> default: Mounting shared folders...
    default: /vagrant_data => C:/Users/Davy/data
Vagrant was unable to mount VirtualBox shared folders. This is usually
because the filesystem "vboxsf" is not available. This filesystem is
made available via the VirtualBox Guest Additions and kernel module.
Please verify that these guest additions are properly installed in the
guest. This is not a bug in Vagrant and is usually caused by a faulty
Vagrant box. For context, the command attempted was:

mount -t vboxsf -o uid=1000,gid=1000 vagrant_data /vagrant_data

The error output from the command was:

mount: unknown filesystem type 'vboxsf'

這是因為 Vagrant 提供了多種同步方式,在使用 VirtualBox 的時候,缺省同步類型是 vboxsf 掛載文件系統,它需要在虛擬機內部安裝客戶機增強包,也就是 VirtualBox Guest Additions(輸出信息中也提示了)。

如何在虛機系統中安裝 guest additions 要分操作系統而定,有點小坑,後面會細說,現在修改一下配置,明確指定同步類型是 rsync

config.vm.synced_folder "../data", "/vagrant_data", type: "rsync"

這樣表示仍然使用 rsync 來單向同步。

更改虛機規格

VirtualBox 等虛擬機軟件在 Vagrant 中被稱為 Provider,虛機的規格等配置是和 Provider 相關的。因為 VirtualBox 用的最多,所以默認的配置提示是以 VirtualBox 舉例。

如果想要了解其它 Provider 的配置,請參考 文檔

把中間那一段取消注釋,其它的可以刪掉:

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

使用 VSCode 時,選中它們,然後按下快捷鍵 Ctrl + / 即可:

  config.vm.provider "virtualbox" do |vb|
    # Display the VirtualBox GUI when booting the machine
    vb.gui = true

    # Customize the amount of memory on the VM:
    vb.memory = "1024"
  end

vb.gui = true 是在虛機啟動時自動打開 VirtualBox 的圖形界面,這對服務器來說沒什麼用,直接刪掉。

添加 CPU 的配置,同時修改內存大小:

  config.vm.provider "virtualbox" do |vb|
    vb.cpus = 2
    vb.memory = 2048
  end

注意到,內存的大小單位是 MB,值是數字,默認的示例中有引號,實際也可以不加。

特別提醒一下,在 說明文檔 里給的例子,其中的變量名是 v,這其實是在雙豎線中定義的,直接拷貝的時候要看清楚。

config.vm.provider "virtualbox" do |v|
  v.memory = 1024
  v.cpus = 2
end

Provision

Provision 是指在虛機初次創建的時候,Vagrant 自動去執行的構造任務,比如安裝軟件,更新系統配置等。

因為 box 往往只提供基礎的系統(雖然我們可以自定義 box,但是並不是每次都要這麼做,而且這樣做會喪失一部分靈活性),有些東西仍然需要在創建虛機的時候完成。

  # Enable provisioning with a shell script. Additional provisioners such as
  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL

因為這部分完全是個開放的內容,所以我們這裡不過多討論,來看一下什麼情況下會觸發 provision 的操作:

  • 某個環境初次執行 vagrant up 的時候
  • 執行 vagrant provision 命令
  • 重啟的時候 vagrant reload --provision,帶上 --provision 選項

除了上面這些默認提示給出的配置項,Vagrantfile 還支持其它很多配置,具體請 查看文檔

由於 Vagrantfile 本身是 Ruby 腳本,所以它並不僅僅是靜態的配置文件,而且可以包含程序邏輯,例如在 如何創建多個虛機 中就有應用,有興趣的可以自行研究。

定製帶客戶機增強的 box

下載 Guest Addition

VirtualBox 的下載頁面並沒有直接給出 Guest Addtion 的下載鏈接,我們先在 VirtualBox 的任一下載鏈接上右鍵,複製鏈接地址,例如得到 //download.virtualbox.org/virtualbox/6.1.6/VirtualBox-6.1.6-137129-Win.exe,去掉最後的文件名,把其中的路徑 //download.virtualbox.org/virtualbox/6.1.6/ 在瀏覽器中打開,就能看到所有可下載的版本,在其中找到 VBoxGuestAdditions_6.1.6.iso 直接下載。

下載客戶機增強
下載客戶機增強

安裝 Guest Addition

重新使用 Vagrant 從原始的鏡像啟動一個乾淨的虛機。

VBoxGuestAdditions_6.1.6.iso 需要以光盤的形式掛載到虛機上,但是默認啟動的這個虛機是沒有光驅的。添加虛擬光驅需要先將虛機關閉。

然後在 VirtualBox 界面上操作,打開 設置,選擇 存儲,點擊 添加虛擬光驅,點擊 控制器: IDE,選擇 VBoxGuestAdditions_6.1.6.iso,點擊 OK

直接在 VirtualBox 上啟動虛機,如果你在虛機菜單上選擇 設備安裝增強功能,大概率是會遇到下面這樣的錯誤,別管它,我們手動來安裝。

VirtualBoxVM_安裝增強
VirtualBoxVM_安裝增強
VirtualBoxVM_安裝增強4
VirtualBoxVM_安裝增強4

使用 vagrant/vagrant 登錄到虛機內,切換到 root 用戶,查看虛擬光盤是否已經掛載上來了:

$ sudo -i
# lsblk
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda      8:0    0  40G  0 disk
└─sda1   8:1    0  40G  0 part /
sr0     11:0    1  57M  0 rom

下面的 sr0 就是光盤設備,要把它掛載到文件系統中

# mount /dev/sr0 /mnt/
mount: /dev/sr0 is write-protected, mounting read-only
# cd /mnt/
# ls
AUTORUN.INF  cert  OS2           TRANS.TBL                VBoxDarwinAdditionsUninstall.tool  VBoxSolarisAdditions.pkg        VBoxWindowsAdditions.exe
autorun.sh   NT3x  runasroot.sh  VBoxDarwinAdditions.pkg  VBoxLinuxAdditions.run             VBoxWindowsAdditions-amd64.exe  VBoxWindowsAdditions-x86.exe

在 Linux 系統中要運行 VBoxLinuxAdditions.run,這時候直接運行仍然會報錯:

VirtualBoxVM_安裝增強4
VirtualBoxVM_安裝增強4

大致意思是要它需要內核的頭文件來構建。

頭文件通過安裝 kernel-devel 就可以了,但是直接安裝的版本是最新版本的,可能會當前的內核版本不一致,仍然是沒用的。例如:

[root@localhost mnt]# uname -r
3.10.0-1062.12.1.el7.x86_64
[root@localhost mnt]# yum info kernel-devel
Installed Packages
Name        : kernel-devel
Arch        : x86_64
Version     : 3.10.0
Release     : 1127.el7
Size        : 38 M
Repo        : installed
From repo   : base

這裡 kernel-develRelease 版本號和系統內核版本不一致,所以仍然不行。

需要先更新一把,然後再安裝(也可以先安裝再更新):

# yum update -y
# yum install -y gcc kernel-devel

這裡選擇的是更新到最新版本,如果你的開發環境需要特定的內核版本,你也可以根據情況安裝指定的版本,只要保證內核版本和頭文件版本完全匹配

Ubuntu 系統的安裝方式有所不同,可以參考 Vagrant 的文檔

升級內核後需要重啟虛機,然後再次嘗試安裝 Additions:

[root@localhost mnt]# ./VBoxLinuxAdditions.run
Verifying archive integrity... All good.
Uncompressing VirtualBox 6.1.6 Guest Additions for Linux........
VirtualBox Guest Additions installer
Removing installed version 6.1.6 of VirtualBox Guest Additions...
Copying additional installer modules ...
Installing additional modules ...
VirtualBox Guest Additions: Starting.
VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel
modules.  This may take a while.
VirtualBox Guest Additions: To build modules for other installed kernels, run
VirtualBox Guest Additions:   /sbin/rcvboxadd quicksetup <version>
VirtualBox Guest Additions: or
VirtualBox Guest Additions:   /sbin/rcvboxadd quicksetup all
VirtualBox Guest Additions: Building the modules for kernel
3.10.0-1127.el7.x86_64.
VirtualBox Guest Additions: Running kernel modules will not be replaced until
the system is restarted

# 檢查模塊是否已經加載了
[root@localhost mnt]# lsmod |grep vbox
vboxvideo              35867  1
ttm                    96673  1 vboxvideo
drm_kms_helper        186531  1 vboxvideo
drm                   456166  4 ttm,drm_kms_helper,vboxvideo
vboxguest             349038  1
[root@localhost mnt]#

能夠看到 vboxguest 就代表安裝成功了。

測試

在 Vagrantfile 中添加同步文件夾設置,這次不再指定同步類型

config.vm.synced_folder "../data", "/vagrant_data"

然後執行 vagrant reload

$ vagrant reload
...
==> default: Checking for guest additions in VM...
==> default: Rsyncing folder: /cygdrive/c/Users/Davy/demo/ => /vagrant
==> default: Mounting shared folders...
    default: /vagrant_data => C:/Users/Davy/data
==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> default: flag to force provisioning. Provisioners marked to run always will still run.

可以看到關於 additions 的提示信息沒有了,新的同步文件夾也能正常同步了,這次的同步是雙向的。我們可以先去同步文件夾隨便創建一個文件:

$ vagrant ssh
Last login: Tue May  5 12:16:39 2020 from 10.0.2.2
[vagrant@localhost ~]$ cd /vagrant_data/
[vagrant@localhost vagrant_data]$ ls
data.txt
[vagrant@localhost vagrant_data]$ touch vm.txt   # 新建一個文件
[vagrant@localhost vagrant_data]$ ls
data.txt  vm.txt

回到 Windows 宿主機上,data 文件夾下面也能看到 vm.txt 文件,

Davy@Davy-Desktop MINGW64 ~/data
$ ls
data.txt  vm.txt

這樣,我們的 Guest Addition 就安裝成功了。

清理磁盤

接下來我們來把這個好不容易才裝上了增強包的虛機保存為新的鏡像。

這裡你可能還想安裝點其它軟件,但是作為基礎鏡像,最好還是保持乾淨一點,不宜安裝太多東西。後續可以在這個基礎之上,再次構建其它特定的鏡像。

為了使做出來的鏡像文件大小緊湊點,我們把剛才安裝過程中的緩存也刪掉:

yum clean all

我在打包過程中還遇到過 swapfile 佔用磁盤空間的問題,導致鏡像文件過大,可以這樣檢查:

du /swapfile   # 查看是否有佔用
swapoff -a     # 關閉 SWAP
rm -f /swapfile

使用 df -h 命令查看磁盤佔用情況,像我這次操作,磁盤根目錄不到 2GB 的空間佔用:

# df -h
/dev/sda1        40G  1.5G   39G   4% /

打包為 box

執行 vagrant package 就可以把當前環境打包生成新的 box 文件:

$ vagrant  package
    ==> default: Attempting graceful shutdown of VM...
==> default: Clearing any previously set forwarded ports...
==> default: Exporting VM...
==> default: Compressing package to: C:/Users/Davy/demo/package.box

生成的文件就在 Dockerfile 所在的目錄,文件名默認是 package.box。大小不到 600MB。

注意:,因為默認啟動虛機時本地目錄會 rsync 到虛機中,package.box 文件也會同步(拷貝)到虛機中,佔用虛機磁盤。這時候如果二次執行打包,生成的文件大小會翻倍。

添加到 vagrant 中

繼續使用下面的命令把新建的 box 添加到 vagrant 中:

$ vagrant box add package.box --name davy/centos-7-base

為了區分這是個人創建的基礎鏡像,加了個人用戶名作為前綴,同時加了 base 後綴。

添加成功後,本地的 package.box 就可以刪除了。

後續再創建虛機,使用下面的命令就可以了:

$ vagrant init davy/centos-7-base

使用 SSH 客戶端

vagrant ssh 命令雖然很方便,但是在 Windows 環境下,因為默認的命令行終端不太好用,所以往往還需要使用更專業的 SSH 客戶端例如 XShell 或 SecureCRT。

默認的鏡像只支持 private_key 的方式登錄,vagrant/vagrant 可以在 VirtualBox 上登錄系統,但是如果用來登錄 SSH,會被拒絕。

當然你可以在製作鏡像的時候修改 ssh 服務的配置,讓它能夠用密碼登錄,但是實際上用密鑰更加方便。

先使用 vagrant ssh-config 命令可以看到 SSH 的配置:

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 22222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile E:/VirtualBox/.vagrant.d/boxes/davy-VAGRANTSLASH-centos-7-base/0/virtualbox/vagrant_private_key
  IdentitiesOnly yes
  LogLevel FATAL

可以看到其中的 IdentityFile 就是私鑰文件。

發現這個自定義 box 啟動的虛機的密鑰文件是固定在 VAGRANT_HOME 下的相關目錄下。那麼就好辦了,直接在 SSH 客戶端軟件上導入這個私鑰文件就可以了。

以 SecureCRT 為例:

SecureCRT_密鑰
SecureCRT_密鑰

使用模板文件

vagrant init 命令只是用來生成 Vagrantfile 文件,但是默認的配置選項每次都要修改也很麻煩。該命令提供了 --template 選項,可以指定一個模板文件,我們可以在自定義自己的模板文件。

這個模板文件的格式 ERBRuby 的模板語法,如果有 Ruby on Rails 開發經驗的可能會比較熟悉。但是我們不用去學習這些細節。

可以從 這裡 拷貝一份原文件,還能在 Vagrant 的安裝位置 Vagrant\embedded\gems\2.2.7\gems\vagrant-2.2.7\templates\commands\init 底下找到,並且有一個 Vagrantfile.min.erb 是去掉所有注釋的:

Vagrant.configure("2") do |config|
  config.vm.box = "<%= box_name %>"
  <% if box_version -%>
  config.vm.box_version = "<%= box_version %>"
  <% end -%>
  <% if box_url -%>
  config.vm.box_url = "<%= box_url %>"
  <% end -%>
end

可以看到其中是怎麼配置 config.vm.box 的。 像 <% 這樣的語法有興趣可以自己去了解,這裡我們只要把自己想要的配置項原樣寫上去就行了。

下面是我按照自己的需要寫的:

Vagrant.configure("2") do |config|
  config.vm.box = "<%= box_name %>"
  config.vm.network "forwarded_port", guest: 22, host: 2222, id: "ssh", disabled: "true"
  config.vm.network "forwarded_port", guest: 22, host: 22222

  # config.vm.network "private_network", ip: "192.168.56.10"
  # config.vm.synced_folder "../data", "/data"

  config.vm.provider "virtualbox" do |vb|
    # vb.name = "give me a better name"
    vb.memory = "1024"
  end
end

不要有中文,不然會遇到編碼的麻煩。

其中像私有網絡和同步文件夾配置,幾乎每次基本都要,但是又不好固定,所以仍然以注釋的形式保留,每次稍微改一下也很方便。

把這個文件找個目錄,保存為 vagrant.erb

接着在使用 vagrant init 的時候通過 --tempate 指定它就可以了,例如:

vagrant init davy/centos-7-base --tempate "C:\Users\Davy\vagrant.erb"

顯然,每次要記住並且輸入這個模板文件也很麻煩的。可以通過設置環境變量 VAGRANT_DEFAULT_TEMPLATE 來一勞永逸地解決這個問題。

尾聲

費了莫大的力氣,終於可以比較愉快地玩耍了。雖然也只是剛把基礎鏡像搞定了,後面可能還要針對不同用途的環境編寫更加複雜的 Vagrantfile。

現在很多人剛認識到 Vagrant 之後都會問,Vagrant 和 Docker 的區別是什麼?

在容器流行之前,Vagrant 就是用來編排虛機和自動部署開發環境的,有了 Docker/Kubernetes 之後,直接用容器來編排應用確實更香。但是還有一些工作,例如容器平台自身的安裝,多節點集群的部署測試等,更方便用虛機解決。

此外,現在 Windows 中還可以通過 WSL 使用 Linux 系統,但是使用場景上還是有所不同。Vagrant 更多地用於快速搭建可重用的開發環境,從這個角度看,Vagrant 其實好比 IaaS 雲平台,只不過規模局限在個人電腦上。


感謝您的閱讀,請繼續關注「雲計算實驗室」。

本文使用 mdnice 排版