Using Vagrant to manage development and production

2019-03-24 16:26发布

问题:

How are people handling simple automation (with puppet) for dev / prod environments with vagrant (ideally from the same vagrantfile)?

Use case I'm trying to solve

  • I would love to spin up the the production machine with vagrant if it isn't created.
  • I would love to reload nginx or apache confs on production with vagrant if they were tweaked in the puppet files for my dev environment.

The Problem

When you call vagrant up with a provider like AWS or Digital Ocean, it becomes the active provider and you can't switch. You get this error:

An active machine was found with a different provider. Vagrant currently allows each machine to be brought up with only a single provider at a time. A future version will remove this limitation. Until then, please destroy the existing machine to up with a new provider.

It seems the answer it to destroy, but I just need to switch. I don't want to destroy.

I would love to be able to say

vagrant up prod

or

vagrant reload prod

and then a simple vagrant up would fall back to the default machine.

This syntax is similar to how multiple machines work, but I don't want to spin up a dev and production environment when I just call vagrant up (which is the default behavior).

Should I be looking at packer as part of the workflow? I watched the whole talk at puppetconf 2013 on Mitchell's talk on Multi-Provider http://puppetlabs.com/presentations/multi-provider-vagrant-aws-vmware-and-more

I'm still not seeing a solution for my problem.


UPDATE 9/27/13

In case anybody else is fighting this idea, this article cleared up a lot of questions I had. http://pretengineer.com/post/packer-vagrant-infra

回答1:

As for workaround, you should define config.vm.define (as suggested here), in order to support multiple providers.

Please find the following configuration posted by @kzap as example:

Vagrant.configure("2") do |config|

  # Store the current version of Vagrant for use in conditionals when dealing
  # with possible backward compatible issues.
  vagrant_version = Vagrant::VERSION.sub(/^v/, '')

  # Configuration options for the VirtualBox provider.
  def configure_vbox_provider(config, name, ip, memory = 2048, cpus = 1)
    config.vm.provider :virtualbox do |v, override| 
      # override box url
      override.vm.box = "ubuntu/trusty64"
      # configure host-only network
      override.vm.hostname = "#{name}.dev"
      override.vm.network :private_network, id: "vvv_primary", ip: ip

      v.customize ["modifyvm", :id, 
        "--memory", memory,
        "--cpus", cpus,
        "--name", name,
        "--natdnshostresolver1", "on",
        "--natdnsproxy1", "on"
      ]
    end
  end

  default_provider = "virtualbox"
  supported_providers = %w(virtualbox rackspace aws managed)
  active_provider = ENV['VAGRANT_ACTIVE_PROVIDER'] # it'd be better to get this from the CLI --provider option
  supported_providers.each do |provider|
  next unless (active_provider.nil? && provider == default_provider) || active_provider == provider

    #
    # VM per provider
    #
    config.vm.define :"sample-#{provider}" do | sample_web_config |

      case provider
      when "virtualbox"
        configure_vbox_provider(sample_web_config, "examine-web", "192.168.50.1")

      when "aws"
        configure_aws_provider(sample_web_config)

      when "managed"
        configure_managed_provider(sample_web_config, "1.2.3.4")

      when "rackspace"
        configure_rackspace_provider(sample_web_config)  

      end
  end

end

Or the following example posted at gist by @maxlinc:

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

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "dummy"

  config.vm.provider :rackspace do |rs|
    rs.username = ENV['RAX_USERNAME']
    rs.api_key  = ENV['RAX_API_KEY']
    rs.rackspace_region   = :ord
  end

  supported_providers = %w(virtualbox rackspace)
  active_provider = ENV['VAGRANT_ACTIVE_PROVIDER'] # it'd be better to get this from the CLI --provider option
  supported_providers.each do |provider|
    next unless active_provider.nil? || active_provider == provider

    config.vm.define "exact_name_#{provider}" do |box|
      box.vm.provider :rackspace do |rs|
        rs.flavor = '1 GB Performance'
        rs.image  = 'Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)'
      end
    end

    config.vm.define "regex_#{provider}" do |box|
      box.vm.provider :rackspace do |rs|
        rs.flavor = /1\s+GB\s+Performance/
        rs.image  = /Ubuntu.*Trusty Tahr.*(PVHVM)/
      end
    end

    config.vm.define "id_#{provider}" do |box|
      box.vm.provider :rackspace do |rs|
        rs.flavor = 'performance1-1'
        rs.image  = 'bb02b1a3-bc77-4d17-ab5b-421d89850fca'
      end
    end

    config.vm.define "unlisted_#{provider}" do |box|
      box.vm.provider :rackspace do |rs|
        rs.flavor = 'performance1-1'
        rs.image = '547a46bd-d913-4bf7-ac35-2f24f25f1b7a'
      end
    end
  end
end


回答2:

Not an ideal solution, but what about using git branches? My thinking is that it could be conceptually similar to using heroku, where you might have a master, staging, and production versions (since they're usually different remotes).

In this case you start off the prod branch with the small edit to the Vagrantfile to name the VM a little differently. Then you should be able to merge all changes from dev with the prod branch as they occur. So your workflow would look like:

$ git checkout prod
$ vagrant up
$ git checkout master
...  make changes to puppet ...
$ git checkout prod
$ git merge master
$ vagrant reload
$ git checkout master

You could script and alias these so you end up with

$ start_production
$ reload_production


回答3:

Here is a simple way of dynamically changing the 'default' machine name depending on the specified --provider from the command line, so they won't conflict between the different providers:

require 'getoptlong'
opts = GetoptLong.new(
  [ '--provider', GetoptLong::OPTIONAL_ARGUMENT ],
  [ '--vm-name',  GetoptLong::OPTIONAL_ARGUMENT ]
)

provider=ENV['PROVIDER'] || 'virtualbox'
vm_name=ENV['VM_NAME'] || 'default'
opts.each do |opt, arg|
  case opt
    when '--provider'
      provider=arg
    when '--vm-name'
      vm_name=arg
  end
end

Vagrant.configure(2) do |config|

  # HERE you are dynamically changing the machine name to prevent conflict.
  config.vm.define "mt-#{provider}-#{vm_name}"

  # Below sections are just examples, not relevant.
  config.vm.provider "virtualbox" do |vm|
    vm.name = "test.local"
    vm.network "private_network", ip: "192.168.22.22"
    vm.customize ['modifyvm', :id, '--natdnshostresolver1', 'on']
    config.vm.box = "ubuntu/wily64"
  end

  config.vm.provider :aws do |aws, override|
    aws.aws_profile = "testing"
    aws.instance_type = "m3.medium"
    aws.ami = "ami-7747d01e"
    config.vm.box = "testing"
  end
end

Example usage:

VM_NAME=dev PROVIDER=virtualbox vagrant up --provider=virtualbox
VM_NAME=uat PROVIDER=aws vagrant up --provider=aws
VM_NAME=test PROVIDER=aws vagrant up --provider=aws
VM_NAME=prod PROVIDER=aws vagrant up --provider=aws
VM_NAME=uat PROVIDER=aws vagrant destroy -f
VM_NAME=test PROVIDER=aws vagrant status

See also: Multiple provisioners in a single vagrant file?



回答4:

what I came up with to work with this scenario is to manage 2 distincts .vagrant folder.

Note: most of the other answers deal with setting up multi-provider assuming you will run dev and prod on different provider, in most cases this might be true but you can definitely have cases where you have same provider for dev and prod. Lets say you're using aws and you want to use dev and prod as ec2 instance it will be the same provider.

Say you want to manage dev and prod instances, potentially using different providers (but could also very well be on the same provider) so you'll do:

  • set up dev instance with normal vagrant up --provider <dev_provider>. This will create a dev VM that you can manage

  • back up the .vagrant folder created in your project directory and rename it like .vagrant.dev

  • set up prod instance with your provider of choice vagrant up --provider <prod_provider>. This now creates your prod VM

  • back up the newly .vagrant folder created in your project directory and rename it like .vagrant.prod

now, depending if you want to work on dev or prod, you'll rename the .vagrant.dev or .vagrant.prod directory as .vagrant and vagrant will operate the right VM.

I did not come up with a script as mainly the most of the time I work with dev and very few times I need to switch to the other provider. but I dont think it will be too hard to read the parameter from CLI and make the renaming more dynamic.