Automatically Assign Elastic IPs to Elastic Beanstalk Instances
I've had a need for a while to automatically assign elastic IP addresses to newly launched Elastic Beanstalk instances. This is super useful if you're using an external database host like Compose, and you only want to allow connections coming from your app instances.
But when elastic beanstalk brings up a new instance it assigns it a random IP address. The script below solves this, by triggering a lambda function with a Cloudwatch rule when a new EC2 instance is launched.
Note: This script assumes you have a pool of elastic IPs – it doesn't provision them.
1. Create a Cloudwatch Rule
Create a cloudwatch rule that runs when an EC2 instance transitions to a "running" state, and have it trigger a lambda function.
2. Create the Lambda Function
Paste the following code into a lambda function:
const AWS = require('aws-sdk');
const ec2 = new AWS.EC2();
const PROD_ENV_NAME = 'my-prod-env-name';
// Example Event
// {
// "version": "0",
// "id": "ee376907-2647-4179-9203-343cfb3017a4",
// "detail-type": "EC2 Instance State-change Notification",
// "source": "aws.ec2",
// "account": "123456789012",
// "time": "2015-11-11T21:30:34Z",
// "region": "us-east-1",
// "resources": [
// "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111"
// ],
// "detail": {
// "instance-id": "i-abcd1111",
// "state": "running"
// }
// }
exports.handler = async (event) => {
console.log("EVENT:", event);
// The newly launched instance ID.
const instanceId = event.detail['instance-id'];
// Fetch info about the newly launched instance
const result = await ec2.describeInstances({
Filters: [ { Name: "instance-id", Values: [instanceId] } ]
}).promise()
// The instance details are buried in this object
const instance = result.Reservations[0].Instances[0];
const isAttached = instance.NetworkInterfaces.find(int => int.Association.IpOwnerId !== 'amazon');
// Bail if the instance is already attached to another EIP
if (isAttached) {
console.log("This instance is already assigned to an elastic IP")
return { statusCode: 200, body: '' }
}
// In elastic beanstalk, the instance name gets assigned to the enviroment name.
// There is also an environment name tag, which could be used here.
const name = instance.Tags.find(t => t.Key === 'Name').Value;
// Only assign EIPs to production instances
if (name !== PROD_ENV_NAME) {
console.log('Not a production instance. Not assigning. Instance name:', name)
return { statusCode: 200, body: ''}
}
// Get a list of elastic IP addresses
const addresses = await ec2.describeAddresses().promise();
// Filter out addresses already assigned to instances
const availableAddresses = addresses.Addresses.filter(a => !a.NetworkInterfaceId);
// Raise an error if we have no more available IP addresses
if (availableAddresses.length === 0) {
console.log("ERROR: no available ip addresses");
return { statusCode: 400, body: JSON.stringify("ERROR: no available ip addresses") }
}
const firstAvail = availableAddresses[0]
try {
// Associate the instance to the address
const result = await ec2.associateAddress({
AllocationId: firstAvail.AllocationId,
InstanceId: instanceId
}).promise();
console.log('allocation result', result)
return { statusCode: 200, body: JSON.stringify('Associated IP address.') };
} catch (err) {
console.log("ERROR: ", err);
}
};
Whenever your elastic beanstalk environment launches a new instance, the above lambda function will get hit, and if there's an available elastic IP address, it will assign it to the new instance.