Skip to content

Instantly share code, notes, and snippets.

@rocketraman
Last active August 4, 2020 20:16
Show Gist options
  • Save rocketraman/3f3f3925be3a346336a405e2a5d48df0 to your computer and use it in GitHub Desktop.
Save rocketraman/3f3f3925be3a346336a405e2a5d48df0 to your computer and use it in GitHub Desktop.
External DNS script for handling node port services, which is not supported yet by External DNS upstream - see https://github.com/kubernetes-sigs/external-dns/issues/1210#issuecomment-668729798
#!/usr/bin/env bash
usage() {
echo >&2 "Invalid environment and/or arguments specified"
exit 1
}
# settings
project_id=${PROJECT_ID:-$1}
zone=${ZONE:-$2}
target_service_annotation=${TARGET_SVC_ANNOTATION:-external-dns-nodes/hostname}
txt_prefix=${TXT_PREFIX:-ownership-}
record=${RECORD:-nodes}
debug=${DEBUG:-false}
if [[ -z $project_id || -z $zone ]]; then
usage
fi
if [[ "$debug" == "true" ]]; then
echo "Settings: project_id=$project_id, zone=$zone, target_service_annotation=$target_service_annotation, txt_prefix=$txt_prefix, record=$record"
fi
fqdn=${record}.${zone}.
while true; do
# IPs of ready nodes
node_ips=($(kubectl get nodes -o json |
jq -r '.items[] | select([(.status.conditions[] | select(.type == "Ready" and .status == "True"))] | length == 1) | .status.addresses[] | select(.type == "InternalIP") | .address'))
if [[ "$debug" == "true" ]]; then
echo "Ready Node IPs:"
for i in ${node_ips[@]}; do echo $i; done
fi
if [[ "$debug" == "true" ]]; then
gcloud_verbosity="--verbosity=debug"
else
gcloud_verbosity="--verbosity=warning"
fi
# Append the trailing fqdn dot to the zone
zone_name=$(gcloud dns --project $project_id $gcloud_verbosity managed-zones list --format=json |
jq -r '.[] | select(.dnsName == "'$zone'.").name')
current_node_ips=($(gcloud dns --project redock-ops $gcloud_verbosity record-sets list --zone=$zone_name --format=json |
jq -r '.[] | select(.name == "'$fqdn'" and .type == "A") | .rrdatas[]'))
if [[ "$debug" == "true" ]]; then
echo "Current Node IPs:"
for i in ${current_node_ips[@]}; do echo $i; done
fi
set_diffs=($(echo ${node_ips[@]} ${current_node_ips[@]} | tr ' ' '\n' | sort | uniq -u))
# Update the node IPs DNS record if necessary
if [[ ${#set_diffs[@]} == 0 ]]; then
echo "Record $fqdn already up-to-date, current ready node IPs: ${node_ips[@]}"
else
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction start --zone=$zone_name
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction remove --zone=$zone_name --name=$fqdn --ttl=300 --type=A $(echo ${current_node_ips[@]})
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction add --zone=$zone_name --name=$fqdn --ttl=300 --type=A $(echo ${node_ips[@]})
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction execute --zone=$zone_name
echo "Updated record $fqdn"
fi
# Find hostnames from services with type NodePort and annotation $target_service_annotation
hostnames=($(kubectl get svc --all-namespaces -o json |
jq -r '.items[] | select(.spec.type == "NodePort" and .metadata.annotations["'$target_service_annotation'"] != null) | .metadata.annotations["'$target_service_annotation'"]'))
for h in ${hostnames[@]}; do
if [[ $h != *${zone} ]]; then
echo "Ignoring hostname $h, does not match domain ${zone}"
continue
fi
# existing record(s)
txt=$(gcloud dns --project=$project_id $gcloud_verbosity record-sets list --type TXT --name ${txt_prefix}${h}. --zone $zone_name --format=json | jq -r '.[].rrdatas[0]')
cname=$(gcloud dns --project=$project_id $gcloud_verbosity record-sets list --type CNAME --name ${h}. --zone $zone_name --format=json | jq -r '.[].rrdatas[0]')
if [[ $txt == "" && $cname == "" ]]; then
echo "Adding CNAME record for hostname $h"
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction start --zone=$zone_name
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction add --zone=$zone_name --name=${h}. --ttl=300 --type=CNAME $fqdn
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction add --zone=$zone_name --name=${txt_prefix}${h}. --ttl=300 --type=TXT "external-dns-nodes"
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction execute --zone=$zone_name
elif [[ $txt != '"external-dns-nodes"' && $cname != ${h}. ]]; then
echo "Record $h owned by other application (TXT=$txt), ignoring update..."
elif [[ $txt == '"external-dns-nodes"' ]]; then
if [[ $cname != $fqdn ]]; then
echo "Updating CNAME record for hostname $h"
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction start --zone=$zone_name
if [[ $cname != "" ]]; then
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction remove --zone=$zone_name --name=${h}. --ttl=300 --type=CNAME $cname
fi
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction add --zone=$zone_name --name=${h}. --ttl=300 --type=CNAME $fqdn
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction execute --zone=$zone_name
else
echo "Record $h already up-to-date."
fi
fi
done
# Find records "owned" by external-dns-nodes, for deletions
owned_records=($(gcloud dns --project=$project_id $gcloud_verbosity record-sets list --zone $zone_name --format=json | jq -r '.[] | select(.type == "TXT" and .rrdatas[0] == "\"external-dns-nodes\"") | .name'))
owned_records=("${owned_records[@]##$txt_prefix}")
owned_records=("${owned_records[@]%.}")
records_to_delete=($(echo ${owned_records[@]} ${hostnames[@]} ${hostnames[@]} | tr ' ' '\n' | sort | uniq -u))
for h in ${records_to_delete[@]}; do
if [[ $h != *${zone} ]]; then
echo "Ignoring hostname $h, does not match domain ${zone}"
continue
fi
cname=$(gcloud dns --project=$project_id $gcloud_verbosity record-sets list --type CNAME --name ${h}. --zone $zone_name --format=json | jq -r '.[].rrdatas[0]')
echo "Deleting CNAME and TXT record for hostname $h"
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction start --zone=$zone_name
if [[ $cname != "" ]]; then
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction remove --zone=$zone_name --name=${h}. --ttl=300 --type=CNAME $cname
fi
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction remove --zone=$zone_name --name=${txt_prefix}${h}. --ttl=300 --type=TXT "external-dns-nodes"
gcloud dns --project=$project_id $gcloud_verbosity record-sets transaction execute --zone=$zone_name
done
sleep 60
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment