Deploying and managing Google Cloud DNS for domain and DNS hosting
Google Cloud DNS is a high-performance, resilient, global Domain Name System (DNS) service that publishes your domain names to the global DNS in a cost-effective way. Cloud DNS translates requests for domain names like "www.example.com" into IP addresses like "192.0.2.1".
Key Features
Global Anycast: Global network of anycast name servers for low-latency DNS resolution
High Availability: 100% uptime SLA with automatic failover
Scalability: Handle millions of DNS queries
Security: DNSSEC support for zone signing and validation
Public and Private Zones: Support for both public internet domains and private VPC DNS
Managed Service: No need to provision or manage DNS servers
Cloud Integration: Works with other GCP services for automatic DNS record management
Programmatic Management: Full API, gcloud, and Terraform support
Logging: Query logging for auditing and analytics
Flexible Pricing: Pay only for hosted zones and queries
Deploying Cloud DNS with Terraform
Basic Public Zone Configuration
resource "google_dns_managed_zone" "example_zone" {
name = "example-zone"
dns_name = "example.com."
description = "Example public DNS zone"
# Default visibility is "public"
labels = {
environment = "production"
}
}
# Create an A record
resource "google_dns_record_set" "a_record" {
name = "www.example.com."
managed_zone = google_dns_managed_zone.example_zone.name
type = "A"
ttl = 300
rrdatas = ["203.0.113.1"]
}
# Create a CNAME record
resource "google_dns_record_set" "cname_record" {
name = "mail.example.com."
managed_zone = google_dns_managed_zone.example_zone.name
type = "CNAME"
ttl = 300
rrdatas = ["ghs.googlehosted.com."]
}
# Create MX records for email
resource "google_dns_record_set" "mx_record" {
name = "example.com."
managed_zone = google_dns_managed_zone.example_zone.name
type = "MX"
ttl = 3600
rrdatas = [
"1 aspmx.l.google.com.",
"5 alt1.aspmx.l.google.com.",
"5 alt2.aspmx.l.google.com.",
"10 alt3.aspmx.l.google.com.",
"10 alt4.aspmx.l.google.com."
]
}
# Create TXT records for verification and SPF
resource "google_dns_record_set" "txt_record" {
name = "example.com."
managed_zone = google_dns_managed_zone.example_zone.name
type = "TXT"
ttl = 3600
rrdatas = [
"\"v=spf1 include:_spf.google.com ~all\"",
"\"google-site-verification=abcdefghijklmnopqrstuvwxyz\""
]
}
# Output the name servers
output "name_servers" {
description = "Cloud DNS name servers for this zone"
value = google_dns_managed_zone.example_zone.name_servers
}
Private DNS Zone Configuration
# Create a VPC network
resource "google_compute_network" "vpc_network" {
name = "my-vpc-network"
auto_create_subnetworks = false
}
# Create a private DNS zone
resource "google_dns_managed_zone" "private_zone" {
name = "private-example-zone"
dns_name = "internal.example."
description = "Private DNS zone for internal services"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.vpc_network.id
}
}
}
# Create A records for internal services
resource "google_dns_record_set" "api_record" {
name = "api.internal.example."
managed_zone = google_dns_managed_zone.private_zone.name
type = "A"
ttl = 300
rrdatas = ["10.0.0.10"]
}
resource "google_dns_record_set" "db_record" {
name = "db.internal.example."
managed_zone = google_dns_managed_zone.private_zone.name
type = "A"
ttl = 300
rrdatas = ["10.0.0.20"]
}
# Service discovery via SRV records
resource "google_dns_record_set" "service_discovery" {
name = "_grpc._tcp.internal.example."
managed_zone = google_dns_managed_zone.private_zone.name
type = "SRV"
ttl = 300
rrdatas = [
"0 1 8080 service-a.internal.example.",
"0 1 8080 service-b.internal.example."
]
}
DNSSEC Configuration
resource "google_dns_managed_zone" "secure_zone" {
name = "secure-example-zone"
dns_name = "secure.example."
description = "Secure DNS zone with DNSSEC enabled"
dnssec_config {
state = "on"
default_key_specs {
algorithm = "rsasha256"
key_length = 2048
key_type = "zoneSigning"
}
default_key_specs {
algorithm = "rsasha256"
key_length = 2048
key_type = "keySigning"
}
}
}
# Output DNSSEC information
output "dnssec_info" {
description = "DNSSEC DS record information"
value = google_dns_managed_zone.secure_zone.dnssec_config
}
Cloud DNS Peering Configuration
# Create two VPC networks to demonstrate DNS peering
resource "google_compute_network" "network_a" {
name = "network-a"
auto_create_subnetworks = false
}
resource "google_compute_network" "network_b" {
name = "network-b"
auto_create_subnetworks = false
}
# Create subnets
resource "google_compute_subnetwork" "subnet_a" {
name = "subnet-a"
ip_cidr_range = "10.0.1.0/24"
region = "us-central1"
network = google_compute_network.network_a.id
}
resource "google_compute_subnetwork" "subnet_b" {
name = "subnet-b"
ip_cidr_range = "10.0.2.0/24"
region = "us-central1"
network = google_compute_network.network_b.id
}
# DNS zone for network A
resource "google_dns_managed_zone" "network_a_dns" {
name = "network-a-dns"
dns_name = "services-a.internal."
description = "DNS zone for Network A services"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.network_a.id
}
}
}
# DNS zone for network B
resource "google_dns_managed_zone" "network_b_dns" {
name = "network-b-dns"
dns_name = "services-b.internal."
description = "DNS zone for Network B services"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.network_b.id
}
}
}
# Create DNS peering from network A to network B
resource "google_dns_managed_zone" "peering_zone_a_to_b" {
name = "peering-a-to-b"
dns_name = "services-b.internal."
description = "DNS peering zone from Network A to Network B"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.network_a.id
}
}
peering_config {
target_network {
network_url = google_compute_network.network_b.id
}
}
}
# Create DNS peering from network B to network A
resource "google_dns_managed_zone" "peering_zone_b_to_a" {
name = "peering-b-to-a"
dns_name = "services-a.internal."
description = "DNS peering zone from Network B to Network A"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.network_b.id
}
}
peering_config {
target_network {
network_url = google_compute_network.network_a.id
}
}
}
# Add some example records
resource "google_dns_record_set" "service_a" {
name = "app.services-a.internal."
managed_zone = google_dns_managed_zone.network_a_dns.name
type = "A"
ttl = 300
rrdatas = ["10.0.1.10"]
}
resource "google_dns_record_set" "service_b" {
name = "db.services-b.internal."
managed_zone = google_dns_managed_zone.network_b_dns.name
type = "A"
ttl = 300
rrdatas = ["10.0.2.20"]
}
Managing Cloud DNS with gcloud CLI
Creating and Managing DNS Zones
# Create a public DNS zone
gcloud dns managed-zones create example-zone \
--description="Example public zone" \
--dns-name="example.com." \
--labels="environment=prod"
# Create a private DNS zone
gcloud dns managed-zones create private-zone \
--description="Example private zone" \
--dns-name="internal.example." \
--visibility=private \
--networks=my-vpc-network
# List managed zones
gcloud dns managed-zones list
# Describe a specific zone
gcloud dns managed-zones describe example-zone
Managing DNS Records
# Add an A record
gcloud dns record-sets transaction start --zone=example-zone
gcloud dns record-sets transaction add "203.0.113.1" \
--name="www.example.com." \
--ttl=300 \
--type=A \
--zone=example-zone
gcloud dns record-sets transaction execute --zone=example-zone
# Add multiple records in one transaction
gcloud dns record-sets transaction start --zone=example-zone
gcloud dns record-sets transaction add "10.0.0.1" "10.0.0.2" \
--name="api.example.com." \
--ttl=300 \
--type=A \
--zone=example-zone
gcloud dns record-sets transaction add "mail.example.com." \
--name="alias.example.com." \
--ttl=300 \
--type=CNAME \
--zone=example-zone
gcloud dns record-sets transaction execute --zone=example-zone
# Update an existing record
gcloud dns record-sets transaction start --zone=example-zone
gcloud dns record-sets transaction remove "203.0.113.1" \
--name="www.example.com." \
--ttl=300 \
--type=A \
--zone=example-zone
gcloud dns record-sets transaction add "203.0.113.2" \
--name="www.example.com." \
--ttl=300 \
--type=A \
--zone=example-zone
gcloud dns record-sets transaction execute --zone=example-zone
# List all records in a zone
gcloud dns record-sets list --zone=example-zone
# Delete a record
gcloud dns record-sets transaction start --zone=example-zone
gcloud dns record-sets transaction remove "mail.example.com." \
--name="alias.example.com." \
--ttl=300 \
--type=CNAME \
--zone=example-zone
gcloud dns record-sets transaction execute --zone=example-zone
Configuring DNSSEC
# Enable DNSSEC for a zone
gcloud dns managed-zones update example-zone \
--dnssec-state=on
# View DNSSEC configuration
gcloud dns managed-zones describe example-zone \
--format="json(dnssecConfig)"
# Get DS record information (to provide to your domain registrar)
gcloud dns dns-keys list --zone=example-zone
Managing DNS Policies
# Create a DNS policy with specific forwarders
gcloud dns policies create custom-forwarding-policy \
--description="Custom DNS forwarding policy" \
--networks=my-vpc-network \
--alternative-name-servers=192.168.1.1,192.168.1.2 \
--enable-logging
# List DNS policies
gcloud dns policies list
# Update a DNS policy
gcloud dns policies update custom-forwarding-policy \
--enable-logging
# Delete a DNS policy
gcloud dns policies delete custom-forwarding-policy
Real-World Example: Multi-VPC DNS Architecture
This example demonstrates a complete multi-VPC DNS setup with private zones, forwarding, and DNS peering:
Architecture Overview
Hub & Spoke VPC network architecture
Central DNS management in Hub VPC
DNS forwarding for on-premises integration
DNS peering to shared services
Service Discovery support
Terraform Implementation
provider "google" {
project = var.project_id
region = var.region
}
# Variables
variable "project_id" {
description = "The GCP project ID"
type = string
}
variable "region" {
description = "The default region"
type = string
default = "us-central1"
}
variable "on_prem_dns" {
description = "On-premises DNS server IPs"
type = list(string)
default = ["192.168.1.10", "192.168.1.11"]
}
# Create the Hub VPC
resource "google_compute_network" "hub_vpc" {
name = "hub-vpc"
auto_create_subnetworks = false
description = "Hub VPC for centralized services"
}
resource "google_compute_subnetwork" "hub_subnet" {
name = "hub-subnet"
ip_cidr_range = "10.0.0.0/24"
region = var.region
network = google_compute_network.hub_vpc.id
}
# Create Spoke VPCs
resource "google_compute_network" "spoke_vpc_1" {
name = "spoke-vpc-1"
auto_create_subnetworks = false
description = "Spoke VPC 1 for application workloads"
}
resource "google_compute_subnetwork" "spoke_subnet_1" {
name = "spoke-subnet-1"
ip_cidr_range = "10.1.0.0/24"
region = var.region
network = google_compute_network.spoke_vpc_1.id
}
resource "google_compute_network" "spoke_vpc_2" {
name = "spoke-vpc-2"
auto_create_subnetworks = false
description = "Spoke VPC 2 for database workloads"
}
resource "google_compute_subnetwork" "spoke_subnet_2" {
name = "spoke-subnet-2"
ip_cidr_range = "10.2.0.0/24"
region = var.region
network = google_compute_network.spoke_vpc_2.id
}
# Create VPC peering between Hub and Spokes
resource "google_compute_network_peering" "hub_to_spoke1" {
name = "hub-to-spoke1"
network = google_compute_network.hub_vpc.id
peer_network = google_compute_network.spoke_vpc_1.id
export_custom_routes = true
import_custom_routes = true
}
resource "google_compute_network_peering" "spoke1_to_hub" {
name = "spoke1-to-hub"
network = google_compute_network.spoke_vpc_1.id
peer_network = google_compute_network.hub_vpc.id
export_custom_routes = true
import_custom_routes = true
}
resource "google_compute_network_peering" "hub_to_spoke2" {
name = "hub-to-spoke2"
network = google_compute_network.hub_vpc.id
peer_network = google_compute_network.spoke_vpc_2.id
export_custom_routes = true
import_custom_routes = true
}
resource "google_compute_network_peering" "spoke2_to_hub" {
name = "spoke2-to-hub"
network = google_compute_network.spoke_vpc_2.id
peer_network = google_compute_network.hub_vpc.id
export_custom_routes = true
import_custom_routes = true
}
# Create DNS zones
# 1. Main private zone in Hub VPC
resource "google_dns_managed_zone" "internal_corp" {
name = "internal-corp"
dns_name = "corp.internal."
description = "Main corporate private DNS zone"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.hub_vpc.id
}
}
}
# 2. DNS zones for the spoke VPCs
resource "google_dns_managed_zone" "apps_zone" {
name = "apps-zone"
dns_name = "apps.corp.internal."
description = "Private DNS zone for applications"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.spoke_vpc_1.id
}
}
}
resource "google_dns_managed_zone" "db_zone" {
name = "db-zone"
dns_name = "db.corp.internal."
description = "Private DNS zone for databases"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.spoke_vpc_2.id
}
}
}
# 3. DNS peering from Hub to Spokes
resource "google_dns_managed_zone" "hub_to_apps_peer" {
name = "hub-to-apps-peer"
dns_name = "apps.corp.internal."
description = "DNS peering from Hub to Applications"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.hub_vpc.id
}
}
peering_config {
target_network {
network_url = google_compute_network.spoke_vpc_1.id
}
}
}
resource "google_dns_managed_zone" "hub_to_db_peer" {
name = "hub-to-db-peer"
dns_name = "db.corp.internal."
description = "DNS peering from Hub to Databases"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.hub_vpc.id
}
}
peering_config {
target_network {
network_url = google_compute_network.spoke_vpc_2.id
}
}
}
# 4. DNS zone for on-prem domain
resource "google_dns_managed_zone" "onprem_zone" {
name = "onprem-zone"
dns_name = "onprem.example."
description = "Zone for on-premises domain"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.hub_vpc.id
}
}
forwarding_config {
target_name_servers {
ipv4_address = var.on_prem_dns[0]
}
target_name_servers {
ipv4_address = var.on_prem_dns[1]
}
}
}
# Create DNS policy for outbound server policy
resource "google_dns_policy" "dns_forwarding_policy" {
name = "dns-forwarding-policy"
networks {
network_url = google_compute_network.hub_vpc.id
}
enable_inbound = false
alternative_name_server_config {
target_name_servers {
ipv4_address = var.on_prem_dns[0]
}
target_name_servers {
ipv4_address = var.on_prem_dns[1]
}
}
}
# Create DNS records for services
# 1. Common services in the hub VPC
resource "google_dns_record_set" "hub_services" {
name = "services.corp.internal."
managed_zone = google_dns_managed_zone.internal_corp.name
type = "A"
ttl = 300
rrdatas = ["10.0.0.10"]
}
resource "google_dns_record_set" "hub_proxy" {
name = "proxy.corp.internal."
managed_zone = google_dns_managed_zone.internal_corp.name
type = "A"
ttl = 300
rrdatas = ["10.0.0.20"]
}
# 2. Application services in spoke VPC 1
resource "google_dns_record_set" "app_frontend" {
name = "frontend.apps.corp.internal."
managed_zone = google_dns_managed_zone.apps_zone.name
type = "A"
ttl = 300
rrdatas = ["10.1.0.10"]
}
resource "google_dns_record_set" "app_backend" {
name = "backend.apps.corp.internal."
managed_zone = google_dns_managed_zone.apps_zone.name
type = "A"
ttl = 300
rrdatas = ["10.1.0.20"]
}
# 3. Database services in spoke VPC 2
resource "google_dns_record_set" "db_primary" {
name = "primary.db.corp.internal."
managed_zone = google_dns_managed_zone.db_zone.name
type = "A"
ttl = 300
rrdatas = ["10.2.0.10"]
}
resource "google_dns_record_set" "db_replica" {
name = "replica.db.corp.internal."
managed_zone = google_dns_managed_zone.db_zone.name
type = "A"
ttl = 300
rrdatas = ["10.2.0.11"]
}
# 4. Service discovery using SRV records
resource "google_dns_record_set" "service_discovery" {
name = "_http._tcp.services.corp.internal."
managed_zone = google_dns_managed_zone.internal_corp.name
type = "SRV"
ttl = 300
rrdatas = [
"0 1 8080 frontend.apps.corp.internal.",
"0 1 8080 backend.apps.corp.internal."
]
}
# Output the name servers and information
output "dns_zones" {
value = {
internal_corp = google_dns_managed_zone.internal_corp.dns_name
apps_zone = google_dns_managed_zone.apps_zone.dns_name
db_zone = google_dns_managed_zone.db_zone.dns_name
onprem_zone = google_dns_managed_zone.onprem_zone.dns_name
}
}
output "app_services" {
value = {
frontend = google_dns_record_set.app_frontend.name
backend = google_dns_record_set.app_backend.name
}
}
output "db_services" {
value = {
primary = google_dns_record_set.db_primary.name
replica = google_dns_record_set.db_replica.name
}
}
Testing DNS Resolution Script
#!/bin/bash
# test-dns-resolution.sh - Tests DNS resolution across the network architecture
# Base variables
PROJECT_ID=$(gcloud config get-value project)
REGION=$(gcloud config get-value compute/region 2>/dev/null || echo "us-central1")
BASE_CMD="gcloud compute ssh"
# Test from each VPC
test_resolution() {
local vm=$1
local zone=$2
echo "==== Testing DNS Resolution from $vm in $zone ===="
# Define domains to test
local domains=(
"services.corp.internal"
"proxy.corp.internal"
"frontend.apps.corp.internal"
"backend.apps.corp.internal"
"primary.db.corp.internal"
"replica.db.corp.internal"
"onprem-server.onprem.example"
)
for domain in "${domains[@]}"; do
echo "Resolving $domain..."
$BASE_CMD $vm --zone $zone --command "nslookup $domain" -- -q
echo ""
done
# Test service discovery
echo "Testing service discovery SRV record:"
$BASE_CMD $vm --zone $zone --command "nslookup -type=SRV _http._tcp.services.corp.internal" -- -q
echo ""
}
# Create test VMs if they don't exist
echo "Checking for test VMs..."
# Hub VM
if ! gcloud compute instances describe hub-test-vm --zone $REGION-a &>/dev/null; then
echo "Creating hub-test-vm..."
gcloud compute instances create hub-test-vm \
--zone=$REGION-a \
--machine-type=e2-micro \
--subnet=hub-subnet \
--image-family=debian-11 \
--image-project=debian-cloud
fi
# Spoke1 VM
if ! gcloud compute instances describe spoke1-test-vm --zone $REGION-a &>/dev/null; then
echo "Creating spoke1-test-vm..."
gcloud compute instances create spoke1-test-vm \
--zone=$REGION-a \
--machine-type=e2-micro \
--subnet=spoke-subnet-1 \
--image-family=debian-11 \
--image-project=debian-cloud
fi
# Spoke2 VM
if ! gcloud compute instances describe spoke2-test-vm --zone $REGION-a &>/dev/null; then
echo "Creating spoke2-test-vm..."
gcloud compute instances create spoke2-test-vm \
--zone=$REGION-a \
--machine-type=e2-micro \
--subnet=spoke-subnet-2 \
--image-family=debian-11 \
--image-project=debian-cloud
fi
# Run tests
test_resolution "hub-test-vm" "$REGION-a"
test_resolution "spoke1-test-vm" "$REGION-a"
test_resolution "spoke2-test-vm" "$REGION-a"
echo "DNS resolution testing complete."
Best Practices
Zone Design
Use descriptive zone names that reflect their purpose
Follow a consistent naming convention
Keep zone structure flat when possible
Consider separate zones for different environments
Performance and Reliability
Set appropriate TTL values (lower for frequently changing records)
Avoid excessive DNS lookups in application code
Use multi-region deployments for public-facing services
Implement DNS monitoring and alerting
Security
Enable DNSSEC for public zones
Implement strict policies for DNS zone access
Limit who can modify DNS records
Use private zones for internal services
Enable logging for audit purposes
Operational Excellence
Use Infrastructure as Code for all DNS configurations
Document DNS architecture and record management processes
Implement automated testing for DNS resolution
Create runbooks for common DNS operations
Cost Optimization
Clean up unused DNS zones and records
Monitor query volumes for unexpected spikes
Consolidate zones where appropriate
Use Cloud DNS Policies efficiently
Common Issues and Troubleshooting
Resolution Issues
Verify VPC network attachments for private zones
Check that DNS peering is correctly configured
Ensure proper IAM permissions for DNS management
Test resolution from different VPC networks
Check for conflicting or overlapping DNS zones
Propagation Delays
Allow sufficient time for DNS changes to propagate
Check if TTL values are set appropriately
Use DNS monitoring tools to verify propagation
Consider reducing TTL before planned changes
Test from multiple global regions
DNSSEC Problems
Ensure correct DS records are published at the parent zone
Verify DNSSEC key signing key (KSK) and zone signing key (ZSK)
Check for DNSSEC validation errors in logs
Test DNSSEC validation with online tools
Allow time for DNSSEC changes to propagate
Integration with On-Premises DNS
Verify DNS forwarding configuration
Check firewall rules for DNS traffic (port 53)
Test DNS resolution in both directions
Configure conditional forwarding on on-premises DNS servers