Skip to content

Run EC2 VMDK image in Oracle VirtualBox on EC2 Metal Instance

All the scripts in this article are bash scripts

Create VMDK image

Clone the aws-ec2-ami-packer repository and follow the instructions in the README to create a Virtual Machine Disk (VMDK) from an EC2 Instance.

Then follow the steps bellow to run the VMDK in VirtualBox in an EC2 Metal instance.

Create working directory

mkdir -p ~/tmp/vmdk-on-ec2
cd ~/tmp/vmdk-on-ec2

Start an EC2 Metal Instance

Start an AWS c5.metal instance:

# Set variables
key_name="aws-ec2-key"

region="us-west-2"
instance_type="c5.metal"
instance_name="ec2-vmdk-test"
security_group_name="ec2-vmdk-test-sg"

# Get the latest Ubuntu 22.04 AMI ID
ami_id=$(aws ec2 describe-images \
    --owners 099720109477 \
    --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
              "Name=state,Values=available" \
    --query "sort_by(Images, &CreationDate)[-1].ImageId" \
    --output text \
    --region $region)

echo "Using Ubuntu 22.04 AMI: $ami_id"

# Get the default VPC ID
default_vpc_id=$(aws ec2 describe-vpcs \
    --filters "Name=isDefault,Values=true" \
    --query "Vpcs[0].VpcId" \
    --output text \
    --region $region)

echo "Using default VPC: $default_vpc_id"

# Create a security group that allows SSH access
security_group_id=$(aws ec2 create-security-group \
    --group-name $security_group_name \
    --description "Security group for SSH access" \
    --vpc-id $default_vpc_id \
    --region $region \
    --output text \
    --query 'GroupId')

echo "Created security group: $security_group_id"

# Add a rule to allow SSH access
aws ec2 authorize-security-group-ingress \
    --group-id $security_group_id \
    --protocol tcp \
    --port 22 \
    --cidr 0.0.0.0/0 \
    --region $region

# Launch the EC2 instance in the default subnet of the default VPC
instance_id=$(aws ec2 run-instances \
    --image-id $ami_id \
    --count 1 \
    --instance-type $instance_type \
    --key-name $key_name \
    --security-group-ids $security_group_id \
    --region $region \
    --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$instance_name}]" \
    --query 'Instances[0].InstanceId' \
    --output text)

echo "Launched EC2 instance: $instance_id with Name tag: $instance_name"

# Wait for the instance to be running
aws ec2 wait instance-running --instance-ids $instance_id --region $region

instance_public_ip=$(aws ec2 describe-instances --instance-ids $instance_id --query 'Reservations[0].Instances[0].PublicIpAddress' --output text --region $region)

echo "Instance ID: $instance_id"
echo "Instance Public IP: $instance_public_ip"

Allow S3 Access

# Set variables
region="us-west-2"

s3_bucket="nginx-server-packer-images"

iam_role_name="ec2-vmdk-test-s3-access-role"
instance_profile_name="ec2-vmdk-test-s3-access-profile"

instance_name="ec2-vmdk-test"

# Get the instance ID by name
instance_id=$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=$instance_name" "Name=instance-state-name,Values=running" \
    --query "Reservations[0].Instances[0].InstanceId" \
    --output text \
    --region $region)

# Create IAM role
aws iam create-role \
    --role-name $iam_role_name \
    --assume-role-policy-document '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Service": "ec2.amazonaws.com"
                },
                "Action": "sts:AssumeRole"
            }
        ]
    }' \
    --region $region

# Attach S3 access policy to the role
aws iam put-role-policy \
    --role-name $iam_role_name \
    --policy-name S3AccessPolicy \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject",
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::'"$s3_bucket"'",
                    "arn:aws:s3:::'"$s3_bucket"'/*"
                ]
            }
        ]
    }' \
    --region $region

# Create an instance profile and add the role to it
aws iam create-instance-profile --instance-profile-name $instance_profile_name --region $region
aws iam add-role-to-instance-profile --instance-profile-name $instance_profile_name --role-name $iam_role_name --region $region

echo "Created IAM role and instance profile for S3 access"

# Attach the IAM role to the EC2 instance
aws ec2 associate-iam-instance-profile \
    --instance-id $instance_id \
    --iam-instance-profile Name=$instance_profile_name \
    --region $region

echo "Attached IAM role to EC2 instance: $instance_id"

Connect to the EC2 Instance

key="aws-ec2-key"

instance_public_ip=$(aws ec2 describe-instances \
                --filters Name=tag:Name,Values=$instance_name \
            | jq -r '.Reservations[-1].Instances[-1].PublicIpAddress')

ssh -i ~/.ssh/$key ubuntu@$instance_public_ip

Install additional software

Run this on the EC2 Instance:

sudo apt update -y
sudo apt install -y unzip
sudo apt install -y genisoimage
sudo apt install -y virtualbox

Install AWS CLI v2

Run this on the EC2 Instance:

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

Download the VMDK image

Run this on the EC2 Instance:

s3_bucket="nginx-server-packer-images"
s3_prefix="exports/"
s3_filename="nginx-server-packer.vmdk"

# Download the VMDK file from S3
echo "Downloading VMDK file from S3..."
aws s3 cp s3://$s3_bucket/$s3_prefix$s3_filename .

Create a Virtual Machine

Create and start a new VM

# Set variables
vm_name="nginx-server-packer"
vmdk_path="nginx-server-packer.vmdk"
vm_ram=4096
vm_cpus=2

# The port on your EC2 instance to forward to the VM's port 22
host_port=2222  

cloud_init_dir=~/cloud-init
cloud_init_iso=~/cloud-init.iso
ssh_key_path=~/.ssh/${vm_name}_ssh_key


# Generate SSH key
ssh-keygen -t rsa -b 2048 -f "$ssh_key_path" -N "" -C "key for $vm_name"
pub_key=$(cat "${ssh_key_path}.pub")

# Create cloud-init configuration
mkdir -p $cloud_init_dir

# Create user-data file
# This file must begin with #cloud-config in order to be valid
cat << EOF > $cloud_init_dir/user-data
#cloud-config

users:
  - default

output:
  all: ">> /var/log/cloud-init-output.log"

ssh_authorized_keys:
  - $pub_key
EOF

# Create a simple meta-data file
cat << EOF > $cloud_init_dir/meta-data
local-hostname: $vm_name
EOF

# Create cloud-init ISO
genisoimage -output "$cloud_init_iso" -volid cidata -joliet -rock "$cloud_init_dir/user-data" "$cloud_init_dir/meta-data"

# Create VM
echo "Creating new VM: $vm_name"
VBoxManage createvm --name "$vm_name" --ostype "Ubuntu_64" --register
VBoxManage modifyvm "$vm_name" --memory $vm_ram --cpus $vm_cpus
VBoxManage storagectl "$vm_name" --name "SATA Controller" --add sata --controller IntelAHCI
VBoxManage storageattach "$vm_name" --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium "$vmdk_path"

# Attach cloud-init ISO
VBoxManage storageattach "$vm_name" --storagectl "SATA Controller" --port 1 --device 0 --type dvddrive --medium "$cloud_init_iso"

# Set up port forwarding
VBoxManage modifyvm "$vm_name" --natpf1 "guestssh,tcp,,$host_port,,22"

# Start the VM
echo "Starting VM $vm_name"
VBoxManage startvm "$vm_name" --type headless

# Wait for VM to boot
echo "Waiting for VM to boot..."
sleep 30

Update ssh key permissions:

chmod 600 $ssh_key_path
To login to the VM:

# remove the old host key from your known_hosts file to avoid SSH warnings.
ssh-keygen -R [localhost]:2222

# ssh using the key that we setup in the machine
ssh -p $host_port -i $ssh_key_path ubuntu@localhost

To stop the VM:

VBoxManage controlvm $vm_name poweroff

Cleanup

# Set variables
region="us-west-2"

instance_name="ec2-vmdk-test"

security_group_name="ec2-vmdk-test-sg"

iam_role_name="ec2-vmdk-test-s3-access-role"
instance_profile_name="ec2-vmdk-test-s3-access-profile"

# Get instance ID
instance_id=$(aws ec2 describe-instances \
        --filters "Name=tag:Name,Values=$instance_name" "Name=instance-state-name,Values=running,stopped" \
        --query "Reservations[0].Instances[0].InstanceId" \
        --output text \
        --region $region)

echo "Terminating EC2 instance: $instance_id"
aws ec2 terminate-instances --instance-ids $instance_id --region $region

echo "Waiting for instance to terminate..."
aws ec2 wait instance-terminated --instance-ids $instance_id --region $region

echo "Instance terminated successfully"

# Get security group ID
security_group_id=$(aws ec2 describe-security-groups \
        --group-names "$security_group_name" \
        --query "SecurityGroups[0].GroupId" \
        --output text \
        --region $region)

echo "Deleting security group: $security_group_id"
aws ec2 delete-security-group --group-id $security_group_id --region $region

echo "Security group deleted successfully"

# Remove instance profile from IAM role
echo "Removing instance profile from IAM role"
aws iam remove-role-from-instance-profile \
    --instance-profile-name $instance_profile_name \
    --role-name $iam_role_name \
    --region $region

# Delete instance profile
echo "Deleting instance profile: $instance_profile_name"
aws iam delete-instance-profile \
    --instance-profile-name $instance_profile_name \
    --region $region

# Detach policy from IAM role
echo "Detaching policy from IAM role"
aws iam list-role-policies --role-name $iam_role_name --region $region | \
    jq -r '.PolicyNames[]' | while read policy_name; do
    aws iam delete-role-policy \
        --role-name $iam_role_name \
        --policy-name $policy_name \
        --region $region
done

# Delete IAM role
echo "Deleting IAM role: $iam_role_name"
aws iam delete-role --role-name $iam_role_name --region $region

echo "Cleanup complete"