When launching new EC2 instances in AWS, it is often desirable to add the new instance's IP to a route53 record set. For instance when an autoscaling group launches a new node, it would be convenient if the node's IP could be automatically added to a record set, similarly to the way it can be automatically added to a load balancer group.
route53-controller provides a service to automatically add instances
to Route53 record sets. You provide a resource.json
describing
which instances to add to which record set, and route53-controller
adds the appropriate IPs to your record sets when invoked.
route53-controller
can be run standalone from a CLI, and can also be
uploaded as an AWS Lambda function, where it can be triggered by SNS.
The instances and resource record sets to which to add the instances' IPs are described by a JSON format, referred to as resource.json.
Given a resource.json
we may immediately update our resource record
sets by using ./bin/updateRecordSets.js
.
$ node bin/updateRecordSets.js --resource resource.json
The root structure of the resource.json is
{
"HostedZone": "Z148QEXAMPLE8V",
"Resources": {
/* Resources describing (Instance -> record set) pairs */
}
}
The HostedZone is the ID of a pre-existing route53 Hosted Zone. There may be only HostedZone one per resource.json. If multiple
HostedZones must be controlled, you will need to create multiple route53-controller resource descriptions.
The Resources attribute is a list of all the record sets which will be modified, along with filters describing the instances whose IPs will be associated to the record set. The format of the Resources is
"Resources": {
"ResourceID": {
"ResourceRecordSet": {
/* Description of the Route53 resource record update */
},
"Instances": [
/* One or more EC2 instance descriptions to be associated to the Route53 record set */
]
}
}
The ResourceID is a convenience name describing the update, but has no effect on the logical operation of the record set update. Any valid JSON attribute name is acceptable.
The ResourceRecordSet describes the Resource Record to be updated. The basic required format is
"ResourceRecordSet": {
"Name": "bar.example.com", // domain name to be modified
}
route53-controller will locate the instances to be added to the record set, then it will use the ResourceRecordSet JSON object to update the record.
The ResourceRecordSet object will used as the ResourceRecordSet
parameter in a call to the AWS SDK
route53.changeResourceRecordSets
function,
except:
- ResourceRecords will be set to to the list of instances' IPs
- The required Type will default to "A"
After route53-controller executes, all of the IPs associated with the record set will be replaced. Except, if no instances are found, there will be no change to the record set.
See the changeResourceRecordSets API documentation for more information about attributes which may be included in the ResourceRecordSet object.
The Instances attribute is an array of descriptions of EC2 instance IPs to be associated with the ResourceRecordSet. The format is
"Instances": [
/* one or more instance descriptions */
{
"Filters": [
// One or more ec2.describeInstances filters
],
"PrivateIP": true || false, // Optional: whether to use the instance's private IP
"Region": "us-east-1" || "us-west-2" || "eu-central-1" || etc. // the AWS Region in which to find the instances
}
]
By default, the public IP of each EC2 instance will be used.
However, if the PrivateIP attribute is present and true
, then
the instance's private IP will be used instead.
The Region attribute specifies the AWS region in which to find the instances. Only one region may be specified per instance description. If the record set must include IPs of instances from different regions, then multiple instance descriptions must be used.
The Filters describe which EC2 instances will be included in the
new value of the route53 record set. The filters will be used
directly in an ec2.describeInstances
operation without modification.
See the describeInstances API
documentation
for information and examples of possible filters.
A complete mock example of a resource.json is
{
"HostedZone": "Z148QEXAMPLE8V",
"Resources": {
"bar.example": {
"Instances": [
{
"Region": "us-west-1",
"PrivateIP": true,
"Filters": [
{
"Name": "tag:Name",
"Values": [
"bar.example"
]
}
]
}
],
"ResourceRecordSet": {
"Name": "bar.example.com",
"TTL": 30
}
}
},
"foo.example": {
"Instances": [
{
"Region": "us-west-2",
"Filters": [
{
"Name": "tag:Name",
"Values": [
"foo.example"
]
}
]
},
{
"Region": "us-east-1",
"Filters": [
{
"Name": "tag:Name",
"Values": [
"foo.example"
]
}
]
}
],
"ResourceRecordSet": {
"Name": "foo.example.com",
}
}
}
Here, the resource.json file describes modifying the recordset in a fake example HostedZone.
- All instances tagged with the
Name
ofbar.example
in theus-west-1
region are included as IPs in the record setbar.example.com
. - All instances tagged with the
Name
offoo.example
in theus-west-2
andus-east-1
regions are included as IPs in the record setfoo.example.com
.
route53-controller
provides a convenience script,
'bin/lambdaFunction' to upload and update the Lambda function directly. By
default, route53-contoller will name the Lambda function as
route53-controller
. You may specify a different name by providing
the --name
argument.
You may create the Lambda function by running
$ node bin/lambdaFunction.js create --resource resource.json --role arn:aws:iam::NNNNNNNNNNNN:role/lambda_role --region=us-west-2
A role ARN must be provided when creating the lambda function, but is not necessary to update an existing lambda function. Also, not all regions support Lambda functions, so you may need to specify the region explicitly. Fortunately, the region where the Lambda function is deployed does not affect its ability to find IPs of instances in other regions, nor does it affect its ability to update route53 records sets.
An existing route53-controller
may be updated by running
$ node bin/lambdaFunction.js update --resource resource.json --region=us-west-2
Again, the region may be required, but the role ARN is not required to update the Lambda function.
If you prefer to upload the function manually route53-controller can
be used to create the AWS Lambda deployment package, but not upload it. Follow, for
example, the AWS Lambda
walkthrough.
Create a lambda execution role with the required route53-controller
IAM policy, and upload the zip file created by
./bin/createLambdaPackage.js
.
For example:
$ node bin/createLambdaPackage.js --resource resource.json
After uploading the lambda function, you may invoke it either with the AWS console, or via with the AWS CLI:
$ aws lambda invoke --region us-west-2 --function-name route53-controller --invocation-type RequestResponse --log-type Tail --payload '{}' lambda-output.txt
Make sure to invoke the function with the region
and function-name
you chose. The payload does not have any effect on the route53-controller
behavior.
See the Auto Scaling developer guide "Getting Notifications When Your Auto Scaling Group Changes" to set up SNS notifications when your autoscaling group changes.
Then see the Amazon Simple Notification Service Developer Guide "Invoking Lambda functions using Amazon SNS notifications" to trigger the lambda function when the SNS event occurs.
When updating record sets either in CLI mode, or in AWS Lambda, AWS requires appropriate IAM permissions to both describe the EC2 instances, and modify the record sets.
The script ./bin/createPolicy.js
can be used to create the
necessary policy. createPolicy.js
requires either a local copy of
the resource.json
file or an s3location.json
file describing where
to fetch the resource.json
(more information below).
Alternatively, ./bin/createPolicy.js
can create a policy which may
be attached to roles or users by including the --createPolicy
option:
$ node bin/createPolicy.js --resource resource.json --createPolicy route53-controller
./bin/createPolicy.js
may also attach the policy inline for an
existing user or role by providing the --userPolicy
or
--rolePolicy
respectively.
$ node bin/createPolicy.js --resource resource.json --rolePolicy lambda_role
If an s3location.json
file is provided, the policy will include permission to read
the resource.json S3 object.
$ node bin/createPolicy.js --resource resource.json --s3location s3location.json
Normally, the resource.json
will be uploaded as part of the Lambda
function deployment package. However, if resource.json
must be
updated frequently, it may be more convenient (and require
fewer permissions) to store resource.json
at an S3 location,
where it can be updated without requiring redeployment of the Lambda
function.
The S3 location is describe by an s3Location.json
file. For example:
{
"Bucket": "my-bucket",
"Key": "resource.json"
}
The resource file may be uploaded by ./bin/uploadResource.js
$ node bin/uploadResource.js s3Location.json resource.json
Now route53-controller
requires read access to the static s3
location. The necessary policy may be created by
$ node bin/createPolicy.js --s3location s3Location.json
Then, we may provide the s3location.json
file in place of resource.json
.
$ node bin/lambdaFunction.js update --s3location resource.json --region=us-west-2
$ node bin/lambdaFunction.js --s3location s3Location.json