How to point external DNS record to GCP VM instance without static external IP?
Solution 1:
hope you are well. To add a little bit to the previous answer from @Sergiusz, here is what I did.
-
Configured a zone in Cloud DNS (outside of the scope of the question)
-
Created a service account with DNS Administrator role/permissions assigned (can be tightened a bit), then downloaded the json key for the service account to /root/sa-file.json on the VM
-
Updated the VM's metadata with custom values:
dns_zone_name (to store the name of the zone, fe gcp-jd-zone)
dns_domain (to store the fqdn, fe gcp.jabbsondude.com)
dns_record_ttl (to store ttl for created A record, fe 600)
key_path (to store the path to the file with Service Account Key, fe /root/sa-file.json) -
Updated the VM's metadata with a startup-script:
#!/bin/bash
host=$HOSTNAME
url_project_id="http://metadata.google.internal/computeMetadata/v1/project/project-id"
url_ip_address="http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
url_attributes="http://metadata.google.internal/computeMetadata/v1/instance/attributes"
get_metadata() {
local data=`curl -s -f $url_attributes/$1 -H "Metadata-Flavor: Google"`
if [ -z "$data" ]; then
echo "$1 does not exist in metadata. Exiting."
exit 1
fi
echo "$data"
}
auth_gcloud() {
project=`curl -s $url_project_id -H "Metadata-Flavor: Google"`
echo "Authenticating gcloud with service account ($key_path), project ($project)."
gcloud auth activate-service-account --project=$project --key-file=$key_path >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "Authentication succeeded."
else
echo "Authentication failed. Exiting."
exit 2
fi
}
dns_check_record_exists() {
echo "Checking if record ($host.$dns_domain) already exists in Cloud DNS"
dns_data=`gcloud beta dns record-sets list --zone=$dns_zone_name --name=$host.$dns_domain --format=text`
if [ $? -eq 0 ]; then
echo "Received answer from Cloud DNS."
else
echo "Didn't receive any data from Cloud DNS. Exiting."
exit 3
fi
if [[ -n "$dns_data" ]]; then
echo "Found existing record."
return 1 # data exists
else
echo "Didn't find existing record."
return 0 # no data
fi
}
dns_record_create() {
echo "Creating new record in Cloud DNS ($host.$dns_domain) in zone ($dns_zone_name) with ip ($ip) and ttl ($dns_record_ttl)"
gcloud beta dns record-sets create $host.$dns_domain --rrdatas=$ip --type=A --zone=$dns_zone_name --ttl=$dns_record_ttl >/dev/null 2>&1
}
dns_record_update() {
echo "Updating existing record in Cloud DNS ($host.$dns_domain) in zone ($dns_zone_name) with ip ($ip) and ttl ($dns_record_ttl)"
gcloud beta dns record-sets update $host.$dns_domain --rrdatas=$ip --type=A --zone=$dns_zone_name --ttl=$dns_record_ttl >/dev/null 2>&1
}
# getting metadata
dns_zone_name="$(get_metadata dns_zone_name)"
dns_domain="$(get_metadata dns_domain)"
dns_record_ttl="$(get_metadata dns_record_ttl)"
key_path="$(get_metadata key_path)"
# getting exterinal ip
ip=`curl -s $url_ip_address -H "Metadata-Flavor: Google"`
# validating external ip
if [[ -n "$ip" && $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "External ip detected: $ip"
auth_gcloud
dns_check_record_exists
if [ $? -eq 0 ]; then
dns_record_create
else
dns_record_update
fi
else
echo "No external ip detected. Exiting."
exit 5
fi
Here is how it looks in the VM instance details: img
- Rebooted the VM
The output from the logs (/var/log/daemon.log in case of Debian):
Found startup-script in metadata.
startup-script: External ip detected: 123.213.132.2
startup-script: Authenticating gcloud with service account (/root/sa-file.json), project (jabbsondude-proj).
startup-script: Authentication succeeded.
startup-script: Checking if record (instance-1.gcp.jabbsondude.com) already exists in Cloud DNS
startup-script: Received answer from Cloud DNS.
startup-script: Didn't find existing record.
startup-script: Creating new record in Cloud DNS (instance-1.gcp.jabbsondude.com) in zone (gcp-jd-zone) with ip (123.213.132.2) and ttl (600)
startup-script exit status 0
The script works for creating and updating A records for each VM started with above script. It can probably be improved with couple more checks, this is just something I came up with out of curiosity in the last hour.
Let me know if you need any help with that or if something isn't working.
Solution 2:
If you rarely use those virtual machines it might be better to simply check current external IP with command: gcloud compute addresses list --filter="VM-NAME"
Anyway, here's my (ugly) solution:
- Create public DNS zone and add A record pointing to VM's current external IP.
- On registrar's page add NS record redirecting your domain to Google DNS, it will be one of following (they will be listed in your zone):
ns-cloud-a1.googledomains.com.
ns-cloud-b1.googledomains.com.
ns-cloud-c1.googledomains.com.
- Add startup script that will check the VM's ephemeral public IP and update public DNS zone:
#!/bin/bash
IP=$(curl -s api.ipify.org)
gcloud beta dns record-sets delete mydomain.com \
--type=A \
--zone=ZONE-NAME
gcloud beta dns record-sets create mydomain.com \
--rrdatas=$IP \
--ttl=TTL \
--type=A \
--zone=ZONE-NAME
You can find more info on DNS records here.