Cloud DNS
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
- Check for subnet overlaps causing routing issues