本地覆盖Vagrant配置设置(每个开发者)

58

我希望能够得到一般性的答案,但为了举例,这里有一个用例:

我正在使用 Vagrant 进行简单的 LMAP 项目,使用独立的 Puppet 进行配置。现在,有些开发人员可能会坐在代理后面,他们需要对 VM 进行一些额外的配置。对于 Puppet 配置方面,我已经解决了问题:我可以将代理 IP(如果有)作为事实传递给 Puppet,在 Vagrantfile 中进行相应的设置。

我唯一遇到的问题是:如何让开发人员在不更改 Vagrantfile 的情况下指定/覆盖其开发环境中的某些设置(Vagrantfile 受版本控制,必须保持开发环境中立)?

如果人们可以在名为 e.g. Vagrantfile.local 的文件中覆盖一些 Vagrant 设置,那将是很棒的,我将通过 .gitignore 排除它。

Vagrantfile 只是 Ruby 文件,因此我尝试了以下内容:

# Also load per-dev custom vagrant config
custom_vagrantfile = 'Vagrantfile.local'
load custom_vagrantfile if File.exist?(custom_vagrantfile)

文件包含基本上可以工作,但是看起来在包含的文件中,我不再处于相同的Vagrant上下文中了...

Vagrant::Config.run do |config|
  config.vm.provision :puppet do |puppet|
    puppet.facter = { "proxy" => "proxy.host:80" }
  end
end

...还重置了我在主Vagrantfile中设置的所有其他木偶配置值,这让我觉得我在这方面走错了方向。需要注意的是,我对Ruby完全不熟悉 ;)

有没有人能够给我一些提示或者通用的解决方案,来实现开发者自定义呢?

8个回答

91

Vagrantfile 就是 Ruby,所以 YAML 是另一种选择。

例如,在 Vagrantfile 中我这样做:

# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'yaml'

settings = YAML.load_file 'vagrant.yml'
db_ip_address = settings['db']['ip_address']
api_ip_address = settings['api']['ip_address']

Vagrant.configure("2") do |config|
  config.vm.box = "ffuenf/ubuntu-13.10-server-amd64"
  config.vm.box_url = "https://vagrantcloud.com/ffuenf/ubuntu-13.10-server-amd64/version/4/provider/virtualbox.box"

  config.vm.define "db" do |db|
    db.vm.synced_folder settings['db']['artifacts_dir']['host'], settings['db']['artifacts_dir']['guest']
    db.vm.network "private_network", ip: db_ip_address
    ... other stuff ...
  end

  config.vm.define "api" do |api|
    api.vm.synced_folder settings['api']['artifacts_dir']['host'], settings['api']['artifacts_dir']['guest']
    api.vm.network "private_network", ip: api_ip_address
    api.vm.network "forwarded_port", guest: settings['api']['forwarded_port']['guest'], host: settings['api']['forwarded_port']['host']
  end
end

然后我有一个vagrant.yml文件(我只是随便起的名字,你可以用任何你喜欢的名字)来进行开发人员特定的配置:

db:
  ip_address: 192.168.4.14
  artifacts_dir:
    host: /Users/willie/myapp/db-scripts
    guest: /opt/myapp/db

api:
  ip_address: 192.168.4.15
  forwarded_port:
    host: 9080
    guest: 8080
  artifacts_dir:
    host: /Users/willie/myapp/artifacts
    guest: /opt/myapp/api

1
好的,我在其他答案之后几年才添加了这个。 :-) - user41871
3
这应该是 Vagrant 的惯用语法。我会在所有的项目中使用它。 - Jim Mitchener
1
请为此创建一个问题。 - user41871
Ruby并不强制使用YAML而非JSON、XML、properties等。它们都有各自的特点,大多数情况下只是个人偏好的问题。 - user41871
Homestead使用Vagrant,类似但更复杂。它实际上同时使用了YAML和JSON。 - trysis
显示剩余5条评论

28

我建议使用环境变量来动态更改 Vagrantfile 的行为,而无需编辑文件本身。

以下是一个真实世界的例子,演示如何默认使用 Ubuntu 基础镜像,但通过环境变量定义另一种 Linux 发行版:

if ENV['OPERATINGSYSTEM']
  if ENV['OPERATINGSYSTEM'].downcase == 'redhat'
    os_name = 'centos'
    config.vm.box     = 'centos'
    config.vm.box_url = 'https://dl.dropbox.com/u/7225008/Vagrant/CentOS-6.3-x86_64-minimal.box'
  else
    raise(Exception, "undefined operatingsystem: #{ENV['OPERATINGSYSTEM']}")
  end
else
  os_name = 'precise64'
  config.vm.box     = 'precise64'
  config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
end

这个例子来自于 https://github.com/puppetlabs/puppetlabs-openstack_dev_env

3
我一直在使用这种方法,并且很喜欢......除了现在我需要管理多个不同的 AWS 盒子,所有这些盒子都是使用不同的 Vagrant 脚本从同一台机器上预置的......每次设置正确的 ENV 设置都很麻烦。你有没有考虑过除了使用环境变量之外的其他概念?也许读取一个非检入的配置文件,在更高级别的目录中管理所有设置? - JayCrossler

12
如果你准备定义应用于所有vagrant boxes的设置,值得注意的是,“Vagrant实际上加载一系列Vagrantfiles,并在加载时合并这些设置。”(参考https://docs.vagrantup.com/v2/vagrantfile/
因此,我在~/.vagrant.d/Vagrantfile中定义了以下内容,以增加我的Vagrant boxes的RAM数量:
Vagrant.configure(2) do |config|
    config.vm.provider "virtualbox" do |vb|
      vb.memory = 2048
    end
end

如果像答案所述,您可以接受应用于所有vagrant框的设置,那么比操纵环境变量要容易得多。 - Seth
正如Seth和回答者所提到的,如果您在.vagrant.d/Vagrantfile中添加了供应商或其他任何内容,这可能会产生意想不到的后果,因为它们将被应用于通过Vagrant在计算机上任何地方启动的任何VM。最安全的方法是使用另一个答案中提到的yaml解析。 - dragon788

6
这是一个想法。它可能很“丑陋”和“错误”,但至少它能够正常工作 :)
# file2.rb, this is your per-dev configuration file
puts "included external file which uses outer var: #{foo}"

# file1.rb, this would be your Vagrantfile
puts 'first'
foo = 'bar'

external = File.read 'file2.rb'
eval external
puts 'second'

让我们运行它

$ ruby file1.rb
first
included external file which uses outer var: bar
second

适应你的示例,file2.rb 只包含对 config 的使用,而不定义它(config 将从外部上下文提供)。
  config.vm.provision :puppet do |puppet|
    puppet.facter = { "proxy" => "proxy.host:80" }
  end

你的Vagrant文件可能如下所示:

Vagrant::Config.run do |config|
  external = File.read 'Vagrantfile.local'
  eval external

  # proceed with general settings here
  config.vm.provision :puppet do |puppet|
    puppet.facter = { "proxy" => "proxy.host:80" }
  end
end

更新(另一种“数据驱动”方法)

# Vagranfile.local
config_values[:puppet][:facter][:proxy] = 'proxy.host:80'

# Vargantfile
Vagrant::Config.run do |config|
  config_values = {
    puppet: {
      facter: {
        proxy: nil
      },
      manifests_file: 'my_manifest.pp'

    }
  }
  external = File.read 'Vagrantfile.local'
  eval external # this should overwrite proxy config

  # proceed with general settings here
  config.vm.provision :puppet do |puppet|
    if config_values[:puppet][:facter][:proxy]
      puppet.facter = { "proxy" => config_values[:puppet][:facter][:proxy] } 
    end

    puppet.manifests_file = config_values[:puppet][:manifests_file]
  end
end

问题在于似乎多个 config.vm.provision :puppet... 块会互相覆盖(完全)。因此,在主 Vagrantfile 中配置 Vagrant 的 Puppet 块的一部分和在自定义文件中配置另一部分是不可能的。我尝试在该块内使用 eval。当然,那样可以让开发人员设置 Puppet 参数,但这非常具体、丑陋(正如您所说),如果他们想要配置除 Puppet 块之外的更多内容,就会变得重复... - netmikey
您可以使用相同的模式,但是使外部文件提供您稍后在块中使用的值。我更新了答案。这将使您的主Vagrantfile有些混乱,但至少应该是可配置的。 - Sergio Tulentsev
也许可以通过一些动态技巧(例如send等)来优化这个过程。 - Sergio Tulentsev
或者使用环境变量来达到相同的目的呢? - Sergio Tulentsev
是的,我们的其中一位开发人员也有这个想法。如果没有办法以本地方式让第二个文件“交互”主Vagrantfile,我认为使用专用环境变量,虽然不太灵活,但可能是一个可以接受的解决方案。您能否快速举个例子(最好是简短的方式)来表达 some.config.val = 如果设置了环境变量'MYVAR',则使用其值,否则使用默认值'123'if (env_var_set('MYVAR')) { 使用env_var('MYVAR')做某事 }?也许在另一个回复中,这样我就可以验证一下 :) - netmikey

5
我相信这正是创建Nugrant插件的确切用例。它允许您的每个开发人员拥有一个YAML格式的.vagrantuser文件(该文件被.gitignore忽略),指定自定义配置值,然后在Vagrantfile中轻松地引用这些值。
在您的情况下,代理开发人员的.vagrantuser文件如下:
proxy: 'proxy.host:80'

你的Vagrantfile文件应该像这样(伪代码,我不太懂Ruby):

Vagrant::Config.run do |config|
  config.vm.provision :puppet do |puppet|
    if config.user.has_key?('proxy')
      puppet.facter = { "proxy" => config.user.proxy }
    end
  end
end

你应该为开发人员提供一个样本/参考的vagrant用户文件(即vagrantuser.example),以便他们复制并调整到自己的环境中。


谢谢 - 我不知道这个插件。 - Myer

5

扩展@Willie Wheeler的答案。 我的设置是:

Root
|-- defaults.yml
|-- env.yml
|-- Vagrantfile

Vagrantfile

# Load local env config
require 'yaml'
dir = File.dirname(File.expand_path(__FILE__))

# defaults
settings = YAML::load_file("#{dir}/defaults.yml")

if File.exist?("#{dir}/env.yml")
    env_settings = YAML::load_file("#{dir}/env.yml")
    settings.merge!(env_settings)
end
...
# Customize the amount of memory on the VM:
    vb.memory = settings["vb"]["memory"]

defaults.yml

vb:
  memory: 1024

env.yml

vb:
  memory: 204

这将把您的默认设置与每个开发者的配置合并。同时,开发人员可以清楚地知道哪些值是可以更改的。


0
考虑使用vagrant-proxyconf插件。它允许全局为所有Vagrant VM设置代理。
另一个解决方案是在配置期间运行外部shell脚本。我使用单独的config.vm.provision部分在Vagrantfile开头来完成它:
# reset: true below is needed to reset the connection to the VM so that new
# environment variables set in /etc/environment will be picked up in next
# provisioning steps
config.vm.provision "shell", reset: true, inline: <<-SHELL

  if [ -f /vagrant/Vagrantfile-settings.sh ]
  then
    /vagrant/Vagrantfile-settings.sh
  fi
SHELL

然后只需在Vagrantfile旁边放置一个Vagrantfile-settings.sh文件,将其添加到.gitignore(或其他)中,并放置任何脚本,例如为交互式终端、所有守护程序和Docker容器设置代理:

# Proxy for interactive terminals
echo "http_proxy=http://PROXY_ADDRESS:PROXY_PORT" >> /etc/environment
echo "https_proxy=http://PROXY_ADDRESS:PROXY_PORT" >> /etc/environment
echo "no_proxy=127.0.0.1,localhost" >> /etc/environment
# Proxy for daemons (e.g. Docker deamon - used to pull images, apt - run from default daily cron job)
mkdir /etc/systemd/system.conf.d
echo [Manager] > /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"http_proxy=PROXY_ADDRESS:PROXY_PORT\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"https_proxy=PROXY_ADDRESS:PROXY_PORT\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"no_proxy=127.0.0.1,localhost\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "# Docker requires upper-case http proxy environment variables..." >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"HTTP_PROXY=http://PROXY_ADDRESS:PROXY_PORT2\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"HTTPS_PROXY=http://PROXY_ADDRESS:PROXY_PORT\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"NO_PROXY=127.0.0.1,localhost\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
# Proxy for docker containers started with `docker run`
mkdir /home/vagrant/.docker
cat <<EOF > /home/vagrant/.docker/config.json
{
  "proxies": {
    "default": {
      "httpProxy": "http:/PROXY_ADDRESS:PROXY_PORT",
      "httpsProxy": "http://PROXY_ADDRESS:PROXY_PORT",
      "noProxy": "127.0.0.1,localhost"
    }
  }
}
EOF
chown -R vagrant:vagrant /home/vagrant/.docker

-2

您可以从YAML文件中加载设置。如下所示,在Drupal VM中演示:

# Use config.yml for basic VM configuration.
require 'yaml'
dir = File.dirname(File.expand_path(__FILE__))
if !File.exist?("#{dir}/config.yml")
  raise 'Configuration file not found! Please copy example.config.yml to config.yml and try again.'
end
vconfig = YAML::load_file("#{dir}/config.yml")

那么你可以创建{{link1:config.yml}},如下:

vagrant_box: geerlingguy/ubuntu1404
vagrant_user: vagrant
vagrant_ip: 192.168.88.88

Vagrantfile 中,您可以使用变量,例如:

config.vm.box = vconfig['vagrant_box']
config.vm.network "private_network", ip: vconfig['vagrant_ip']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接