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:

  1. Create public DNS zone and add A record pointing to VM's current external IP.
  2. 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.
  1. 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.