How to setup an EC2 instance and similar local development virtual machine with Virtualbox, Vagrant and Chef

I've finally done it and taken the plunge into the world of devops. I've been meaning to automate the build of my live server out for a while but recent changes to the EC2 pricing structure have given me extra motivation. Financial motivation! What I wanted to achieve was:

  • Automate creating, starting and stopping an Amazon EC2 instance using Vagrant
  • Automate creating a similar local virtual machine using Vagrant
  • Provisioning both with Chef to install packages such as Apache, MySQL, etc
  • Deploy base codebases and databases for all my sites

The holy grail for me would be to run one command and bang! - an EC2 instance with all my sites would appear. Then run another command and boom! - a local virtual machine would appear with all the sites running locally for development. And of course all the deployment and setup would be shared so there would be no duplication.

There were many problems found along the way pursuing this dream but in the end it turns out Virtualbox, Vagrant and Chef can deliver the goods. And deploy the goods. And provision the goods!

The benefits for this process are plenty:

  • Recreate the environment quickly and easily, recreate locally.
  • Test changes to the environment locally then deploy live.
  • Migration to another OS is simple. Where possible the tools are platform agnostic and where this is not possible platform specific work arounds can be implemented.
  • This toolset is widely accepted so would be simple to migrate to another hosting platform
  • All config is kept in one place under version control. It's entirely possible to work on all the config files like virtual host files, db config in your favourite IDE locally and deploy changes via Chef so you don't need to fiddle around with vi inside a ssh tunnel. (Although I do like that type of thing!)

Creating a local development virtual machine with Vagrant

So to get started we need Vagrant https://docs.vagrantup.com/v2/installation/ and also Virtualbox if you don't already have it.

With these in place we can start to create the configuration for our local virtual machine

mkdir mydomain
cd mydomain
mkdir vagrant_local
cd vagrant_local
vagrant init

vagrant init creates a file called Vagrantfile that defines everything Vagrant needs to create the VM and later the details for Chef to know how to provision your new server. The file is heavily commented with sensible defaults to help us when we need to start tweaking.

So first thing we need to consider is the 'base box' to use. This is the base flavour of the OS and where our journey starts. Kinda like an install DVD. Or ISO. Normally this is a matter of choosing a base box to match the intended OS, eg CentOS, Debian, Ubuntu. However we want to create a server on Amazon EC2, so we must choose an image that is both available as a Vagrant base box and as an EC2 AMI (the EC2 equivalent of a Vagrant base box)

I was already planning to run Ubuntu so our next job is to find a menu of base boxes and AMI's.

Luckily there are excellent online resources for Ubuntu. EC2 AMI's are listed here http://cloud-images.ubuntu.com/locator/ec2/ and Vagrant boxes listed here https://vagrantcloud.com/ubuntu

Ubuntu Server 14.04 LTS is the best match here, so let's configure our Vagrantfile for the local VM. Fire up your favourite editor and amend the Vagrantfile to use this box and to setup networking:

config.vm.box = "ubuntu/trusty64"

config.vm.network "private_network", ip: "192.168.56.210"

Then to start the vm run this in the same directory as the Vagrantfile

vagrant up

Vagrant will now download the base box and instruct Virtualbox to create and start the VM. Once it's completed we can login to our new local development server

vagrant ssh

So that's our local development server created with a single command. Later we will introduce Chef to install applications like MySQL and Apache. Here's the full Vagrantfile for the local VM:

(mydomain/vagrant_local/Vagrantfile)

# 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 = "ubuntu/trusty64"
    config.vm.network "private_network", ip: "192.168.56.210"

end

Creating a remote EC2 server with Vagrant

Next to setup the live EC2 server. For this we need to start with installing a Vagrant plugin to do the talking with EC2.

vagrant plugin install vagrant-aws

And then setup the Vagrant environment

cd ..
mkdir vagrant_ec2
cd vagrant_ec2
vagrant init

And again we have to configure the new Vagrantfile. But before we can we have do some some work in our AWS Management Console.

We need to:

  • Setup an IAM access key to allow Vagrant to talk to EC2
  • Setup a SSH key pair to use to login to the EC2 instance once it's created
  • Choose the location to launch the instance
  • Setup a security group for the instance
  • Choose the type of instance
  • Choose the AMI

That sounds like a lot to do! And there's more, and this was a real gotcha for me. I wanted to take advantage of the new t2.micro pricing. It's cheaper AND better spec than the t1.micro. No brainer. However it turns out that t2 instances only run under Amazon VPC. I thought this would be end of the road, with either VPC not working with the vagrant-ec2 plugin or it costing too much. Turns out VPC has no cost and it does work with vagrant-ec2. Phew!

So the final item for the AWS Management Console list is:

  • Setup a VPC network

So off to work. To obtain the IAM access key follow the instructions here:

http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html

You will end up with an Access key ID (example: AKIAIOSFODNN7EXAMPLE) and Secret access key (example: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY) These keys identify the vagrant-ec2 plugin with an Amazon IAM user/role/group. In the IAM console you set the permission policy to only let these keys access what is neccessary to create, boot and halt an instance. However as we are keen to get started we can allow all actions by creating a group assigned to our user with the following policy:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowItAllPolicy",
    "Effect": "Allow",
    "Action": ["ec2:*"],
    "Resource": "*"
  }]
}

With this in place enter the key details into your Vagrantfile (the full Vagrantfile is listed at the end of the article, refer to it to see where to insert each snippet)

aws.access_key_id = "AKIAIOSFODNN7EXAMPLE"
aws.secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

It's probably a good idea next to decide which EC2 region you want your server to live in. We have much to do in the EC2 console so should make sure we are setting things up in the right region. I've selected 'US East' for my example.

Next task is the ssh keys to access the instance once it's created. This is not managed in the IAM console but the EC2 console, selecting 'Key Pairs' from the navigation menu. Once your keypair is setup, enter the details in the Vagrantfile

aws.keypair_name = "mykeypairname"
override.ssh.username = "ubuntu"
override.ssh.private_key_path = "/path/to/the/keypair/file.pem"

Amazon Ubuntu AMI's require ssh using the ubuntu user, which we specify with the 'override.ssh.username' parameter.

Now we need to setup the VPC, as this needs to be in place for the other items on our todo list. Again in the EC2 console select 'Network Interfaces' from the navigation menu and create a VPC network interface. Vagrantfile:

aws.availability_zone = "us-east-1b"
aws.subnet_id = "subnet-123x456y"

Then select 'Security Groups' from the navigation menu and create a security group for the VPC. At least add SSH, HTTP and HTTPS inbound rules for a web server. More food for our Vagrantfile. Note you must use the 'Group ID' value:

aws.security_groups = ["sg-1ab2c345"]

Now the instance type. I already know I want the cheap one:

aws.instance_type = "t2.micro"

And the AMI. In this example we want 64 bit trusty tahr for us-east-1, to fit the VPC. But which type? Turns out for the t2.micro instance we must have the 'hvm' type. The list at http://cloud-images.ubuntu.com/locator/ec2/ leads us to ami-9aaa1cf2 which we can enter, along with your region

aws.ami = "ami-9aaa1cf2"
aws.region = "us-east-1"

Then create an elastic IP (select the VPC type) for the instance and enter it here:

aws.elastic_ip = "54.172.123.123"

Finally we have to set the base box. As said before Amazon doesn't use Vagrant base boxes but it's own AMI's, but vagrant still needs a base box to do it's stuff. So we specify a dummy box that is built to work with the Vagrant-EC2 plugin:

# Dummy box to work with Vagrant EC2 plugin
config.vm.box = "dummy"
config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"

Now we are set. Time to see the vagrant-ec2 plug work it's magic (note the extra provider option calling Vagrant to tell it to talk to EC2)

vagrant up --provider=aws

Check in the EC2 console to see the instance spark into life. In my testing the elastic IP didn't always connect so I needed to connect it by hand, but that's a small step to put right.

Again once booted we can login

vagrant ssh

Another win, we are now half way to our objective. A live EC2 server and corresponding local development server all controlled via Vagrant. In true devops style you can create, start, stop, destroy and recreate repeatedly from the same config. Infact I suggest you next run

vagrant destroy
vagrant up --provider=aws

just because you can

As promised the Amazon EC2 Vagrantfile in full:

# https://github.com/mitchellh/vagrant-aws

# To start use vagrant up --provider=aws

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

    # Dummy box to work with Vagrant EC2 plugin
    config.vm.box = "dummy"
    config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"

    config.vm.provider :aws do |aws, override|

        # https://blog.logentries.com/2014/03/devops-vagrant-with-aws-ec2-digital-ocean/
    aws.access_key_id = "AKIAIOSFODNN7EXAMPLE"
    aws.secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

        # http://cloud-images.ubuntu.com/locator/ec2/
        # us-east-1
        # trusty
        # 14.04LTS
        # amd64
        # hvm:ebs
        # ami-9aaa1cf2

        aws.ami = "ami-9aaa1cf2"
        aws.instance_type = "t2.micro"
        aws.region = "us-east-1"
        aws.availability_zone = "us-east-1b"

        # t2 instance types can only be launched to a VPC
        aws.security_groups = ["sg-1ab2c345"] # Replace with your security group
        aws.elastic_ip = "54.172.123.123" # Replace with your EIP
        aws.subnet_id = "subnet-123x456y" # Replace with your subnet id
        aws.keypair_name = "mykeypairname"
        override.ssh.username = "ubuntu"
        override.ssh.private_key_path = "/path/to/the/keypair/file.pem"

    end
end

Next article: Use Chef to provision both the local VM and the EC2 instance. It's nice to have created these servers but they really need applications installing so they can do some work for us!

Tags: chef, devops, ec2, vagrant, virtualbox