Last active
August 4, 2020 20:16
-
-
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
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
#!/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