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
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:
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:
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"