Vagrant wait for other box to finish booting - vagrant

I'm in the process of learning how to set up Puppet ( master and agents ). I'd like to have Vagrant start up several boxes; one as the Puppet master, the rest as agents that'd use that box to get their configurations from.
Is there a way to tell Vagrant to ensure that one box is loaded before attempting to bring up others?
For example, if I've got three boxes, defined like so:
config.vm.define "master" do |test|
test.vm.box = "puppetlabs/ubuntu-12.04-64-puppet"
test.vm.network "private_network", ip: "192.168.50.10"
test.vm.hostname = "puppet-master"
test.ssh.forward_agent = true
end
config.vm.define "web01" do |web01|
web01.vm.box = "puppetlabs/ubuntu-12.04-64-puppet"
web01.vm.network "private_network", ip: "192.168.50.01"
web01.vm.hostname = "web01-dev"
web01.ssh.forward_agent = true
end
config.vm.define "web02" do |web02|
web02.vm.box = "puppetlabs/ubuntu-12.04-64-puppet"
web02.vm.network "private_network", ip: "192.168.50.02"
web02.vm.hostname = "web02-dev"
web02.ssh.forward_agent = true
end
Is there a way to have web01 and web02 make sure that puppet-master is up and running before booting? Or do I just have to manually make sure that the boxes boot in the right order? I'd prefer to have some sort of configuration option, so that I don't have to rely on other people on my team reading docs before running vagrant up.

Related

Multi-machine Vagrant project not provisioning as per docs

I’ve trying to set up a multi-machine Vagrant project. According to the docs (https://www.vagrantup.com/docs/multi-machine/), provisioning is “outside in”, meaning any top-level provisioning scripts are executed before provisioning scripts in individual machine blocks.
The project contains a Laravel project, and a Symfony project. My Vagrantfile looks like this:
require "json"
require "yaml"
confDir = $confDir ||= File.expand_path("vendor/laravel/homestead", File.dirname(__FILE__))
homesteadYamlPath = "web/Homestead.yaml"
homesteadJsonPath = "web/Homestead.json"
afterScriptPath = "web/after.sh"
aliasesPath = "web/aliases"
require File.expand_path(confDir + "/scripts/homestead.rb")
Vagrant.configure(2) do |config|
config.vm.provision "shell", path: "init.sh"
config.vm.define "web" do |web|
web.ssh.forward_x11 = true
if File.exists? aliasesPath then
web.vm.provision "file", source: aliasesPath, destination: "~/.bash_aliases"
end
if File.exists? homesteadYamlPath then
Homestead.configure(web, YAML::load(File.read(homesteadYamlPath)))
elsif File.exists? homesteadJsonPath then
Homestead.configure(web, JSON.parse(File.read(homesteadJsonPath)))
end
if File.exists? afterScriptPath then
web.vm.provision "shell", path: afterScriptPath
end
end
config.vm.define "api" do |api|
api.vm.box = "ubuntu/trusty64"
api.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "2048"]
end
api.vm.network "private_network", ip: "10.1.1.34"
api.vm.network "forwarded_port", guest: 80, host: 8001
api.vm.network "forwarded_port", guest: 3306, host: 33061
api.vm.network "forwarded_port", guest: 9200, host: 9201
api.vm.synced_folder "api", "/var/www/api"
api.vm.provision "shell", path: "api/provision.sh"
end
end
I have a block (web) for the Laravel project, where I’ve copied the contents of the Homestead-based Vagrantfile, and an api block that uses the “standard” Vagrant configuration.
To bootstrap the projects, I created a simple shell script (init.sh) that simply clones the Git repositories into git-ignored directories. Given the documentation says configuration works outside-in, I’d therefore expect that script to run, and then the machine-specific blocks, but this doesn’t seem to be happening. Instead, on vagrant up, I receive the following error:
There are errors in the configuration of this machine. Please fix the following errors and try again:
vm:
* A box must be specified.
It seems it’s still trying to provision the individual machines, before running the shell script. I know the shell script isn’t getting called as I added an echo statement to it. Instead, the terminal just outputs the following:
Bringing machine 'web' up with 'virtualbox' provider...
Bringing machine 'api' up with 'virtualbox' provider...
So how can I get Vagrant to run my shell script first? I think it’s failing because the web group is checking if my web/Homestead.yaml file exists and if so, use the values in there for configuring (including the box name), but as my shell script hasn’t been ran and hasn’t cloned the repository that file does not exist, so there is no box specified, which Vagrant complains about.
The issue is that you do not define a box for the web machine. You need to either define the box in the outer space like
config.vm.box = "ubuntu/trusty64"
if you plan to use the same box/OS for both machines or define in the web scope
web.vm.box = "another box"
EDIT
Using the provision property will run the script in the VM, which is not what you want here, as you want the script to run on your host. (and because it runs in the VM, it needs the VM to be booted first)
Vagrantfile is just a simple ruby script, so you could add your script or even an execution to it (from ruby call), a potential issue I could see is that you cannot guarantee the execution and specially that the execution of your init script will be complete before vagrant does it things on the VM.
A possibility is to use the vagrant trigger plugin and execute your shell script before the up event
config.trigger.before :up do
info "Dumping the database before destroying the VM..."
run "init.sh"
end
Running it this way, vagrant will wait for the script to be executed before it runs its part of the up command.
You would need to do some check in your script to make sure it runs only when needed, otherwise, it will run everytime you start the machine (invoking vagrant up), e.g. you could make a check on the presence of the yaml file

Vagrant global-status all VM's named default

When I run vagrant global-status, I get a listing back of all the vagrant managed VM's I'm running, but something I have not expected is output with the machine details. Even though I give every machine an explicit name during initialization, the value for name in the global-status output for every machine is always default.
Why does vagrant status or vagrant global-status not list the actual names of the boxes? At times, when I've destroyed a box, it has actually destroyed more than one box.
id name provider state directory
-------------------------------------------------------------------------
e44520d default virtualbox poweroff /Users/name/code/foo
1310726 default virtualbox poweroff /Users/name/code/bar
430fd52 default virtualbox poweroff /Users/name/code/baz
The machines are named foo, bar, baz... why are they showing as default?
If you have default Vagrantfile as configuration, your machine VM will be named default, so something like
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu-12.04"
config.vm.hostname = "foo"
config.vm.network "forwarded_port", guest: 80, host: 8080
. . .
Now, if you have a multi machine configuration, you will have something like this in your Vagrantfile (simplified)
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu-12.04"
config.vm.define "db" do |db|
db.vm.hostname = db.test
db.vm.provider "vmware_fusion" do |vm|
vm.vmx["memsize"] = "2048"
end
db.vm.provision "shell", path: "bootstrap-dev.sh"
end
config.vm.define "app", primary: true do |app|
app.vm.hostname = app.test
app.vm.network "private_network", "192.168.50.11"
app.vm.synced_folder "project/site", "/var/www"
end
end
In this case, you have defined 2 machines db and app and the vagrant global-status will prompt
id name provider state directory
-----------------------------------------------------------------------------------------------------------
de264ac db vmware_fusion not running /Volumes/Transcend/Project1
bdd4385 app vmware_fusion not running /Volumes/Transcend/Project1

Do multiple machines declared in a Vagrantfile have to be based on the same base box?

From the documentation you can address the need to have multiple environments running to reproduce your production (like one db machine, one web machine etc.):
Vagrant.configure("2") do |config|
config.vm.define "web" do |web|
web.vm.box = "apache"
end
config.vm.define "db" do |db|
db.vm.box = "mysql"
end
end
This is well explained but I also understand they will be all based on the same box: e.g. ubuntu-1404 for instance.
What happens if you need the db box to be on oracle linux, the web box on ubuntu for instance?
Appart from creating two directories with two Vagrantfile I don't see any other option.
Has anyone ever done that and how?
No, the boxes can definitely be different. The example you showed (from the Multi-Machine doc page is a big confusing because it's not clear what "apache" and "mysql" are referring to.
You can set *.vm.box to actual box names. For example, finding a couple of boxes from the Vagrant cloud might change your example Vagrantfile to:
Vagrant.configure("2") do |config|
config.vm.define "web" do |web|
web.vm.box = "hashicorp/precise64"
end
config.vm.define "db" do |db|
db.vm.box = "box-cutter/oel65"
end
end
No, you are free to overwrite every setting - which you have already done. Didn't you tested it?

vagrant primary box defined but commands still run against all boxes

I am trying to set my "dev" VM as primary so most commands such as vagrant up, vagrant halt, etc operate on the "dev" VM and ignore the "stage" VM unless the "stage" VM name is explicitly listed on the command line. Here's my Vagrantfile, but when I run vagrant up both VMs are brought up instead of just "dev".
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
def box_setup(config, ip, playbook, inventory)
config.vm.network :private_network, ip: ip
config.vm.provision "ansible" do |ansible|
ansible.playbook = playbook
ansible.inventory_path = inventory
# ansible.verbose = "vvv"
end
end
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# common settings shared by all vagrant boxes for this project
config.vm.box = "debian-7-amd64"
config.vm.box_url = "https://dl.dropboxusercontent.com/s/xymcvez85i29lym/vagrant-debian-wheezy64.box"
config.ssh.forward_agent = true
# development box intended for ongoing development
config.vm.define "dev", primary: true do |dev|
box_setup dev, \
"10.9.8.30", "deploy/playbook_dev.yml", "deploy/hosts/vagrant_dev.yml"
end
# stage box intended for configuration closely matching production
config.vm.define "stage" do |stage|
box_setup stage, \
"10.9.8.31", "deploy/playbook_full_stack.yml", "deploy/hosts/vagrant_stage.yml"
end
end
The primary flag seems to only work for vagrant ssh for me.
In the past I have used the following method to hack around the issue.
# stage box intended for configuration closely matching production
if ARGV[1] == 'stage'
config.vm.define "stage" do |stage|
box_setup stage, \
"10.9.8.31", "deploy/playbook_full_stack.yml", "deploy/hosts/vagrant_stage.yml"
end
end

Using Vagrant to manage development and production environments?

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
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
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
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?
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.

Resources