Skip to main content

Creating and accessing an AWS EC2 instance on AWS Wavelength

DevRel | 04 Jul 2024
10 minutes reading time


Following the blog post about how 5G and Multi-access Edge Computing (MEC) can help reduce latency on applications, the idea of this walkthrough is to guide developers in creating their own environment in Vodafone's edge network, with the help of AWS Wavelength Zones. Vodafone is a unique partner to AWS in that it is the first one to offer commercially available AWS Wavelength deployment across multiple edge zones and countries (London - Manchester in the UK and Berlin - Dortmund - Munich in Germany) and AWS Regions (eu-west-2 and eu-central-1).

To learn more about AWS Wavelength Zones and how it can help deliver lower latency on your applications, you can go to this link: AWS Wavelength FAQs.

It should take between 15 and 20 minutes to complete the steps listed on this article.

Prerequisites

To complete this guide you will need:

  • A device on Vodafone's network (a mobile or a computer connected to a Vodafone 5G hotspot, for example).

    • Attention: currently, AWS Wavelength Zones are only available in England and Germany.

    • The best way to experience the benefits of Vodafone's MEC in the UK is by using an IoT SIM card, but a regular Vodafone SIM card will work as well. Information on how to acquire an IoT SIM card can be found here.

  • An active AWS account.

  • AWS CLI installed and configured. Here is the official installation/update guide for AWS CLI.

  • AWS Wavelength Zones are not automatically available and need to be opted in. This guide describes how to do that.

Warning

  1. Not all of the AWS resources utilised during this exercise qualify for AWS' Free-Tier, so be aware that there will be charges to your account. To reduce costs, it is strongly advised that any EC2 instances not being used are kept in a "STOPPED" state. Using two EC2 instances (one at an AWS Wavelength Zone and one at eu-west-2), one carrier IP and one VPC would generate an approximate cost of 6.19 USD per month. This cost is based on certain assumptions and might vary depending on your own usage and needs. A summary of this estimate can be found here. You can learn about pricing assumptions here.

  2. EC2 instances deployed on AWS Wavelength Zones have a few constraints. The main ones for this walkthrough are:

    1. UDP traffic between the EC2 instance and the internet is not allowed and only outbound and the response is allowed for TCP. Between the EC2 instance and a device on the carrier network, all traffic is allowed.

    2. Only the following instance types are supported:

      • t3.medium

      • t3.xlarge

      • r5.2xlarge

      • g4dn.2xlarge

    3. EBS volumes must be gp2.

A full list of considerations can be found on this link.

Let's do this!

Now that all of that is out of the way, let's talk architecture. As mentioned, the EC2 instance deployed on the AWS Wavelength Zone does not accept direct connections from the internet, so a bastion server needs to be put in place to allow communication with the instance. Alternatively, the AWS Systems Manager can be used to access an instance deployed on an AWS Wavelength Zone, however that requires configuring roles at AWS IAM. A full guide on how to set this up can be found here.

For this walkthrough the bastion option will be used and the final architecture will look like this:


But first things first: before any instances is deployed, some bits and pieces need to be in place:

Virtual Private Cloud (VPC)

First, a VPC and associated resources need to be created for the servers to be deployed in. The following command will create one at AWS' eu-west-2 (London) region:

export VPC_ID=$(aws ec2 create-vpc \
--region eu-west-2 \
--cidr-block 10.0.0.0/16 \
--output text \
--query 'Vpc.VpcId')

Next is to create an internet gateway and attach it to the VPC from the previous step:

export IGW_ID=$(aws ec2 create-internet-gateway \
--region eu-west-2 \
--output text \
--query 'InternetGateway.InternetGatewayId')

aws ec2 attach-internet-gateway \
--region eu-west-2 \
--vpc-id $VPC_ID \
--internet-gateway-id $IGW_ID

And finally for this section, a carrier gateway needs to be created. As explained here, a carrier gateway servers two purposes: 

  • It allows inboud traffic from a carrier network in a specific location, and

  • it allows outbound traffic to the carrier network and the internet.

As previously mentioned, there is no inboud connections from the internet to an AWS Wavelength Zone through the carrier gateway.

The command below will create the needed carrier gateway:

export CAGW_ID=$(aws ec2 create-carrier-gateway \
--region eu-west-2 \
--output text \
--vpc-id $VPC_ID \
--query 'CarrierGateway.CarrierGatewayId') 

Security groups

This section contains a step-by-step guide on how to create Security Groups, which are used to control inbound and outbound traffic for associated resources.. 

For this architecture, two Security Groups will be created: one for the bastion host and another for the application server.

The commands below create the first Security Group, for the bastion host, and then add a rule allowing ingress TCP traffic through port 22 (ssh protocol) for one specific IP address (your own). All outbound traffic is allowed.

export BASTION_SG_ID=$(aws ec2 create-security-group \
--region eu-west-2 \
--vpc-id $VPC_ID \
--group-name bastion-sg \
--description "Security group for bastion host" \
--output text \
--query 'GroupId')

export OWN_IP=$(curl https://checkip.amazonaws.com)

aws ec2 authorize-security-group-ingress \
--region eu-west-2 \
--group-id $BASTION_SG_ID \
--protocol tcp \
--port 22 \
--cidr $OWN_IP/32

Now, the Security Group for the application server. It will contain two rules:

  • one allowing ingress TCP traffic through port 22 (ssh protocol) for one specific Security Group: the one to which the bastion host belongs to; and

  • one allowing ingress TCP traffic through port 8181 for all IPs. That port is for an application to listen to for incoming connections. If your application uses a different port, feel free to change it in the respective command.

All outbound traffic is allowed.

Here are the commands:

export APP_SG_ID=$(aws ec2 create-security-group \
--region eu-west-2 \
--vpc-id $VPC_ID \
--group-name app-sg \
--description "Security group for application server" \
--output text \
--query 'GroupId')

aws ec2 authorize-security-group-ingress \
--region eu-west-2 \
--group-id $APP_SG_ID \
--protocol tcp \
--port 22 \
--source-group $BASTION_SG_ID

aws ec2 authorize-security-group-ingress \
--region eu-west-2 \
--group-id $APP_SG_ID \
--protocol tcp \
--port 8181 \
--cidr 0.0.0.0/0

Subnets and Routing Tables

For the next steps, the network will continue to be configured so that once the instances are deployed, they are accessible right away.

Firstly, let's create the subnet for the AWS Wavelength Zone, its route table, associate them and at the end create a route so that traffic is correctly routed to the carrier gateway.

export WL_SUBNET_ID=$(aws ec2 create-subnet \
--region eu-west-2 \
--cidr-block 10.0.0.0/24 \
--availability-zone eu-west-2-wl1-lon-wlz-1 \
--vpc-id $VPC_ID \
--output text \
--query 'Subnet.SubnetId')

export WL_RT_ID=$(aws ec2 create-route-table \
--region eu-west-2 \
--vpc-id $VPC_ID \
--output text \
--query 'RouteTable.RouteTableId')

aws ec2 create-route \
--region eu-west-2 \
--route-table-id $WL_RT_ID \
--destination-cidr-block 0.0.0.0/0 \
--carrier-gateway-id $CAGW_ID

export WL_RT_ASSOC_ID=$(aws ec2 associate-route-table \
--region eu-west-2 \
--route-table-id $WL_RT_ID \
--subnet-id $WL_SUBNET_ID \
--output text \
--query 'AssociationId')

With that completed, now it is time to create a subnet, a route table and a route for the bastion host:

BASTION_SUBNET_ID=$(aws ec2 create-subnet \
--region eu-west-2 \
--cidr-block 10.0.1.0/24 \
--vpc-id $VPC_ID \
--output text \
--query 'Subnet.SubnetId')

export BASTION_RT_ID=$(aws ec2 create-route-table \
--region eu-west-2 \
--vpc-id $VPC_ID \
--output text \
--query 'RouteTable.RouteTableId') \

aws ec2 create-route \
--region eu-west-2 \
--route-table-id $BASTION_RT_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID

export BASTION_RT_ASSOC_ID=$(aws ec2 associate-route-table \
--region eu-west-2 \
--subnet-id $BASTION_SUBNET_ID \
--route-table-id $BASTION_RT_ID \
--output text \
--query 'AssociationId')

Lastly, a final configuration on the bastion's subnet to ensure the host can get a public IP address by default:

aws ec2 modify-subnet-attribute \
--region eu-west-2 \
--subnet-id $BASTION_SUBNET_ID \
--map-public-ip-on-launch

Elastic IP and Network Interface

To finalise the environment for the instances, an IP associated with Vodafone's network and an associated Elastic Network Interface (ENI) are necessary for the application server. The bastion host will have a public IP address, as per the last command executed.

Here are the commands for the Elastic IP:

export APP_CIP_ALLOC_ID=$(aws ec2 allocate-address \
--region eu-west-2 \
--domain vpc \
--network-border-group eu-west-2-wl1-lon-wlz-1 \
--output text \
--query 'AllocationId')

And for the ENI:

export APP_ENI_ID=$(aws ec2 create-network-interface \
--region eu-west-2 \
--subnet-id $WL_SUBNET_ID \
--groups $APP_SG_ID \
--output text \
--query 'NetworkInterface.NetworkInterfaceId')

And finally, to associate the IP with the ENI:

export ASSOC_ID=$(aws ec2 associate-address \
--region eu-west-2 \
--allocation-id $APP_CIP_ALLOC_ID \
--network-interface-id $APP_ENI_ID \
--output text \
--query 'AssociationId')

Instances

With the foundations in place, it is now time to deploy the instance.

A key pair is needed to connect into the hosts. If one is not available, a new one can be created with this command:

export KEY_NAME=VFWLTutorial

aws ec2 create-key-pair \
--key-name $KEY_NAME \
--key-type "rsa" \
--key-format "pem" \
--output text \
--query 'KeyMaterial' \
>> ./privKey.pem

# need to change the permissions on the .pem file for it to work properly:
chmod 400 ./privKey.pem

The above will download the key into the current directory, save it as privKey.pem and change its permissions.

Here are some details about the choices made for the instances about to be deployed:

  • Bastion host:

    • Instance type: t2.micro. Cheapest ("Free-Tier" eligible) instance available as it will only be used as a bridge to the application server. More information on the instance can be found here. More information on AWS Free-Tier is here.

    • Image: ami-0c618421e207909d0 (Amazon Linux 2023)

    • Key name: name of the key created to connect to the instance. Check here for more details.

  • App server:

    • InsInstance type: t3.medium. Cheapest (not eligible for “Free-Tier”) instance available for deployment on an AWS Wavelength zone. More information on the instance can be found here.

    • Image: ami-0c618421e207909d0 (Amazon Linux 2023)

    • Volume type: gp2. Only type of volume allowed on an AWS Wavelength Zone.

    • Key name: name of the key created to connect to the instance. Check here for more details.

Here are the commands for the bastion host:

export BASTION_INST_ID=$(aws ec2 run-instances \
--region eu-west-2 \
--instance-type t2.micro \
--image-id ami-0c618421e207909d0  \
--subnet-id $BASTION_SUBNET_ID \
--associate-public-ip-address \
--security-group-ids $BASTION_SG_ID \
--key-name $KEY_NAME \
--output text \
--query 'Instances[0].InstanceId')

export BASTION_PUB_IP=$(aws ec2 describe-instances \
--instance-ids $BASTION_INST_ID \
--output text \
--query 'Reservations[*].Instances[*].PublicIpAddress')

This last command stores the external IP address of the bastion server so it can be easily accessed via SSH.

Now, for the application server:

export APP_INST_ID=$(aws ec2 run-instances \
--region eu-west-2 \
--instance-type t3.medium \
--network-interface '[{"DeviceIndex":0,"NetworkInterfaceId":"'$APP_ENI_ID'"}]' \
--image-id ami-0c618421e207909d0  \
--key-name $KEY_NAME \
--block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"Encrypted":false,"DeleteOnTermination":true,"VolumeSize":8,"VolumeType":"gp2"}}]' \
--output text \
--query 'Instances[0].InstanceId')

export APP_PRIV_IP=$(aws ec2 describe-instances \
--instance-ids $APP_INST_ID \
--output text \
--query 'Reservations[*].Instances[*].PrivateIpAddress')

As mentioned on the beginning of this guide, any instance deployed on an AWS Wavelength Zone cannot be reached via SSH through the open internet, therefore the internal IP address of the application server is needed so the connection can happen via the bastion server, as they are both in the same VPC.

As a security measure, we will be using the -A flag of the ssh command which enables forwarding the connections from an authentication agent. That way, there is no need to transfer the key to the bastion server. For that, ssh-agent needs to be running and the private key added to the authentication agent. These  can be achieved by the following commands:

eval "$(ssh-agent)"
ssh-add ./privKey.pem 

Finally, using the bastion host as a hop, here is how to connect to the application server. Make sure to make a note of the application server private IP:

echo ${APP_PRIV_IP}

The -A flag enables forwarding of connections from an authentication agent. A security prompt about the host not being in the list of known hosts might show up before the connection is completed.

ssh -i ./privKey.pem -A ec2-user@$BASTION_PUB_IP

Next, ssh into the application server, using its internal IP address (APP_PRIV_IP; 10.0.0.24 in this example, could be different in your implementation)

ssh ec2-user@10.0.0.24

If your terminal screen looks like the image below, it means you did it. Congratulations!

Terminal screen displaying connection between bastion host and the application server
Figure 2: successful connection between bastion host and application server

 

Clean Up (Optional)

Here I will share the commands needed to remove all the instances and products created above. Note that these commands must be executed in the same terminal session as the ones done throughout this guide, as the environment variables are session specific.

First the instances need to be terminated:

aws ec2 terminate-instances --instance-ids $APP_INST_ID $BASTION_INST_ID

The following commands will only succeed once both instances have been terminated. To check if that is the case, repeat the previous command until the output looks like this, i.e. "CurrentState.Name: terminated", for both instances:

{
    "TerminatingInstances": [
        {
            "CurrentState": {
                "Code": 48,
                "Name": "terminated"
            },
            "InstanceId": "i-042804544c4023f17",
            "PreviousState": {
                "Code": 48,
                "Name": "terminated"
            }
        },
        {
            "CurrentState": {
                "Code": 48,
                "Name": "terminated"
            },
            "InstanceId": "i-0b6fa8edd6bbd0a6c",
            "PreviousState": {
                "Code": 48,
                "Name": "terminated"
            }
        }
    ]
}

Once that happens, execute the following commands:

aws ec2 delete-key-pair --key-name $KEY_NAME
rm -fv ./privKey.pem 
aws ec2 disassociate-address --association-id $ASSOC_ID
aws ec2 delete-network-interface --network-interface-id $APP_ENI_ID
aws ec2 disassociate-route-table --association-id $BASTION_RT_ASSOC_ID 
aws ec2 delete-route-table --route-table-id $BASTION_RT_ID
aws ec2 delete-subnet --subnet-id $BASTION_SUBNET_ID
aws ec2 disassociate-route-table --association-id $WL_RT_ASSOC_ID         
aws ec2 delete-route-table --route-table-id $WL_RT_ID
aws ec2 delete-subnet --subnet-id $WL_SUBNET_ID
aws ec2 delete-security-group --group-id $BASTION_SG_ID 
aws ec2 delete-security-group --group-id $APP_SG_ID 
aws ec2 delete-carrier-gateway --carrier-gateway-id $CAGW_ID
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID 
aws ec2 delete-vpc --vpc-id $VPC_ID

Summary

In this walkthrough you have been shown how to create and implement all the elements needed to deploy a host at one of AWS Wavelengths Zones. In short, the following were created:

  • A Virtual Private Cloud,

  • Security Groups for the bastion host and the application server,

  • The necessary Subnets and Route Tables,

  • The Elastic IP and the Network Interfaces, and

  • The bastion host and application server.

In the next guide, I will show you how to deploy an application in a host located in an AWS Wavelength Zone and a comparison with the same application running on the public internet.

See you then!

Vodafone Developer Portal

Discover, try, and purchase our APIs to start building your own apps