Elastic Compute Cloud (EC2)

Get started with Amazon Elastic Compute Cloud (EC2) on LocalStack

Introduction

Elastic Compute Cloud (EC2) is a core service within Amazon Web Services (AWS) that provides scalable and flexible virtual computing resources. EC2 enables users to launch and manage virtual machines, referred to as instances.

LocalStack allows you to use the EC2 APIs in your local environment to create and manage EC2 instances and related resources such as VPCs, EBS volumes, etc. The list of supported APIs can be found on the API coverage page.

Getting started

This guide is designed for users new to EC2 and assumes basic knowledge of the AWS CLI and our awslocal wrapper script. We will demonstrate how to create an EC2 instance that runs a simple Python web server. LocalStack Pro running on a Linux host is required as network access to containers is not possible on macOS.

Start your LocalStack container using your preferred method.

Create or import a key pair

Key pairs are SSH public key/private key combinations that are used to log in to created instances.

To create a key pair, you can use the CreateKeyPair API. Run the following command to create the key pair and pipe the output to a file named key.pem:

$ awslocal ec2 create-key-pair \
    --key-name my-key \
    --query 'KeyMaterial' \
    --output text | tee key.pem

You may need to assign necessary permissions to the key files for security reasons. This can be done using the following commands:

$ chmod 400 key.pem
$acl = Get-Acl -Path "key.pem"
$fileSystemAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("$env:username", "Read", "Allow")
$acl.SetAccessRule($fileSystemAccessRule)
$acl.SetAccessRuleProtection($true, $false)
Set-Acl -Path "key.pem" -AclObject $acl
icacls.exe key.pem /reset
icacls.exe key.pem /grant:r "$($env:username):(r)"
icacls.exe key.pem /inheritance:r

If you already have an SSH public key that you wish to use, such as the one located in your home directory at ~/.ssh/id_rsa.pub, you can import it instead.

$ awslocal ec2 import-key-pair --key-name my-key --public-key-material file://~/.ssh/id_rsa.pub

If you only have the SSH private key, a public key can be generated using the following command, and then imported:

$ ssh-keygen -y -f id_rsa > id_rsa.pub

Add rules to your security group

Currently, LocalStack only supports the default security group. You can add rules to the security group using the AuthorizeSecurityGroupIngress API. Run the following command to add a rule to allow inbound traffic on port 8000:

$ awslocal ec2 authorize-security-group-ingress \
    --group-id default \
    --protocol tcp \
    --port 8000 \
    --cidr 0.0.0.0/0

The above command will enable rules in the security group to allow incoming traffic from your local machine on port 8000 of an emulated EC2 instance.

Run an EC2 instance

You can fetch the Security Group ID using the DescribeSecurityGroups API. Run the following command to fetch the Security Group ID:

$ awslocal ec2 describe-security-groups

You should see the following output:

{
    "SecurityGroups": [
        {
            "Description": "default VPC security group",
            "GroupName": "default",
            ...
            "OwnerId": "000000000000",
            "GroupId": "sg-0372ee3c519883079",
            ...
        }
    ]
}

To start your Python Web Server in your locally emulated EC2 instance, you can use the following user script by saving it to a file named user_script.sh:

#!/bin/bash -xeu

apt update
apt install python3 -y
python3 -m http.server 8000

You can now run an EC2 instance using the RunInstances API. Run the following command to run an EC2 instance by adding the appropriate Security Group ID that we fetched in the previous step:

$ awslocal ec2 run-instances \
    --image-id ami-df5de72bdb3b \
    --count 1 \
    --instance-type t3.nano \
    --key-name my-key \
    --security-group-ids '<SECURITY_GROUP_ID>' \
    --user-data file://./user_script.sh

Test the Python web server

You can now open the LocalStack logs to find the IP address of the locally emulated EC2 instance. Run the following command to open the LocalStack logs:

$ localstack logs

You should see the following output:

2023-08-16T17:18:29.702  INFO --- [   asgi_gw_0] l.s.ec2.vmmanager.docker   : Instance i-b07acefd77a3c415f will be accessible via SSH at: 127.0.0.1:12862, 172.17.0.4:22
2023-08-16T17:18:29.702  INFO --- [   asgi_gw_0] l.s.ec2.vmmanager.docker   : Instance i-b07acefd77a3c415f port mappings (container -> host): {'8000/tcp': 29043, '22/tcp': 12862}

You can now use the IP address to test the Python Web Server. Run the following command to test the Python Web Server:

$ curl 172.17.0.4:8000

You should see the following output:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
...

Connecting via SSH

You can also set up an SSH connection to the locally emulated EC2 instance using the instance IP address.

This section assumes that you have created or imported an SSH key pair named my-key (see instructions above). When running the EC2 instance, make sure to pass the --key-name parameter to the command:

$ awslocal ec2 run-instances --key-name my-key ...

Once the instance is up and running, we can use the ssh command to set up an SSH connection. Assuming the instance is available under 127.0.0.1:12862 (as per the LocalStack log output), use this command:

$ ssh -p 12862 -i key.pem root@127.0.0.1

VM Managers

LocalStack EC2 supports multiple methods to simulate the EC2 service. All tiers support the mock/CRUD capability. For advanced setups, LocalStack Pro comes with emulation capability for certain resource types so that they behave more closely like AWS.

The underlying method for this can be controlled using the EC2_VM_MANAGER configuration option. You may choose between plain mocked resources, containerized or virtualized.

Mock VM Manager

With the Mock VM manager, all resources are stored as in-memory representation. This only offers the CRUD capability.

This is the default VM manager in LocalStack Community edition. To use this VM manager in LocalStack Pro, set EC2_VM_MANAGER to mock.

This serves as the fallback manager if an operation is not implemented in other VM managers.

Docker VM Manager

LocalStack Pro supports the Docker VM manager which uses the Docker Engine to emulate EC2 instances. This VM manager requires the Docker socket from the host machine to be mounted inside the LocalStack container at /var/run/docker.sock.

This is the default VM manager in LocalStack Pro. You may set EC2_VM_MANAGER to docker to explicitly use this VM manager.

All launched EC2 instances have the Docker socket mounted inside them at /var/run/docker.sock to make Docker-in-Docker usecases possible.

All limitations associated with containers are also applicable to EC2 instances managed by the Docker manager. These restrictions include things like root access and networking.

Please note that this VM manager does not fully support persistence. While the records of resources will be persisted, the instances or AMIs themselves (i.e. Docker containers and Docker images) will not be persisted.

AMIs

LocalStack utilizes a specific naming scheme to recognize and manage associated containers and images. Docker base images which are tagged with the scheme localstack-ec2/<AmiName>:<AmiId> are recognized as Amazon Machine Images (AMIs).

You can mark any Docker base image as AMI using the below command:

$ docker tag ubuntu:focal localstack-ec2/ubuntu-focal-ami:ami-000001

The above example will make LocalStack treat the ubuntu:focal Docker image as an AMI with name ubuntu-focal-ami and ID ami-000001.

At startup, LocalStack downloads the following AMIs that can be used to launch Dockerized instances.

  • Ubuntu 22.04 ami-df5de72bdb3b
  • Amazon Linux 2023 ami-024f768332f0

All LocalStack-managed Docker AMIs bear the resource tag ec2_vm_manager:docker. These AMIs can be listed using:

$ awslocal ec2 describe-images --filters Name=tag:ec2_vm_manager,Values=docker

AWS does not provide an API to download AMIs. This prevents the use stock AWS AMIs on LocalStack. However, in certain cases it may be possible to tweak your AMI build process to target Docker images.

For example, suppose you use Packer to customise the Amazon Linux AMI on AWS. You can instead make Packer use the Docker builder instead of the Amazon builder and add the customisations on top of the Amazon Linux Docker base image. The final image then can be used by LocalStack EC2 as illustrated above.

Instances

When RunInstances is invoked, LocalStack creates an underlying Docker container to simulate an instance. Docker containers that back EC2 instances have the naming scheme localstack-ec2.<InstanceId>.

LocalStack EC2 supports execution of user data scripts when the instance starts. A shell script can be passed to the UserData argument of RunInstances. Alternatively, the user data may also be added using the ModifyInstanceAttribute operation.

The user data is placed at /var/lib/cloud/instances/<InstanceId>/ in the container. The execution log is generated at /var/log/cloud-init-output.log in the container.

Networking

Network addresses for Dockerized instances are allocated by the Docker daemon and can be obtained from the PublicIpAddress attribute. These addresses are also printed in the logs while the instance is being initialized.

2022-03-21T14:46:49.540  INFO  Instance i-1d6327abf04e31be6 will be accessible via SSH at: 127.0.0.1:55705

When instances are launched, LocalStack attempts to start SSH server /usr/sbin/sshd in the Docker base image. If not found, it installs and starts the Dropbear SSH server.

To be able to access the instance at additional ports from the host system, you can modify the default security group and incorporate the needed ingress ports.

The system supports up to 32 ingress ports. This constraint is in place to prevent the host from exhausting available ports.

$ awslocal ec2 authorize-security-group-ingress \
  --group-id default \
  --protocol tcp \
  --port 8080
$ awslocal ec2 describe-security-groups \
  --group-names default

The port mapping details are provided in the logs during the instance initialization process.

2022-12-20T19:43:44.544  INFO  Instance i-1d6327abf04e31be6 port mappings (container -> host): {'8080/tcp': 51747, '22/tcp': 55705}

Elastic Block Store

A common use case is to attach an EBS block device to an EC2 instance, which can then be used to create a custom filesystem for additional storage. This section illustrates how this functionality can be achieved with EC2 Docker instances in LocalStack.

First, we create a user data script init.sh which creates an ext3 file system on the block device /ebs-dev/sda1 and mounts it under /ebs-mounted:

$ cat > init.sh <<EOF
#!/bin/bash

set -eo
mkdir -p /ebs-mounted
mkfs -t ext3 /ebs-dev/sda1
mount -o loop /ebs-dev/sda1 /ebs-mounted
touch /ebs-mounted/my-test-file
EOF

We can then start an EC2 instance, specifying a block device mapping under the device name /ebs-dev/sda1, and pointing to our init.sh user data script:

$ awslocal ec2 run-instances --image-id ami-ff0fea8310f3 --count 1 --instance-type t3.nano \
    --block-device-mapping '{"DeviceName":"/ebs-dev/sda1","Ebs":{"VolumeSize":10}}' \
    --user-data file://init.sh

Please note that, whereas real AWS uses GiB for volume sizes, LocalStack uses MiB as the unit for VolumeSize in the command above (to avoid creating huge files locally). Also, by default block device images are limited to 1 GiB in size, but this can be customized by setting the EC2_EBS_MAX_VOLUME_SIZE config variable (defaults to 1000).

Once the instance is successfully started and initialized, we can first determine the container ID via docker ps, and then list the contents of the mounted filesystem /ebs-mounted, which should contain our test file named my-test-file:

$ docker ps
CONTAINER ID   IMAGE                  PORTS           NAMES
5c60cf72d84a   ...:ami-ff0fea8310f3   19419->22/tcp   localstack-ec2...
$ docker exec 5c60cf72d84a ls /ebs-mounted
my-test-file

Instance Metadata Service

The Docker VM manager supports the Instance Metadata Service which provides information about the running instance.

Both IMDSv1 and IMDSv2 can be used. LocalStack does not strictly enforce either versions. If the X-aws-ec2-metadata-token header is present, LocalStack will use IMDSv2, otherwise it will fall back to IMDSv1.

To create an IMDSv2 token, run the following inside the EC2 container:

$ curl -X PUT "http://169.254.169.254/latest/api/token" -H "x-aws-ec2-metadata-token-ttl-seconds: 300"

The token can be used in subsequent requests like so:

$ curl -H "x-aws-ec2-metadata-token: <TOKEN>" -v http://169.254.169.254/latest/meta-data/

Currently a limited set of metadata categories are implemented. If you would like support for more metadata categories, please make a feature request on GitHub.

Configuration

You can use the EC2_DOCKER_FLAGS LocalStack configuration variable to pass supplementary flags to Docker during the initiation of containerized instances. This allows for fine-tuned behaviours, for example, running containers in privileged mode using --privileged or specifying an alternate CPU platform with --platform. Keep in mind that this will apply to all instances that are launched in the LocalStack session.

Operations

The following table explains the emulated action for various API operations. Any operation not listed below will use the mock VM manager.

OperationNotes
CreateImageUses Docker commit to capture a snapshot of a running instance into a new AMI
DescribeImagesRetrieves a list of Docker images that can be used as AMIs
DescribeInstancesDescribes both mocked and Docker-backed instances. Docker-backed instances are marked with the resource tag ec2_vm_manager:docker
RunInstancesCreates and runs Docker containers that back instances
StopInstancesPauses the Docker containers that back instances
StartInstancesResumes the Docker containers that back instances
TerminateInstancesStops the Docker containers that back instances

Libvirt VM Manager

The Libvirt VM manager uses the Libvirt API to create fully virtualized EC2 resources. This lets you create EC2 setups which closely resemble AWS EC2. Currently LocalStack Pro supports the KVM-accelerated QEMU hypervisor on Linux hosts.

Installation steps for QEMU/KVM will vary based on the Linux distribution on the host machine. On Debian/Ubuntu-based distributions, you can run:

$ sudo apt install -y qemu-kvm libvirt-daemon-system

To check CPU support for virtualization, run:

$ kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used

If the Docker host and Libvirt host is the same, the Libvirt socket on the host must be mounted inside the LocalStack container. This can be done by including the volume mounts when the LocalStack container is started. If you are using the Docker Compose template, include the following line in services.localstack.volumes list:

"/var/run/libvirt/libvirt-sock:/var/run/libvirt/libvirt-sock"

If you are using Docker CLI, include the following parameter in docker run:

-v /var/run/libvirt/libvirt-sock:/var/run/libvirt/libvirt-sock

If you are using a remote Libvirt hypervisor, you can set the EC2_HYPERVISOR_URI config option with a connection URI.

The Libvirt VM manager currently does not have full support for persistence. Underlying virtual machines and volumes are not persisted, only their mock respresentations are.

AMIs

All qcow2 images with cloud-init support can be used as AMIs.

LocalStack does not come preloaded with any AMIs. You can find the download links for images of popular OSs below:

Canonical provides official Ubuntu images at cloud-images.ubuntu.com.

Please use the images in qcow2 format ending in .img.

Debian provides cloud images for direct download at cdimage.debian.org/cdimage/cloud.

Please use the genericcloud image in qcow2 format.

The Fedora project maintains the official cloud images at fedoraproject.org/cloud/download.

Please use the qcow2 images.

An evaluation version of Windows Server 2012 R2 is provided by Cloudbase Solutions.

Compatible qcow2 images must be placed at the default Libvirt storage pool at /var/lib/libvirt/images on the host machine. Images must be named with the prefix ami- followed by at least 8 hexadecimal characters without an extension, e.g. ami-1234abcd. You may need run the following command to make sure the image is registered with Libvirt:

$ virsh pool-refresh default
Pool default refreshed
$ virsh vol-list --pool default
Name Path -------------------------------------------------------------------------------------------------------- ami-1234abcd /var/lib/libvirt/images/ami-1234abcd

Only the images that follow the above naming scheme will be recognised by LocalStack as AMIs suitable for launching virtualized instances. These AMIs will also have the resource tag ec2_vm_manager:libvirt.

$ awslocal ec2 describe-images --filters Name=tag:ec2_vm_manager,Values=libvirt

Instances

Virtualized instances can be launched with RunInstances operation and specifying a compatible AMI. LocalStack will create and start a Libvirt domain to represent the instance.

When instances are launched, LocalStack uses the NoCloud datasource to customize the virtual machine. The login user is created with the username localstack and password localstack. If a key pair is provided, it will added as an authorised SSH key for this user.

LocalStack shuts down all virtual machines when it terminates. The Libvirt domains and volumes are left defined and can be used for debugging, etc.

The Libvirt VM manager supports basic shell scripts for user data. This can be passed to the UserData parameter of the RunInstances operation.

To connect to the graphical display of the instance, first obtain the VNC address using:

$ virsh vncdisplay <instance ID>
127.0.0.1:0

You can then use a compatible VNC client (e.g. TigerVNC) to connect and interact with the virtual machine.

Tiger VNC

Networking

All instances are assigned interfaces on the default Libvirt network. This makes it possible to have host/instance as well as instance/instance network communication.

It is possible to allow network access to the LocalStack container from within the virtualized instance. This is done by configuring the Docker daemon to use the KVM network. Use the following configuration at /etc/docker/daemon.json on the host machine:

{
  "bridge": "virbr0",
  "iptables": false
}

Then restart the Docker daemon:

$ sudo systemctl restart docker

You can now start the LocalStack container, obtain its IP address and use it from the virtualized instance.

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' localstack_main

Elastic Block Stores

LocalStack clones the AMI into an EBS volume when the instance is initialised. LocalStack does not resize the instance root volume, instead it inherits the properties of the AMI.

Currently it is not possible to attach additional EBS volumes to instances.

Instance Metadata Service

The Libvirt VM manager does not support the Instance Metadata Service endpoints.

Operations

The following table explains the emulated action for various API operations. Any operation not listed below will use the mock VM manager.

OperationNotes
DescribeImagesReturns all mock and Libvirt AMIs
RunInstancesDefines and starts a Libvirt domain
StartInstancesStarts an already defined Libvirt domain
StopInstancesStops a running Libvirt domain
RebootInstancesRestarts a Libvirt domain
TerminateInstancesStops and undefines a Libvirt domain
CreateVolumeCreates a sparse Libvirt volume

Resource Browser

The LocalStack Web Application provides a Resource Browser for managing EC2 instances. You can access the Resource Browser by opening the LocalStack Web Application in your browser, navigating to the Resources section, and then clicking on EC2 under the Compute section.

EC2 Resource Browser

The Resource Browser allows you to perform the following actions:

  • Create Instance: Create a new EC2 instance by clicking the Launch Instance button and specifying the AMI ID, instance type, and other parameters.
  • View Instance: View the details of an EC2 instance by clicking on the Instance ID.
  • Terminate Instance: Terminate an EC2 instance by selecting the Instance ID, and clicking on the ACTIONS button followed by clicking on Terminate Selected.
  • Start Instance: Start a stopped EC2 instance by selecting the Instance ID, and clicking on the ACTIONS button followed by clicking on Start Selected.
  • Stop Instance: Stop a running EC2 instance by selecting the Instance ID, and clicking on the ACTIONS button followed by clicking on Stop Selected.
Last modified November 21, 2024: v4 docs (#1547) (88b4e1798)