使用 Vagrant 管理虚拟机

In 1974 computers were oppressive devices in far-off air-conditioned places. Now you can be oppressed in your own living room.

— Ted Nelson

虽然能够在云中部署虚拟机是一个创举,但若能将虚拟机运行在你自己的桌面系统中有时甚至是更方便的, 尤其对于测试来说更是如此。如果每个开发者都有一个克隆自生产系统的运行在自己机器上的虚拟机, 那么实际部署时就不太可能遇到问题。 同样地,每个系统管理员也可以在私人的虚拟机上测试配置管理的变化, 这是一种使错误配置实际影响客户之前捕捉错误的良好方式。

几年前出现的工具(如 VirtualBox 或 VMware)就已经能在桌面系统上创建虚拟机。 然而,Vagrant 的到来使桌面云技术真正实现了起飞,它是一个管理和供应虚拟化环境的自动化工具。 Vagrant 是 VirtualBox 的前端工具,它驱动 VirtualBox 实现自动创建虚拟机的过程, 并使用自动化管理配置工具(Chef 或 Puppet)为虚拟机调配所需的资源, 设置网络,端口转发,以及对运行着的虚拟机打包生成映像文件以便其他人使用。

你可以使用 Vagrant 管理你用于开发目的虚拟机,这些虚拟机既可以运行在你自己的桌面上, 也可以运行在一台共享的机器上,比如一台持续集成服务器(Continuous Integration Server)。 例如,你可以使用像 Jenkins 那样的 CI 工具启动一个由 Vagrant 管理的虚拟机, 部署你的应用程序,然后在虚拟机里运行测试实验,就好像是在生产环境中一样。

操作步骤

  1. 创建一个 vagrant 模块:

    # mkdir /etc/puppet/modules/vagrant
    # mkdir /etc/puppet/modules/vagrant/manifests
    # mkdir /etc/puppet/modules/vagrant/files
    
  2. 使用如下内容创建 /etc/puppet/modules/vagrant/manifests/init.pp 文件:

    class vagrant {
        $virtualbox_deps = [ "libgl1-mesa-glx",
                             "libqt4-network",
                             "libqt4-opengl",
                             "libqtcore4",
                             "libqtgui4",
                             "libsdl1.2debian",
                             "libxmu6",
                             "libxt6",
                             "gawk",
                             "linux-headers-${kernelrelease}" ]
            package { $virtualbox_deps: ensure => installed }
    
            exec { "download-virtualbox":
                cwd     => "/root",
                command => "/usr/bin/wget http://download.virtualbox.org/
     virtualbox/4.1.0/virtualbox-4.1_4.1.0-73009~Ubuntu~lucid_
     i386.deb",
                creates => "/root/virtualbox-4.1_4.1.0-73009~Ubuntu~lucid_
     i386.deb",
                timeout => "-1",
            }
    
            exec { "install-virtualbox":
                command => "/usr/bin/dpkg -i /root/virtualbox-4.1_4.1.0-
     73009~Ubuntu~lucid_i386.deb",
                unless  => "/usr/bin/dpkg -l |/bin/grep virtualbox-4.1",
                require => [ Exec["download-virtualbox"],
                Package[$virtualbox_deps] ],
            }
    
            $vagrant_deps = [ "build-essential",
                              "rubygems" ]
    
            package { $vagrant_deps: ensure => installed }
    
            exec { "install-rubygems-update":
                command => "/usr/bin/gem install -v 1.8.6 rubygemsupdate",
                unless  => "/usr/bin/gem -v |/bin/grep 1.8.6",
                require => Package["rubygems"],
            }
    
            exec { "run-rubygems-update":
                command => "/var/lib/gems/1.8/bin/update_rubygems",
                unless  => "/usr/bin/gem -v |/bin/grep 1.8.6",
                require => Exec["install-rubygems-update"],
            }
    
            package { "vagrant":
                provider => gem,
                ensure   => installed,
                require  => [ Package["build-essential"],
                              Exec["runrubygems-update"] ],
            }
    
            define devbox( $vm_user ) {
                include vagrant
                $vm_dir = "/home/${vm_user}/${name}"
                file { [ $vm_dir,
                         "${vm_dir}/data" ]:
                    ensure => directory,
                    owner  => $vm_user,
                }
    
                file { "${vm_dir}/Vagrantfile":
                    source => "puppet:///modules/vagrant/devbox.
     Vagrantfile",
                    require => File[$vm_dir],
                }
         }
    }
    
  3. 使用如下内容创建 /etc/puppet/modules/vagrant/files/devbox.Vagrantfile 文件:

    Vagrant::Config.run do |config|
      config.vm.box = "lucid32"
      config.vm.box_url = "http://files.vagrantup.com/lucid32.box"
      config.vm.forward_port "http", 80, 8080
      config.vm.share_folder "v-data", "/vagrant_data", "./data"
    
      config.vm.customize do |vm|
        vm.name = "devbox"
      end
    
      config.vm.provision :puppet,:module_path => "puppet/modules-0"
      do |puppet|
        puppet.manifests_path = "puppet/manifests"
        puppet.manifest_file = "site.pp"
      end
    end
    
  4. 在一个你想要运行虚拟机的节点上包含如下代码(将 john 替换为你自己的用户名):

    vagrant::devbox { "devbox":
        vm_user => "john",
    }
    
  5. 添加一个名为 devbox 的节点:

    node devbox {
        group { "puppet": ensure => present }
        file { "/etc/motd":
            content => "Puppet power!\n",
        }
    }
    
  6. 运行 Puppet:

    # puppet agent --test
    
  7. 你应该在要运行虚拟机的宿主机上找到用户 john 自家目录下已创建的 devbox 目录。 在此目录中需要拥有一套 Puppet 配置清单的子目录(名为 puppet), 既可以从你的 Puppet 仓库检出到名为 puppet 的目录,也可以创建一个名为 puppet 的 符号链接symlink)指向宿主机上已存在的 Puppet 配置清单目录:

    # cd ~/devbox
    # git clone [email protected]:Example/Puppet.git puppet
    

    或者

    # ln -s /etc/puppet ~/devbox/puppet
    
  8. 在 devbox 目录中,运行如下命令行:

    # vagrant up
    [default] Box lucid32 was not found. Fetching box from specified
    URL...
    [default] Downloading with Vagrant::Downloaders::HTTP...
    [default] Downloading box: http://files.vagrantup.com/lucid32.box
    [default] Extracting box...
    [default] Verifying box...
    [default] Cleaning up downloaded box...
    [default] Importing base box 'lucid32'...
    [default] Matching MAC address for NAT networking...
    [default] Clearing any previously set forwarded ports...
    [default] Forwarding ports...
    [default] -- http: 80 => 8080 (adapter 1)
    [default] -- ssh: 22 => 2222 (adapter 1)
    [default] Creating shared folders metadata...
    [default] Running any VM customizations...
    [default] Booting VM...
    [default] Waiting for VM to boot. This can take a few minutes.
    [default] VM booted and ready for use!
    [default] Mounting shared folders...
    [default] -- v-root: /vagrant
    [default] -- v-data: /vagrant_data
    [default] -- manifests: /tmp/vagrant-puppet/manifests
    [default] Running provisioner: Vagrant::Provisioners::Puppet...
    [default] Running Puppet with site.pp...
    [default] stdin: is not a tty
    [default] notice: /Stage[main]//Node[devbox]/File[/etc/motd]/
    ensure: defined content as '{md5}0bdeca690dbb409d48391f3772d389b7'
    [default]
    [default] notice: /Group[puppet]/ensure: created
    [default]
    [default] notice: Finished catalog run in 0.36 seconds
    [default]
    

    登录到 devbox 虚拟主机进行测试:

    # vagrant ssh
    Puppet power!
    Last login: Thu Jul 21 13:07:53 2011 from 10.0.2.2
    vagrant@devbox:~$ logout
    Connection to 127.0.0.1 closed.
    

工作原理

vagrant 类安装 Vagrant 和 VirtualBox 以及所有的依赖。它同时还定义了名为 devbox 的 define,你可以使用它为一台宿主机的多个用户创建 devbox 的多个实例。 devbox 的一个实例如下:

vagrant::devbox { "app-foo-devbox":
    vm_user => "john",
}

此实例在用户(本例为 john)的家目录下创建一个名为 app-foo-devbox 的 Vagrant 项目目录 (此目录包含一个配置文件 Vagrantfile,它指定了一个虚拟机的配置定义)。

当 Vagrant 首次启动虚拟机,它会在项目目录的名为 puppet 的子目录中查找提供给本虚拟机的 Puppet 配置清单。 这可以是你当前 Puppet 工作副本的一个符号链接, 也可以是仅为 devbox 编制的独立的 Puppet 配置清单 (无论你使用哪一种方式,只要 Vagrant 能找到即可)。

一旦虚拟主机已经配置好,它就可以投入使用了。运行 vagrant up 命令即可启动虚拟机; vagrant ssh 命令用于登录虚拟机;vagrant halt 命令用于停止虚拟机的运行。

顺便指出,节点定义中名为 puppet 的 group 资源在 Vagrant 的 Puppet 供应时会引发一个错误, 当你看到本书时可能已经被修复。Vagrant 正处于开发活跃期,所以可能会有一两处无法正常工作: 如有疑问,请查看本节最后的文档链接。

你可能会发现有时虚拟机无法完全启动,Vagrant 只是处于超时等待状态。 这似乎也是由于 Vagrant 的一个错误引起的,当你看到本书时可能已经被修复。如果还没有修复, 你可以在 Vagrantfile 中通过添加如下的代码片段来解决这个问题:

config.vm.boot_mode = :gui

修改之后重新启动虚拟机。现在虚拟机在 GUI 模式下启动,同时运行了一个控制台窗口。 在此窗口中,以用户名 vagrant(口令为 vagrant)登录,然后运行如下命令:

# sudo /etc/init.d/networking restart

现在你发现 Vagrant 会完成配置阶段并且 vagrant ssh 命令也会工作正常。

更多用法

在本例中,我们仅对 devbox 配置了一个极其简单的配置清单,它在 /etc/motd 文件中添加了消息。 为了使其更实用,可以让 devbox 提取与你要部署的实际服务器相同的配置清单。例如:

node production, devbox {
    include myapp::production
}

因此,应用到生产服务器配置的任何改变将同时反映在你用于测试的机器上, 这样就可以在实际部署之前先解决出现的问题,如果你需要进行配置的变化以支持新的功能, 可以首先在虚拟机上测试它,看看是否有什么不正常。

如果你不再使用虚拟机,想要挂起或关闭它,只要运行:

# vagrant suspend

# vagrant halt

想要完全删除虚拟机,例如你要重新测试供应,运行:

# vagrant destroy

Vagrant 的维护者为了使其使用简单做了相当多的工作,如果你需要阅读更多关于 Vagrant 的内容请访问其文档站点: http://vagrantup.com/docs/index.html