Ode to Elastic IP

Danil Smirnov
danil.smirnov
Published in
3 min readDec 21, 2023

--

There is a widespread belief that a public Elastic IP address in AWS is a marker of non-optimal architecture. Smart cloud architects expect, that cloud developers always rely on private IPs. Smart and rich. Those rich architects insist on using NAT gateways for egress and load balancers, API gateway, and CDN for ingress traffic. All those services are not free (to say the least) if compared with completely free EIPs (when attached to an EC2 instance).

At least they were, and they’ll be until February 1, 2024, when AWS will introduce new EIP prices making this option less attractive to those poor souls like mine (but still affordable: a single IP address will cost just $3.6 per month — way less than that luxury NAT gateway).

Nevertheless, I’d like to show how to use those EIPs like a pro. Why? First, because you can! On a more serious note, sometimes we want to have a separate IP address per EC2 instance, e.g. in the very demanding email communication world.

But first of all, let’s talk about why EIPs are cool and why they are not. The cool side of them is that we can deploy them instantly and then they will be ours until we delete them explicitly. We can even transfer them to another account (though in the same region). The not that cool side is that there is a limit of only 5 EIPs per region and AWS is a bit reluctant to increase the limit asking you for a strong reason why you need them.

How to attach an EIP to an instance on a start-up

We usually want to run an EC2 instance pre-configured, hence using EC2 launch templates. We can’t reference an existing EIP address directly from a launch template (at least at the moment of writing), so we need to attach it on start-up via a userdata. We can do it via the following AWS CLI call (having an appropriate IAM role attached to the instance):

aws ec2 associate-address \
--instance-id ${INSTANCE_ID} \
--public-ip ${EIP} \
--allow-reassociation

However, to make this work, we must have access to AWS API which requires Internet connectivity, which in turn will be provided by that EIP we are trying to associate… Is it a dead loop? Not really, since we already have a private IP automatically assigned to the instance, so we only need to use it to access AWS API… Does it work out of the box? No. :-( (I’m looking at you, AWS!)

There is a way to “workaround” this, it’s not free though, but affordable. We need to deploy a VPC Endpoint of Interface type for EC2 service (aka com.amazonaws.region.ec2). (No need to deploy it for all the AZs, one subnet is enough and will cover the entire VPC.)

Thus, with this solution, we would pay only for EIP (from February 1, 2024) and for one VPC Endpoint of Interface type (~7$ per month), overall around ten bucks per instance (or less, if we make this one a router). However, I feel sad about even those seven bucks I have to pay for the VPC Endpoint, which I need on the instance start-up only!

There is a way to eliminate even this expense, if we offload the call to AWS API to another instance that has an EIP address already attached and which we might have to keep running anyway. (I hope you have the one, otherwise, you can hire the cheapest EC2 instance of t4g.nano type for ~$3 and delegate this work to it for all other instances, spreading its cost evenly to all of them.)

The following code in Python (Flask framework) lets us achieve exactly what we did via AWS CLI above:

@app.route('/attach')
def attach():
region = request.args.get('region')
ip = request.args.get('ip')
instance_id = request.args.get('instance')
ec2 = boto3.client('ec2', region_name = region)
try:
ec2.associate_address(PublicIp=ip, InstanceId=instance_id, AllowReassociation=True)
except ClientError as e:
logging.error( f"Failed to associate IP address {ip} to the instance {instance_id}:\n{e}" )
abort(make_response(jsonify(message='Failed to associate IP address'), 500))
return jsonify(status='ok')

We call our own EIP attaching service from the client EC2 instance via the following curl:

curl -H "Host:<hostname>” -sS http://<private IP of the attaching service>/attach?instance=${INSTANCE_ID}\&ip=${EIP}\&region=${REGION}

Now we have the most inexpensive Internet connectivity for our EC2 instances and let’s have a Merry Christmas and a Happy New Year too!

--

--