Development platform‎ > ‎Vagrant‎ > ‎

Using Vagrant with AWS EC2

[updated 2016-10-11 2016-10-09]

Basics

If you want to do web development from any machine, you want a development machine in the cloud, such as on Amazon Web Services (AWS) Elastic Compute Cloud (EC2). Vagrant is capable of spinning up VMs in EC2, but doing so is not quite as easy as doing it locally, and shared folders don't work quite as well. 

Components needed

  • Vagrant
  • The aws-vagrant pluginvagrant plugin install vagrant-aws
  • A dummy vagrant box: vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
    • Note that dummy here after box add is arbitrary; you could give it a different name.
  • The ID for an Amazon Machine Image (AMI) that matches both your region and instance-type
    • Ubuntu has a great search tool for its AMIs: https://cloud-images.ubuntu.com/locator/ec2/
    • This was a surprising pain point for me when trying to make this work. First, I had a hard time finding AMIs in Amazon's interface because there are so many to choose from. Second, AMIs are region-specific, and they also are instance-type-specific. So some boxes will work in one region and not the next, and some boxes will  work only with a certain type of instance (say, t1.micro) and not with another (say, t2.micro). 
    • Given the specificity of AMIs, it makes very little sense for me (or anyone) to suggest one for you. 
  • On Windows, additional non-native command-line tools for connecting to your machine and syncing folders:
    • rsync, installed and available in your path
      • You can get it with Cygwin, then add the Cygwin bin directory to your path
      • You can add values to your path at the command line, or in the GUI: right-click My Computer -> Advance System Settings -> Environment Variables
    • SSH, installed and available in your path
      • It is an optional Cygwin component; you must select it specifically (see Stackoverflow)
    • Note: with AWS, folder syncing is one-way (from local machine -> Amazon), and it happens once (on Vagrant up and some other events), not continuosly
  • An AWS account, with appropriate credentials and security settings
    • Conceptually, there are two different kinds of credentials and settings
      • Credentials for AWS that allow you to deploy EC2 resources and create a machine. 
      • Credentials for the EC2 machine that allow you to connect to it.
        • Note: these credentials are machine-specific in the sense that they will provide access to your machine, not your account. But they are machine-agnostic in one sense: they can be shared across multiple machines.
    • The AWS credentials are created in AWS IAM (Identity and Access Management)
      • Create a user that has two keys:
        • Access Key (always visible in the IAM console)
        • Secret Key (only displayed once, when you create the user!)
      • Give the user privileges adequate to the task
        • Users -> User Name -> Permissions -> Attach Policy
        • The Policy EC2 Full Access will work; more limited ones might work too.
      • [Some documentation, including the aws-vagrant plugin documentation, calls for a session token, but it appears to be unnecessary.]
    • The EC2-specific credentials are created within EC2: (1) a Key Pair, and (2) a Security Group
      • Key Pair
        • From the EC2 console, select Key Pairs (under Networks + Security) -> Create Key Pair
        • You will need to download the generated .pem file
          • Note: Vagrant will be able to SSH into the machine with the .pem file, but if you want to SSH in with Putty on Windows, you will need to convert the .pem file to a .ppk file. You can do that with Puttygen
        • Your configuration file will need (1) the path to that .pem file, and (2) the name of the key pair on EC2.
      • Security Group, identified either by name or ID, depending
        • From the EC2 console, select Security Groups (under Networks + Security) -> Create Security Group
        • Make sure the Security Group you create (or the existing one that you use) has whatever inbound access you want. It must accept inbound SSH (port 22) of you want to be able to SSH in using vagrant ssh.
        • Use the right identifier (Name or ID) depending on your VPC type:
          • Default VPC -> use Group Name
          • Non-default VPC -> use Group ID
    • Optionally: an Elastic IP from AWS EC2. This may make some things easier.
  • An AWS-specific Vagrant configuration file
    • You can simply edit your Vagrantfile
    • You can manually create a .yaml config file, like Scott Lowe suggests, that your Vagrantfile calls
    • You can generate a .yaml config file with a tool like PuPHPet
      • I failed at doing this with PuPHPet when I didn't understand what was really going on. 
      • At least for me, it makes more sense to do it by manually editing the Vagrantfile, so I can see and understand what is happening.
    • Note Many tutorials propose putting credential information outside your configuration files, in environment variables. I consider this stupid, as you could conceivably have multiple different AWS credential sets (by creating multiple users). But you can only have one set of environment variables at a time. 

Simple setup

  1. In new directory: vagrant init
    • This will create a vanilla Vagrantfile
  2. Edit the Vagrantfile. This file worked for me:
require "vagrant-aws"
Vagrant.configure("2") do |config|
  # Use dummy AWS box already added
  config.vm.box = "dummy" 
  #AWS provider information
  config.vm.provider 'aws' do |aws, override|
      # Hard-coded AWS variables
      aws.access_key_id = 'your access key here'
      aws.secret_access_key ='your secret access key here'
      # AWS configuration details, in the order that Vagrant lists them in its output
      # Use whatever instance type, ami, and region you like, just make sure they can all work together
      aws.instance_type = 't2.micro'
      aws.ami = 'ami-33ea9424'
      aws.region = 'us-east-1'
      # Specify SSH keypair to use
      aws.keypair_name = 'your keypair name here'
      # Specify security group(s), by name here because I am using the default VPC
      # Brackets are not strictly required if you have only one value, but they show that
      # technically this is an array that could hold multiple values.
      aws.security_groups = ['your security group here']
      # Specify username on remote machine (here, the default for the AMI) and path to local SSH key file
      override.ssh.username = 'ubuntu'
      override.ssh.private_key_path = '[local path to .pem file on your system]'
  end
end
  • This creates a simple working machine, with one synced folder: the local Vagrant directory is rsynced on startup to the remote /vagrant directory.
  • You can SSH to it two ways:
    • In the Vagrantfile directory: vagrant SSH
    • Using Putty, host address is ubuntu@[Amazon public IP], use your private key for authentication (after converting the .pem file to .ppk format)

Troubleshooting

It took me a while to get this to work. These are some errors I encountered on vagrant up, and what they stemmed from.
  • Errors about lack of authorization, and a long list of program errors:
==> default:  -- VPC tenancy specification: default
H:/VirtualMachines/vagrant-home/gems/gems/excon-0.53.0/lib/excon/middlewares/expects.rb:7:in `response_call': UnauthorizedOperation => You are not authorized to perform this operation. (Fog::Compute::AWS::Error)
        from H:/VirtualMachines/vagrant-home/gems/gems/excon-0.53.0/lib/excon/middlewares/response_parser.rb:9:in `response_call'
        from H:/VirtualMachines/vagrant-home/gems/gems/excon-0.53.0/lib/excon/connection.rb:388:in `response'
        [many more lines like these last two]
    • Problem: This was the first error I got, and I had done several things wrong, so it's hard to be sure which one was the cause. But I believe that this particular set of errors was related to two errors occurring at once: (1) specifying the wrong AMI for my region and instance type, and (2) not properly specifying a security group with the necessary inbound SSH privileges. Oliver Veits likewise attributes similar errors to a wrong region and AMI (Appendix C.1of his blog post).
    • Solution: Errors like this went away when I selected an appropriate AMI and security group.
  • Errors about groupID:
There was an error talking to AWS. The error message is shown below:
InvalidParameterValue => Value () for parameter groupId is invalid. The value cannot be empty
    • Problem: I was using Group ID instead of Group Name for aws.security_groups. 
    • Solution: Use Group Name (required because I am using a default VPC; with a nondefault VPC, Group ID is required).

  • Errors about instance types:
There was an error talking to AWS. The error message is shown below:
InvalidParameterCombination => Virtualization type 'hvm' is required for instances of type 't2.micro'. Ensure that you are using an AMI with virtualization type 'hvm'. For more information, see http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/virtualization_types.html
    • Problem: I had not selected an AMI that worked with my region and instance type.
    • Solution: Use the Ubuntu image-search tool to find an AMI that worked with my region (us-east-1) and instance type (t2.micro).
  • Errors about using a particular AMI:
There was an error talking to AWS. The error message is shown below:
OptInRequired => In order to use this AWS Marketplace product you need to accept terms and subscribe. To do so please visit http://aws.amazon.com/marketplace/pp?sku=e0rcty0t3zp1mkin7a899fmgw
    • Problem: I had found and specified an AMI through Amazon's Marketplace that was free (from Canonical) but that required you to go through a subscription process. I had skipped the subscription process and tried to just use the AMI by ID. You can't do that.
    • Solution: Use the Ubuntu image-search tool to find an AMI that doesn't require subscription. (Alternatively, you could go through the subscription process. I'm pretty sure AWS checks the validity of your subscription against your account, and AWS should be able to validate your right to use an image because you have provided the AWS access key and secret key.)
  • Process hung after these messages:
==> default: Waiting for instance to become "ready"...
==> default: Waiting for SSH to become available...
    • Note 1: You must be patient here before deciding the process has hung. It can take a long time.
    • Note 2: This problem occurred after I had solved my AMI-related problems. I had to Ctrl-C to quit the process. After doing that, I got this output (with the strange, and false, statement that the machine is booted and ready for use):
==> default: Waiting for cleanup before exiting...
bhage@BCH-win10-desktop /cygdrive/h/virtualmachines/aws-v1
$ ==> default: Machine is booted and ready for use!
==> default: Terminating the instance...
Vagrant exited after cleanup due to external interrupt.
    • Problem 1: I had left aws.security_groups blank, because Oliver Veits did, and this parameter had been giving me a headache.
    • Problem 2: I got the same error when I specified a security group by name, but the group itself didn't have access from my IP address.
    • SolutionUse Group Name (required because I am using a default VPC; with a nondefault VPC, Group ID is required). Make sure the group has inbound SSH access from a set of IP addresses that includes your IP address.
  • Machine booted successfully, but I got an error that rsync did not work:
==> default: Machine is booted and ready for use!
==> default: Rsyncing folder: /cygdrive/h/VirtualMachines/aws-v1/ => /vagrant
There was an error when attempting to rsync a synced folder.
Please inspect the error message below for more info.

Host path: /cygdrive/h/VirtualMachines/aws-v1/
Guest path: /vagrant
Command: "rsync" "--verbose" "--archive" "--delete" "-z" "--copy-links" "--chmod=ugo=rwX" "--no-perms" "--no-owner" "--no-group" "--rsync-path" "sudo rsync" "-e" "ssh -p 22 -o LogLevel=FATAL   -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i 'H:/Google Drive/webdev/aws/2016-10-06-vagrant-v1.pem'" "--exclude" ".vagrant/" "/cygdrive/h/VirtualMachines/aws-v1/" "ubuntu@ec2-54-173-221-98.compute-1.amazonaws.com:/vagrant"
Error: rsync: Failed to exec ssh: No such file or directory (2)
rsync error: error in IPC code (code 14) at pipe.c(85) [sender=3.1.2]
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in IPC code (code 14) at io.c(226) [sender=3.1.2]
    • Problem: The clue is in the bolded line above: rsync was in the path, but rsync called SSH, and SSH was not in my path because I had not installed it as part of Cygwin.
    • Solution: Rerun Cygwin installer and select OpenSSH client & server for installation. I did not need to reset my path, because I had already placed the Cygwin bin directory in my path.)
  • Shell scripts for provisioning machine worked, but threw an error
==> default: Running provisioner: shell...
default: Running: inline script
==> default: stdin: is not a tty
    • Problem: This is a known issue with Ubuntu and certain commands run from shell scripts. Ubuntu expects them to be run from an interactive terminal (TTY), not a script, so it throws this warning (the commands still run).
    • Solution: The best solution, as documented in this VVV discussion on Github and on the foo-o-rama.com website, is to incorporate this code in your Vagrantfile, above the first shell provisioner:
config.vm.provision "fix-no-tty", type: "shell" do |s|
    s.privileged = false
    s.inline = "sudo sed -i '/tty/!s/mesg n/tty -s \\&\\& mesg n/' /root/.profile"
end

Differences between my simple setup and other public exampl
es
  • Compared to the vagrant-aws plugin example file:
    • That file has aws.session_token; I do not.
    • That file omits aws.instance_type and aws.region, instead specifying only aws.ami; I specify region and instance type. Perhaps Amazon infers region from the AMI name, but I'm not sure how it can infer instance type. Presumably you get some default instance type. I'd rather specify it.
    • Instructions for that file say to run vagrant up --provider=aws, but if you only have one provider in the file, there is no need for the --provider flag; vagrant up works fine.
  • Compared to Scott Lowe's example file:
    • He calls the box aws-dummy, not just dummy; this is not important. The name is arbitrarily specified when you do vagrant box add.
    • He stores secret key and access key in environment variables; this is a matter of taste.
    • He puts the variables in a different order. This does not matter; I simply chose to order them the way they get listed in Vagrant's output messages.
  • Compared to Oliver Veits's example file (scroll to Step 5.1):
    • He specifies the box using override.vm.box within the config.vm.provider block; I do it outside, with config.vm.box. I'm sure there's a use case for doing it his way, but I don't know what it is.
    • He stores secret key, access key, keypair name, and private key path in environment variables; I code them in the file. Again, this is a matter of taste.
    • He does not include aws.security_groups at all. He instead relies on his default security group, which he modifies to allow access from his IP address (see Step 10.1 of his tutorial). This strikes me as needlessly complex..

Resources

Vagrant and AWS in particular

AWS in general


Comments