Building EBS Boot and S3 Based AMIs for EC2 with Ubuntu vmbuilder

Here’s my current recipe for how to build an Ubuntu 9.10 Karmic AMI, either the new EBS boot or the standard S3 based, using the Ubuntu vmbuilder software. The Ubuntu vmbuilder utility replaces ec2ubuntu-build-ami for building EC2 images and it can build images for a number of other virtual machine formats as well.

There is a lot of room for simplification and scripting in the following instructions, but I figured I’d publish what is working now so others can take advantage of the research to date. Happy New Year!

Some sections are marked [For EBS boot AMI] or [For S3 based AMI] and should only be followed when you are building that type of AMI. The rest of the sections apply to either type. It is possible to follow all instructions to build both types of AMIs at the same time.

  1. Run an instance of Ubuntu 9.10 Karmic AMI, either 32-bit or 64-bit depending on which architecture AMI you wish to build. I prefer the c1.* instance types to speed up the builds, but you can get by cheaper with the m1.* instance types. Make a note of the resulting instance id:

     # 32-bit
     instanceid=$(ec2-run-instances   \
       --key YOURKEYPAIR              \
       --availability-zone us-east-1a \
       --instance-type c1.medium      \
       ami-1515f67c |
       egrep ^INSTANCE | cut -f2)
     echo "instanceid=$instanceid"
    
     # 64-bit
     instanceid=$(ec2-run-instances   \
       --key YOURKEYPAIR              \
       --availability-zone us-east-1a \
       --instance-type c1.xlarge      \
       ami-ab15f6c2 |
       egrep ^INSTANCE | cut -f2)
     echo "instanceid=$instanceid"
    

    Wait for the instance to move to the “running” state, then note the public hostname:

     while host=$(ec2-describe-instances "$instanceid" | 
       egrep ^INSTANCE | cut -f4) && test -z $host; do echo -n .; sleep 1; done
     echo host=$host
    
  2. Copy your X.509 certificate and private key to the instance. Use the correct locations for your credential files:

     rsync                            \
       --rsh="ssh -i YOURKEYPAIR.pem" \
       --rsync-path="sudo rsync"      \
       ~/.ec2/{cert,pk}-*.pem         \
       ubuntu@$host:/mnt/
    
  3. Connect to the instance:

     ssh -i YOURKEYPAIR.pem ubuntu@$host
    
  4. Install the image building software. We install the python-vm-builder package from Karmic, but we’re going to be using the latest vmbuilder from the development branch in Launchpad because it has good bug fixes. We also use the EC2 API tools from the Ubuntu on EC2 ec2-tools PPA because they are more up to date than the ones in Karmic, letting us register EBS boot AMIs:

     export DEBIAN_FRONTEND=noninteractive
     echo "deb http://ppa.launchpad.net/ubuntu-on-ec2/ec2-tools/ubuntu karmic main" |
       sudo tee /etc/apt/sources.list.d/ubuntu-on-ec2-ec2-tools.list &&
     sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9EE6D873 &&
     sudo apt-get update &&
     sudo -E apt-get upgrade -y &&
     sudo -E apt-get install -y \
       python-vm-builder ec2-ami-tools ec2-api-tools bzr &&
     bzr branch lp:vmbuilder
    

    You can ignore the “Launchpad ID” warning from bzr.

  5. Fill in your AWS credentials:

     export AWS_USER_ID=...
     export AWS_ACCESS_KEY_ID=...
     export AWS_SECRET_ACCESS_KEY=...
     export EC2_CERT=$(echo /mnt/cert-*.pem)
     export EC2_PRIVATE_KEY=$(echo /mnt/pk-*.pem)
    

    Set up parameters and create files to be used by the build process. The bucket value is only required for S3 based AMIs:

     bucket=...
     codename=karmic
     release=9.10
     tag=server
     if [ $(uname -m) = 'x86_64' ]; then
       arch=x86_64
       arch2=amd64
       pkgopts="--addpkg=libc6-i386"
       kernelopts="--ec2-kernel=aki-fd15f694 --ec2-ramdisk=ari-c515f6ac"
       ebsopts="--kernel=aki-fd15f694 --ramdisk=ari-c515f6ac"
       ebsopts="$ebsopts --block-device-mapping /dev/sdb=ephemeral0"
     else
       arch=i386
       arch2=i386
       pkgopts=
       kernelopts="--ec2-kernel=aki-5f15f636 --ec2-ramdisk=ari-0915f660"
       ebsopts="--kernel=aki-5f15f636 --ramdisk=ari-0915f660"
       ebsopts="$ebsopts --block-device-mapping /dev/sda2=ephemeral0"
     fi
     cat > part-i386.txt <<EOM
     root 10240 a1
     /mnt 1 a2
     swap 1024 a3
     EOM
     cat > part-x86_64.txt <<EOM
     root 10240 a1
     /mnt 1 b
     EOM
    
  6. Create a script to perform local customizations to the image before it is bundled. This is passed to vmbuilder below using the --execscript option:

     cat > setup-server <<'EOM'
     #!/bin/bash -ex
     imagedir=$1
     # fix what I consider to be bugs in vmbuilder
     perl -pi -e "s%^127.0.1.1.*\n%%" $imagedir/etc/hosts
     rm -f $imagedir/etc/hostname
     # Use multiverse
     perl -pi -e 's%(universe)$%$1 multiverse%' \
       $imagedir/etc/ec2-init/templates/sources.list.tmpl
     # Add Alestic PPA for runurl package (handy in user-data scripts)
     echo "deb http://ppa.launchpad.net/alestic/ppa/ubuntu karmic main" |
       tee $imagedir/etc/apt/sources.list.d/alestic-ppa.list
     chroot $imagedir \
       apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BE09C571
     # Add ubuntu-on-ec2/ec2-tools PPA for updated ec2-ami-tools
     echo "deb http://ppa.launchpad.net/ubuntu-on-ec2/ec2-tools/ubuntu karmic main" |
       sudo tee $imagedir/etc/apt/sources.list.d/ubuntu-on-ec2-ec2-tools.list
     chroot $imagedir \
       sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9EE6D873
     # Install packages
     chroot $imagedir apt-get update
     chroot $imagedir apt-get install -y runurl
     chroot $imagedir apt-get install -y ec2-ami-tools
     EOM
     chmod 755 setup-server
    
  7. Build the image:

     now=$(date +%Y%m%d-%H%M)
     dest=/mnt/dest-$codename-$now
     prefix=ubuntu-$release-$codename-$arch-$tag-$now
     description="Ubuntu $release $codename $arch $tag $now"
     sudo vmbuilder/vmbuilder xen ubuntu       \
       --suite=$codename                       \
       --arch=$arch2                           \
       --dest=$dest                            \
       --tmp=/mnt                              \
       --ec2                                   \
       --ec2-version="$description"            \
       --manifest=$prefix.manifest             \
       --lock-user                             \
       --part=part-$arch.txt                   \
       $kernelopts                             \
       $pkgopts                                \
       --execscript ./setup-server             \
       --debug
    

    [For S3 based AMI] include the following options in the vmbuilder command above. This does not preclude you from also building an EBS boot AMI with the same image. Make a note of the resulting AMI id output by vmbuilder:

       --ec2-bundle                            \
       --ec2-upload                            \
       --ec2-register                          \
       --ec2-bucket=$bucket                    \
       --ec2-prefix=$prefix                    \
       --ec2-user=$AWS_USER_ID                 \
       --ec2-cert=$EC2_CERT                    \
       --ec2-key=$EC2_PRIVATE_KEY              \
       --ec2-access-key=$AWS_ACCESS_KEY_ID     \
       --ec2-secret-key=$AWS_SECRET_ACCESS_KEY \
    
  8. [For EBS boot AMI] Copy the image files to a new EBS volume, snapshot it, and register the snapshot as an EBS boot AMI. Make a note of the resulting AMI id:

     size=15 # root disk in GB
     volumeid=$(ec2-create-volume --size $size --availability-zone us-east-1a |
       cut -f2)
     instanceid=$(wget -qO- http://instance-data/latest/meta-data/instance-id)
     ec2-attach-volume --device /dev/sdi --instance "$instanceid" "$volumeid"
     while [ ! -e /dev/sdi ]; do echo -n .; sleep 1; done
     sudo mkfs.ext3 -F /dev/sdi
     ebsimage=$dest/ebs
     sudo mkdir $ebsimage
     sudo mount /dev/sdi $ebsimage
     imageroot=$dest/root
     sudo mkdir $imageroot
     sudo mount -oloop $dest/root.img $imageroot
     sudo tar -cSf - -C $imageroot . | sudo tar xvf - -C $ebsimage
     sudo umount $imageroot $ebsimage
     ec2-detach-volume "$volumeid"
     snapshotid=$(ec2-create-snapshot "$volumeid" | cut -f2)
     ec2-delete-volume "$volumeid"
     while ec2-describe-snapshots "$snapshotid" | grep -q pending
       do echo -n .; sleep 1; done
     ec2-register                   \
       --architecture $arch         \
       --name "$prefix"             \
       --description "$description" \
       $ebsopts                     \
       --snapshot "$snapshotid"
    
  9. Depending on what you want to keep from the above process, there are various things that you might want to clean up.

    If you no longer want to use an S3 based AMI:

     ec2-deregister $amiid
     ec2-delete-bundle                     \
       --access-key $AWS_ACCESS_KEY_ID     \
       --secret-key $AWS_SECRET_ACCESS_KEY \
       --bucket $bucket                    \
       --prefix $prefix
    

    If you no longer want to use an EBS boot AMI:

     ec2-deregister $amiid
     ec2-delete-snapshot $snapshotid
    

    When you’re done with the original instance:

     ec2-terminate-instance $instanceid
    

In the above instructions I stray a bit from the defaults. For example, I add the runurl package from the Alestic PPA so that it is available for use in user-data scripts on first boot. I enable multiverse for easy access to more software, and I install ec2-ami-tools which works better for me than the current euca2ools.

I also set /mnt to the first ephemeral store on the instance even on EBS boot AMIs. This more closely matches the default on the S3 based AMIs, but means that /mnt will not be persistent across a stop/start of an EBS boot instance.

Explore and set options as you see fit for your applications. Go wild with the --execscript feature (similar to the ec2ubuntu-build-ami --script option) to customize your image.

The following vmbuilder options do not currently work with creating EC2 images: --mirror, --components, --ppa. I have submitted bug 502490 to track this.

As with much of my work here, I’m simply explaining how to use software that others have spent a lot of energy building. In this case a lot of thanks go to the Ubuntu server team for developing vmbuilder, the EC2 plugin, the ec2-init startup software, and the code which builds the official Ubuntu AMIs; especially Søren Hansen, Scott Moser, and Chuck Short. I also appreciate the folks who reviewed early copies of these instructions and provided feedback including Scott Moser, Art Zemon, Trifon Trifonov, Vaibhav Puranik, and Chris.

Community feedback, bug reports, and enhancements for these instructions are welcomed.

Increasing Root Disk Size of an "EBS Boot" AMI on EC2

This article is about running a new EC2 instance with a larger boot volume than the default. You can also resize a running EC2 instance.

Amazon EC2’s new EBS Boot feature not only provides persistent root disks for instances, but also supports root disks larger than the previous limit of 10GB under S3 based AMIs.

Since EBS boot AMIs are implemented by creating a snapshot, the AMI publisher controls the default size of the root disk through the size of the snapshot. There are a number of factors which go into deciding the default root disk size of an EBS boot AMI and some of them conflict.

On the one hand, you want to give users enough free space to run their applications, but on the other hand, you don’t want to increase the cost of running the instance too much. EBS volumes run $0.10 to $0.11 per GB per month depending on the region, or about $10/month for 100GB and $100/month for 1TB.

I suspect the answer to this problem might be for AMI publishers to provide a reasonable low default, perhaps 10GB as per the old standard or 15GB following in the footsteps of Amazon’s first EBS Boot AMIs. This would add $1.00 to $1.50 per month to running the instance which seems negligible for most purposes. Note: There are also IO charges and charges for EBS snapshots, but those are more affected by usage and less by the size of the original volume.

For applications where the EBS boot AMI’s default size is not sufficient, users can increase the root disk size at run time all the way up to 1 TB. Here’s a quick overview of how to do this.

Example

The following demonstrates how to run Amazon’s getting-started-with-ebs-boot AMI increasing the root disk from the default of 15GB up to 100GB.

Before we start, let’s check to see the default size of the root disk in the target AMI and what the device name is:

$ ec2-describe-images ami-b232d0db
IMAGE	ami-b232d0db	amazon/getting-started-with-ebs-boot	amazon	available	public		i386	machine	aki-94c527fd	ari-96c527ff		ebs
BLOCKDEVICEMAPPING	/dev/sda1		snap-a08912c9	15	

We can see the EBS snapshot id snap-a08912c9 and the fact that it is 15 GB attached to /dev/sda1. If we start an instance of this AMI it will have a 15 GB EBS volume as the root disk and we won’t be able to change it once it’s running.

Now let’s run the EBS boot AMI, but we’ll override the default size, specifying 100 GB for the root disk device (/dev/sda1 as seen above):

ec2-run-instances \
  --key KEYPAIR \
  --block-device-mapping /dev/sda1=:100 \
  ami-b232d0db

If we check the EBS volume mapped to the new instance we’ll see that it is 100GB, but when we ssh to the instance and check the root file system size we’ll notice that it is only showing 15 GB:

$ df -h /
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda1              15G  1.6G   13G  12% /

There’s one step left. We need to resize the file system so that it fills up the entire 100 GB EBS volume. Here’s the magic command for ext3. In my early tests it took 2-3 minutes to run. [Update: For Ubuntu 11.04 and later, this step is performed automatically when the AMI is booted and you don’t need to run it manually.]

$ sudo resize2fs /dev/sda1
resize2fs 1.40.4 (31-Dec-2007)
Filesystem at /dev/sda1 is mounted on /; on-line resizing required
old desc_blocks = 1, new_desc_blocks = 7
Performing an on-line resize of /dev/sda1 to 26214400 (4k) blocks.
The filesystem on /dev/sda1 is now 26214400 blocks long.

Finally, we can check to make sure that we’re running on a bigger file system:

$ df -h /
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda1              99G  1.6G   92G   2% /

Note: The output reflects “99” instead of “100” because of slight differences in how df and EBS calculate “GB” (e.g., 1024 MB vs 1000 MB).

XFS

If it were possible to create an EBS boot AMI with an XFS root file system, then the resizing would be near instantaneous using commands like the following. [Update: For Ubuntu 11.04 and later, this step is performed automatically when the AMI is booted and you don’t need to run it manually.]

sudo apt-get update && sudo apt-get install -y xfsprogs
sudo xfs_growfs /

The Ubuntu kernels built for EC2 by Canonical have XFS support built in, so XFS based EBS boot AMIs might be possible. This would also allow for more consistent EBS snapshots.

Toolset

Make sure you are running the latest version of the ec2-run-instances command. The current version can be determined with the command

ec2-version

To use EBS boot features, the version should be at least 1.3-45772.

[Updated 2009-12-11: Switch instructions to default us-east-1 since all regions now support this feature.]
[Updated 2011-12-13: Note that file system resize is done automatically on boot in Ubuntu 11.04 and later.]