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         \
  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 karmic main" |
      sudo tee /etc/apt/sources.list.d/ubuntu-on-ec2-ec2-tools.list &&
    sudo apt-key adv --keyserver --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:

    if [ $(uname -m) = 'x86_64' ]; then
      kernelopts="--ec2-kernel=aki-fd15f694 --ec2-ramdisk=ari-c515f6ac"
      ebsopts="--kernel=aki-fd15f694 --ramdisk=ari-c515f6ac"
      ebsopts="$ebsopts --block-device-mapping /dev/sdb=ephemeral0"
      kernelopts="--ec2-kernel=aki-5f15f636 --ec2-ramdisk=ari-0915f660"
      ebsopts="--kernel=aki-5f15f636 --ramdisk=ari-0915f660"
      ebsopts="$ebsopts --block-device-mapping /dev/sda2=ephemeral0"
    cat > part-i386.txt <<EOM
    root 10240 a1
    /mnt 1 a2
    swap 1024 a3
    cat > part-x86_64.txt <<EOM
    root 10240 a1
    /mnt 1 b
  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
    # fix what I consider to be bugs in vmbuilder
    perl -pi -e "s%^*\n%%" $imagedir/etc/hosts
    rm -f $imagedir/etc/hostname
    # Use multiverse
    perl -pi -e 's%(universe)$%$1 multiverse%' \
    # Add Alestic PPA for runurl package (handy in user-data scripts)
    echo "deb karmic main" |
      tee $imagedir/etc/apt/sources.list.d/alestic-ppa.list
    chroot $imagedir \
      apt-key adv --keyserver --recv-keys BE09C571
    # Add ubuntu-on-ec2/ec2-tools PPA for updated ec2-ami-tools
    echo "deb karmic main" |
      sudo tee $imagedir/etc/apt/sources.list.d/ubuntu-on-ec2-ec2-tools.list
    chroot $imagedir \
      sudo apt-key adv --keyserver --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
    chmod 755 setup-server
  7. Build the image:

    now=$(date +%Y%m%d-%H%M)
    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             \

    [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
    sudo mkdir $ebsimage
    sudo mount /dev/sdi $ebsimage
    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.