Create an AMI from your own VM image

Creating your own AMI from scratch

Continuing the trend of AWS-related articles, this time I am looking at how to generate an Amazon Machine Image (AMI) from an ISO source.

Why?

There are a large number of publicly available AMIs. For example the ones owned by Amazon can be searched per region using

aws --region eu-central-1 ec2 describe-images --owner amazon

Those AMIs can be used as a basis for customized AMIs, generated for example using the excellent packer amazon-ebs builder.

Some times though there is a need to create an AMI from an existing virtualization source e.g. a .vmdk file from VirtualBox.

It would be great if we could create our customized OS image either through an interactive installation or in an automated way via Packer and import it as an AMI in EC2.

It turns out that this is possible using AWS ImportImage.

Prerequisites and limitations

  • Virtualization engine that can produce .ova or .vmdk artifacts. In this article I am focusing on virtualbox .vmdk artifacts and vagrant boxes (.box format)
  • The produced AMIs are suitable for HVM virtualization. pv requires more steps such as installing a pv enabled kernel.
  • Define an s3 bucket (in a region close to you, to speed up uploads.) This will be used to upload the images for conversion to AMI.
  • Define roles and policies in AWS. In particular:
  • A vmimport service role and a policy attached to it, precisely as explained in this AWS doc..
  • If you are an IAM AWS user (as opposed to root user) you also need to attach the following inline policy. Replace <youraccountid> with your own.
    {
    "Version": "2012-10-17",
    "Statement": [
    {
        "Sid": "380",
        "Effect": "Allow",
        "Action": [
            "iam:CreateRole",
            "iam:PutRolePolicy"
        ],
        "Resource": [
            "arn:aws:iam::<youraccountid>:role/vmimport"
        ]
    }
    ]
    }
        
  • Fast upstream bandwidth as you will be uploading the image to s3!

Manual steps

With the prerequisites satisfied the process is:

  1. Create your VM. Your vbox needs to have cloud-init installed and configured. RedHat based distributions include cloud-utils in the EPEL repo; the following script can be used to configure the expected login user as ec2-user:
    #!/bin/bash -eux
        yum install -y cloud-init cloud-utils-growpart dracut-modules-growroot
        cat >/etc/cloud/cloud.cfg <<-'EOF'
    users:
     - default
    disable_root: 1
    ssh_pwauth:   0
    locale_configfile: /etc/sysconfig/i18n
    mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
    resize_rootfs_tmp: /dev
    ssh_deletekeys:   0
    ssh_genkeytypes:  ~
    syslog_fix_perms: ~
    cloud_init_modules:
     - bootcmd
     - write-files
     - resizefs
     - set_hostname
     - update_hostname
     - update_etc_hosts
     - rsyslog
     - users-groups
     - ssh
    cloud_config_modules:
     - mounts
     - locale
     - set-passwords
     - timezone
     - runcmd
    cloud_final_modules:
     - scripts-per-once
     - scripts-per-boot
     - scripts-per-instance
     - scripts-user
     - ssh-authkey-fingerprints
     - keys-to-console
     - final-message
    system_info:
      distro: rhel
      default_user:
        name: ec2-user
      paths:
        cloud_dir: /var/lib/cloud
        templates_dir: /etc/cloud/templates
      ssh_svcname: sshd
    EOF
        
  2. When the job is complete describe-import-image-tasks will report Status: completed and the ImageId:
    {
      "Status": "completed",
      "LicenseType": "BYOL",
      "Description": "AMI FROM VMDK",
      "ImageId": "ami-XXXXXX",
      "Platform": "Linux",
      "Architecture": "x86_64",
      "SnapshotDetails": [
          {
              "DeviceName": "/dev/sda1",
              "Description": "AMIFROMVMDK",
              "Format": "VMDK",
              "DiskImageSize": 902047744.0,
              "SnapshotId": "snap-c768a943",
              "UserBucket": {
                  "S3Bucket": "mybucket",
                  "S3Key": "myvm.vmdk"
              }
          }
      ],
      "ImportTaskId": "import-ami-fh9g0yjn"
    }
        
  3. At this point we can use the new AMI. Note that the produced AMI will have some auto generated name and description; the ones we supplied in the aws cli command are used only by the import-image task. I could not find a way to modify the name/description, so we can just copy the image using:
    aws --region <destionation_region> --source-region <the_region_you_used_before> --source-image-id <produced_image_id> --name "My new OS" --description "Details of the AMI"
        
    Remember to deregister and clean up the associated snapshot id for the temporary AMI created by import-image.

Automating the process

The above steps can be tedious and since I needed to import vagrant boxes, I created a tool to automate this:

amiimport.py

The required parameters are:

  • --vboxfile Path to the vagrant .box file you wish to convert to AMI. Currently only virtualbox providers are supported.
  • --s3bucket [--s3key]

The s3bucket and the (temporary) key used for uploading the VM. s3key is optional but if you omit it, vboxfile expect a certain naming convention like osdistroVER-othermetadata.box For example ./oel7.1-x86_64-virtualbox.box is a valid name.

  • --verbose

Displays progress statistics. Very useful if the script is not run from another program.

By default it will create copies of the temporary AMI that AWS import-image creates in three regions -- us-east-1, us-west-2, eu-central-1. It's easy to add or remove destination regions in this list

Example

For an existing oracle linux vagrant box:

$ ./amiimporter.py --s3bucket mybucket --vboxfile ./oel7.1-x86_64-virtualbox.box --verbose
INFO:root:Uploading ./tmpdir/packer-virtualbox-iso-1453910880-disk1.vmdk to s3
99%
INFO:root:Running: aws --region eu-west-1 ec2 import-image --cli-input-json {"Description": "temp-hvm-oel-7-20160129134521", "DiskContainers": [{"UserBucket": {"S3Bucket": "mybucket", "S3Key": "temp-hvm-oel-7-20160129134521"}, "Description": "temp-hvm-oel-7-20160129134521"}]}
INFO:root:AWS is now importing vdmk to AMI.
98%
INFO:root:Done, amiid is ami-7c7bcc0f
INFO:root:Successfully created temporary AMI ami-7c7bcc0f
Created ami-8d322ae1 in region eu-central-1
Created ami-8d16f1ed in region us-west-2
Created ami-89e4c9e3 in region us-east-1
INFO:root:Deregistering temporary AMI ami-7c7bcc0f
INFO:root:Deleting updateded s3 file s3://mybucket/temp-hvm-oel-7-20160129134521

Warnings

If you use a Vagrant box as a source for your AMIs make sure that the vagrant user does not have the default password and/or insecure key as otherwise your deployed instances will be easily hacked.

Also, depending on the distribution, cloud-utils may be unable to resize your root partition automatically. This article/script may come handy.

Conclusion

Despite the fact that AWS public AMI store is very rich in images, there are always corner cases where you need to create something from scratch. I hope the process illustrated above is not too daunting and perhaps the included script will make it even easier!