Last active
September 14, 2024 09:20
-
-
Save kwilczynski/1a197cbd093113c75560 to your computer and use it in GitHub Desktop.
EC2 automatic DNS entry in route53 for Auto Scaling Group
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TTL=300 | |
HOSTED_ZONE_ID= | |
REVERSE_HOSTED_ZONE_ID= | |
INSTANCE_ID= | |
REGION= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
### BEGIN INIT INFO | |
# Provides: route53 | |
# Required-Start: $local_fs $network | |
# Required-Stop: $local_fs $network | |
# Should-Start: $network | |
# Should-Stop: $network | |
# Default-Start: 2 3 5 | |
# Default-Stop: 0 6 | |
# Short-Description: Add and/or remove a DNS entry in Route53 | |
### END INIT INFO | |
. /lib/lsb/init-functions | |
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin | |
SCRIPT_NAME="/etc/init.d/$(basename -- $0)" | |
ROUTE53_BINARY="/usr/local/sbin/route53" | |
case "$1" in | |
start) | |
log_daemon_msg 'Adding a DNS entry into Route53 ...' | |
$ROUTE53_BINARY --add >/dev/null 2>&1 | |
log_end_msg $? | |
;; | |
stop) | |
log_daemon_msg 'Removing a DNS entry from Route53 ...' | |
$ROUTE53_BINARY --check >/dev/null 2>&1 | |
case $? in | |
0) | |
$ROUTE53_BINARY --remove >/dev/null 2>&1 | |
log_end_msg $? | |
;; | |
1) log_end_msg 0 ;; | |
esac | |
;; | |
status) | |
log_daemon_msg 'Checking if a DNS entry exists in Route53 ...' | |
$ROUTE53_BINARY --check >/dev/null 2>&1 | |
log_end_msg $? | |
;; | |
*) | |
log_action_msg "Usage: $SCRIPT_NAME {start|stop|status}" >&2 | |
exit 3 | |
;; | |
esac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"UserData": { | |
"Fn::Base64": { | |
"Fn::Join": [ | |
"", | |
[ | |
"#!/bin/bash\n", | |
"exec >/var/log/bootstrap.log 2>&1\n", | |
"EC2_AUTO_SCALING_GROUP='yes'\n", | |
"ROLE='docker'\n", | |
"DOMAIN='", | |
{ | |
"Ref": "Domain" | |
}, | |
"'\n", | |
"ORGANISATION='", | |
{ | |
"Ref": "Organisation" | |
}, | |
"'\n", | |
"PROJECT='", | |
{ | |
"Ref": "Project" | |
}, | |
"'\n", | |
"STACKNAME='", | |
{ | |
"Ref": "StackName" | |
}, | |
"'\n", | |
"ENVIRONMENT='", | |
{ | |
"Ref": "Environment" | |
}, | |
"'\n", | |
"RELEASESTAGE='", | |
{ | |
"Ref": "ReleaseStage" | |
}, | |
"'\n", | |
"BRANCH='", | |
{ | |
"Ref": "Branch" | |
}, | |
"'\n", | |
"SALT_MASTER_IP='", | |
{ | |
"Fn::GetAtt": [ | |
"InstanceMaster01", | |
"PrivateIp" | |
] | |
}, | |
"'\n", | |
"HOSTED_ZONE_ID='", | |
{ | |
"Ref": "Route53HostedZoneArn" | |
}, | |
"'\n", | |
"export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n", | |
"export DOMAIN ROLE ORGANISATION PROJECT STACKNAME ENVIRONMENT RELEASESTAGE BRANCH\n", | |
"export EC2_AUTO_SCALING_GROUP\n", | |
"export SALT_MASTER_IP\n", | |
"export HOSTED_ZONE_ID\n", | |
"aws --color=off s3 cp --quiet ", | |
{ | |
"Ref": "BootstrapBucketURL" | |
}, | |
"compute/bootstrap.sh - | bash -s\n", | |
"aws --color=off s3 cp --quiet ", | |
{ | |
"Ref": "BootstrapBucketURL" | |
}, | |
"compute/route53/route53.init /etc/init.d/route53\n", | |
"chown root:root /etc/init.d/route53\n", | |
"chmod 755 /etc/init.d/route53\n", | |
"update-rc.d route53 start 99 2 3 5 . stop 99 0 6 .\n", | |
"aws --color=off s3 cp --quiet ", | |
{ | |
"Ref": "BootstrapBucketURL" | |
}, | |
"compute/route53/route53.sh /usr/local/sbin/route53\n", | |
"chown root:root /usr/local/sbin/route53\n", | |
"chmod 755 /usr/local/sbin/route53\n", | |
"/usr/local/sbin/route53 --add\n" | |
] | |
] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash -eu | |
# | |
# route53.sh | |
# | |
# The main tasks of this script are: | |
# | |
# - Automatically add or remove a DNS entry in Route53 based | |
# on *this* instance "Name" tag (assuming it was prior set | |
# to a correct value); | |
# | |
# Auxiliary tasks of this script: | |
# | |
# - Perform a look-up against the Route53 to check whether | |
# a correct DNS entry exists there already, and report back. | |
# | |
# This script is to be installed as /usr/local/sbin/route53. | |
# | |
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin | |
readonly ROUTE53_DEFAULT='/etc/default/route53' | |
readonly EC2_METADATA_URL='http://169.254.169.254/latest/meta-data' | |
readonly LOCK_FILE="/var/lock/$(basename -- "$0").lock" | |
# Make sure files are 644 and directories are 755. | |
umask 022 | |
[[ -e "/proc/$(cat $LOCK_FILE 2>/dev/null)" ]] || rm -f $LOCK_FILE | |
# Load environment variables that are mandatory. | |
if [[ -f $ROUTE53_DEFAULT ]]; then | |
# Necessary details (e.g. Hosted Zone ID, etc.) should | |
# have been passed down in the bootstrap process. | |
source $ROUTE53_DEFAULT | |
else | |
echo "Unable to load environment variables from '$ROUTE53_DEFAULT', aborting..." | |
exit 1 | |
fi | |
# Add, remove or check. | |
ACTION='UNKNOWN' | |
case "${1:-$ACTION}" in | |
# Add (when missing) or update. | |
-a|--add) ACTION='UPSERT' ;; | |
-r|--remove) ACTION='DELETE' ;; | |
-c|--check) ACTION='CHECK' ;; | |
-h|--help) | |
cat <<EOS | tee | |
Automatically add, remove or check a DNS entry in Route53 for this instance. | |
Usage: | |
$(basename -- "$0") <OPTION> | |
Options: | |
--add -a Add a DNS entry into Route53. | |
--remove -r Remove a DNS entry from Route53. | |
--check -c Check if a DNS entry exists in Route53. | |
--help -h This help screen. | |
EOS | |
exit 1 | |
;; | |
*) | |
echo "Unknown or no action given, aborting..." | |
exit 1 | |
;; | |
esac | |
# Check if environment variables are present and non-empty. | |
REQUIRED=( TTL HOSTED_ZONE_ID INSTANCE_ID REGION ) | |
for v in ${REQUIRED[@]}; do | |
eval VALUE='$'${v} | |
if [[ -z $VALUE ]]; then | |
echo "The '$v' environment variable has to be set, aborting..." | |
exit 1 | |
fi | |
done | |
if (set -o noclobber; echo $$ > $LOCK_FILE) &>/dev/null; then | |
# Make a secure temporary file name, needed later. | |
TEMPORARY_FILE=$(mktemp -ut "$(basename $0).XXXXXXXX") | |
# Make sure to remove the temporary | |
# file when terminating, and clean-up | |
# the lock-file too. | |
trap \ | |
"rm -f $LOCK_FILE $TEMPORARY_FILE; exit" \ | |
HUP INT KILL TERM QUIT EXIT | |
# Fetch current private IP address of this instance. | |
INSTANCE_IPV4=$(curl -s ${EC2_METADATA_URL}/local-ipv4) | |
# Fetch current "Name" tag that was set for this | |
# instance, as it will be used when adding (or | |
# updating) a new DNS entry (of a type "A") in | |
# Route53 service. The premise is that whatever | |
# the aforementioned tag is, then the DNS entry | |
# should be exactly the same. | |
INSTANCE_NAME_TAG=$( | |
aws ec2 describe-tags \ | |
--query 'Tags[*].Value' \ | |
--filters "Name=resource-id,Values=${INSTANCE_ID}" 'Name=key,Values=Name' \ | |
--region $REGION --output text 2>/dev/null | |
) | |
# Make sure that the "Name" tag was actually set. | |
if [[ "x${INSTANCE_NAME_TAG}" == "x" ]]; then | |
echo "The 'Name' tag is empty or has not been set, aborting..." | |
exit 1 | |
fi | |
if [[ $ACTION == 'CHECK' ]]; then | |
# Fetch details (about every resource) about given Hosted | |
# Zone from Route53 and format to make it easier to search | |
# for a particular entry. Since the amount of records can | |
# often be quiet large, store it in a temporary file. | |
aws --color=off route53 list-resource-record-sets \ | |
--query 'ResourceRecordSets[*].[Type,TTL,Name,ResourceRecords[0].Value]' \ | |
--hosted-zone-id $HOSTED_ZONE_ID --region $REGION --output text | \ | |
sed -e 's/\s/,/g' 2>/dev/null > $TEMPORARY_FILE | |
# Assemble entry for this instance. | |
RESOURCE=$(printf "%s,%s,%s.,%s" "A" "$TTL" "$INSTANCE_NAME_TAG" "$INSTANCE_IPV4") | |
if grep -q $RESOURCE $TEMPORARY_FILE &>/dev/null; then | |
# Found? Then print using the JSON that can used to | |
# make a change request against Reoute53, if needed. | |
cat <<EOF | |
{ | |
"ResourceRecordSet": { | |
"Name": "${INSTANCE_NAME_TAG}.", | |
"Type": "A", | |
"TTL": ${TTL}, | |
"ResourceRecords": [ | |
{ | |
"Value": "${INSTANCE_IPV4}" | |
} | |
] | |
} | |
} | |
EOF | |
exit 0 | |
fi | |
# Nothing to show? Then make | |
# it a non-clean exit. | |
exit 1 | |
else | |
# Render details of the request (or a "change", | |
# rather) which is going to be sent to Route53. | |
# Note that the "UPSERT" action will both add | |
# the entry if it does not exist yet or update | |
# current value accordingly. Better option over | |
# the "CREATE" action (which in turn would fail | |
# if an entry exists already). | |
cat <<EOF | tee $TEMPORARY_FILE | |
{ | |
"Changes": [ | |
{ | |
"Action": "${ACTION}", | |
"ResourceRecordSet": { | |
"Name": "${INSTANCE_NAME_TAG}.", | |
"Type": "A", | |
"TTL": ${TTL}, | |
"ResourceRecords": [ | |
{ | |
"Value": "${INSTANCE_IPV4}" | |
} | |
] | |
} | |
} | |
] | |
} | |
EOF | |
# Route53 has a queue for incoming change requests. | |
# When a task is placed in a queue successfully, | |
# then a "Change ID" which represents a batch job | |
# will be given back, and it can be used to track | |
# progress (although, IAM role needs to be set | |
# appropriately to allow access, etc.). | |
aws route53 change-resource-record-sets \ | |
--hosted-zone-id $HOSTED_ZONE_ID \ | |
--change-batch file://${TEMPORARY_FILE} \ | |
--region $REGION | |
fi | |
rm -f $LOCK_FILE $TEMPORARY_FILE &>/dev/null | |
# Reset traps to their default behaviour. | |
trap - HUP INT KILL TERM QUIT EXIT | |
else | |
echo "Unable to create lock file (current owner: "$(cat $LOCK_FILE 2>/dev/null)")." | |
exit 1 | |
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
set -e | |
set -u | |
set -o pipefail | |
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin | |
readonly ROUTE53_DEFAULT='/etc/default/route53' | |
readonly LOCK_FILE="/var/lock/$(basename -- "$0").lock" | |
readonly EC2_METADATA_URL='http://169.254.169.254/latest/meta-data' | |
# Make sure files are 644 and directories are 755. | |
umask 022 | |
[[ -e "/proc/$(cat $LOCK_FILE 2>/dev/null)" ]] || rm -f $LOCK_FILE | |
# Load environment variables that are mandatory. | |
if [[ -f $ROUTE53_DEFAULT ]]; then | |
# Necessary details (e.g. Hosted Zone ID, etc.) should | |
# have been passed down in the bootstrap process. | |
source $ROUTE53_DEFAULT | |
else | |
echo "Unable to load environment variables from '$ROUTE53_DEFAULT', aborting..." | |
exit 1 | |
fi | |
# Add, remove or check. | |
ACTION='UNKNOWN' | |
case "${1:-$ACTION}" in | |
# Add (when missing) or update. | |
-a|--add) ACTION='UPSERT' ;; | |
-r|--remove) ACTION='DELETE' ;; | |
-c|--check) ACTION='CHECK' ;; | |
-h|--help) | |
cat <<EOS | tee | |
Automatically add, remove or check a DNS entry in Route53 for this instance. | |
Usage: | |
$(basename -- "$0") <OPTION> | |
Options: | |
--add -a Add a DNS entry into Route53. | |
--remove -r Remove a DNS entry from Route53. | |
--check -c Check if a DNS entry exists in Route53. | |
--help -h This help screen. | |
Note: By default, the forward (A) and reverse (PTR) | |
resource records are added into Rotue53. | |
EOS | |
exit 1 | |
;; | |
*) | |
echo "Unknown or no action given, aborting..." | |
exit 1 | |
;; | |
esac | |
# Check if environment variables are present and non-empty. | |
REQUIRED=(TTL PRIVATE_ZONE_ID REVERSE_ZONE_ID INSTANCE_ID REGION) | |
for v in ${REQUIRED[@]}; do | |
eval VALUE='$'${v} | |
if [[ -z $VALUE ]]; then | |
echo "The '$v' environment variable has to be set, aborting..." | |
exit 1 | |
fi | |
done | |
if (set -o noclobber; echo $$ > $LOCK_FILE) &>/dev/null; then | |
# Make a secure temporary file name, needed later. | |
TEMPORARY_FILE=$(mktemp -ut "$(basename $0).XXXXXXXX") | |
# Make sure to remove the temporary | |
# file when terminating, and clean-up | |
# the lock-file too. | |
trap \ | |
"rm -f $LOCK_FILE $TEMPORARY_FILE; exit" \ | |
HUP INT KILL TERM QUIT EXIT | |
if [[ "x${INSTANCE_ID}" == "x" ]]; then | |
# Fetch the EC2 instance ID. | |
INSTANCE_ID=$(curl -s ${EC2_METADATA_URL}/instance-id) | |
fi | |
# Fetch current private IP address of this instance. | |
PRIVATE_IP_ADDRESS=$(curl -s ${EC2_METADATA_URL}/local-ipv4) | |
# Make the in-addr.arpa. address for this instance. | |
INSTANCE_PTR=$(echo "$(printf '%s.' $PRIVATE_IP_ADDRESS | tac -s'.')in-addr.arpa.") | |
# Fetch current "Name" tag that was set for this | |
# instance, as it will be used when adding (or | |
# updating) a new DNS entry (of a type "A") in | |
# Route53 service. The premise is that whatever | |
# the aforementioned tag is, then the DNS entry | |
# should be exactly the same. | |
INSTANCE_NAME_TAG=$(aws ec2 describe-tags \ | |
--query 'Tags[*].Value' \ | |
--filters "Name=resource-id,Values=${INSTANCE_ID}" 'Name=key,Values=Name' \ | |
--region $REGION --output text 2>/dev/null) | |
# Make sure that the "Name" tag was actually set. | |
if [[ "x${INSTANCE_NAME_TAG}" == "x" ]]; then | |
echo "The 'Name' tag is empty or has not been set, aborting..." | |
exit 1 | |
fi | |
if [[ $ACTION == 'CHECK' ]]; then | |
# Keep a track of resource records. | |
SEEN_RESOURCES=0 | |
# Fetch details (about every resource) about given Hosted | |
# Zone (both forward and reverse) from Route53 and format | |
# to make it easier to search for a particular entry, | |
# and filter the A and PTR resource records only. | |
for zone in PRIVATE_ZONE_ID REVERSE_ZONE_ID; do | |
eval VALUE='$'${zone} | |
aws route53 list-resource-record-sets \ | |
--query 'ResourceRecordSets[*].[Type,TTL,Name,ResourceRecords[0].Value]' \ | |
--hosted-zone-id $VALUE \ | |
--region $REGION --output text | \ | |
grep -E '^(A|PTR)' | sed -e 's/\s/,/g' | \ | |
tee -a $TEMPORARY_FILE >/dev/null || true | |
done | |
# Assemble the A resource record for this instance. | |
RESOURCE=$(printf "%s,%s,%s.,%s" "A" "$TTL" "$INSTANCE_NAME_TAG" "$PRIVATE_IP_ADDRESS") | |
if grep -q $RESOURCE $TEMPORARY_FILE &>/dev/null; then | |
echo $RESOURCE | awk -F',' '{ print $3, $1, $4, $2 }' | |
SEEN_RESOURCES+=1 | |
fi | |
# Assemble the PTR resource record for this instance. | |
RESOURCE=$(printf "%s,%s,%s,%s" "PTR" "$TTL" "$INSTANCE_PTR" "$INSTANCE_NAME_TAG") | |
if grep -q $RESOURCE $TEMPORARY_FILE &>/dev/null; then | |
echo $RESOURCE | awk -F',' '{ print $3, $1, $4, $2 }' | |
SSEEN_RESOURCESEEN+=1 | |
fi | |
if (( $SEEN_RESOURCES < 1 )); then | |
# If there is nothing to show or a records are missing, | |
# then we make it a non-clean exit. | |
echo "No resource records found, aborting..." >&2 | |
exit 1 | |
fi | |
exit 0 | |
else | |
# Render details of the request (or a "change", | |
# rather) which is going to be sent to Route53. | |
# Note that the "UPSERT" action will both add | |
# the entry if it does not exist yet or update | |
# current value accordingly. Better option over | |
# the "CREATE" action (which in turn would fail | |
# if an entry exists already). | |
cat <<EOF > $TEMPORARY_FILE | |
{ | |
"Changes": [ | |
{ | |
"Action": "${ACTION}", | |
"ResourceRecordSet": { | |
"Name": "${INSTANCE_NAME_TAG}.", | |
"Type": "A", | |
"TTL": ${TTL}, | |
"ResourceRecords": [ | |
{ | |
"Value": "${PRIVATE_IP_ADDRESS}" | |
} | |
] | |
} | |
} | |
] | |
} | |
EOF | |
# Route53 has a queue for incoming change requests. | |
# When a task is placed in a queue successfully, | |
# then a "Change ID" which represents a batch job | |
# will be given back, and it can be used to track | |
# progress (although, IAM role needs to be set | |
# appropriately to allow access, etc.). | |
# | |
# Add the A resource record into Route53. | |
aws route53 change-resource-record-sets \ | |
--hosted-zone-id $PRIVATE_ZONE_ID \ | |
--change-batch file://${TEMPORARY_FILE} \ | |
--region $REGION | |
cat <<EOF > $TEMPORARY_FILE | |
{ | |
"Changes": [ | |
{ | |
"Action": "${ACTION}", | |
"ResourceRecordSet": { | |
"Name": "${INSTANCE_PTR}", | |
"Type": "PTR", | |
"TTL": ${TTL}, | |
"ResourceRecords": [ | |
{ | |
"Value": "${INSTANCE_NAME_TAG}" | |
} | |
] | |
} | |
} | |
] | |
} | |
EOF | |
# Add the PTR resource record into Route53. | |
aws route53 change-resource-record-sets \ | |
--hosted-zone-id $REVERSE_ZONE_ID \ | |
--change-batch file://${TEMPORARY_FILE} \ | |
--region $REGION | |
fi | |
rm -f $LOCK_FILE $TEMPORARY_FILE &>/dev/null | |
# Reset traps to their default behaviour. | |
trap - HUP INT KILL TERM QUIT EXIT | |
else | |
echo "Unable to create lock file (current owner: "$(cat $LOCK_FILE 2>/dev/null)")." | |
exit 1 | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment