Examples¶
Practical examples and complete workflows for common bindcar use cases.
Complete Zone Lifecycle¶
Create, Update, and Delete a Zone¶
#!/bin/bash
set -e
TOKEN="your-secret-token"
BASE_URL="http://localhost:8080/api/v1"
# 1. Create zone
echo "Creating zone example.com..."
curl -X POST "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"zoneName": "example.com",
"zoneType": "primary",
"zoneConfig": {
"ttl": 3600,
"soa": {
"primaryNs": "ns1.example.com.",
"adminEmail": "admin.example.com.",
"serial": 1,
"refresh": 3600,
"retry": 1800,
"expire": 604800,
"negativeTtl": 86400
},
"records": [
{
"name": "@",
"type": "NS",
"value": "ns1.example.com."
},
{
"name": "@",
"type": "A",
"value": "192.0.2.1"
},
{
"name": "www",
"type": "A",
"value": "192.0.2.10"
}
]
}
}'
echo -e "\n\n2. Verify zone was created..."
curl "$BASE_URL/zones/example.com" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\n3. Check zone status..."
curl "$BASE_URL/zones/example.com/status" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\n4. Reload zone..."
curl -X POST "$BASE_URL/zones/example.com/reload" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\n5. List all zones..."
curl "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\n6. Delete zone..."
curl -X DELETE "$BASE_URL/zones/example.com" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\nDone!"
DNS Record Management¶
Add, Update, and Remove Individual Records¶
#!/bin/bash
set -e
TOKEN="your-secret-token"
BASE_URL="http://localhost:8080/api/v1"
ZONE="example.com"
# Prerequisites: Zone must be created with dynamic updates enabled
echo "Creating zone with dynamic updates enabled..."
curl -X POST "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"zoneName\": \"$ZONE\",
\"zoneType\": \"primary\",
\"zoneConfig\": {
\"ttl\": 3600,
\"soa\": {
\"primaryNs\": \"ns1.$ZONE.\",
\"adminEmail\": \"admin.$ZONE.\",
\"serial\": 1,
\"refresh\": 3600,
\"retry\": 1800,
\"expire\": 604800,
\"negativeTtl\": 86400
},
\"updateKeyName\": \"update-key\",
\"records\": [
{\"name\": \"@\", \"type\": \"NS\", \"value\": \"ns1.$ZONE.\"}
]
}
}"
# 1. Add A record
echo -e "\n\n1. Adding A record for www..."
curl -X POST "$BASE_URL/zones/$ZONE/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "www",
"type": "A",
"value": "192.0.2.100",
"ttl": 3600
}'
# 2. Add another A record (same name, different IP)
echo -e "\n\n2. Adding second A record for www (load balancing)..."
curl -X POST "$BASE_URL/zones/$ZONE/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "www",
"type": "A",
"value": "192.0.2.101",
"ttl": 3600
}'
# 3. Add MX record
echo -e "\n\n3. Adding MX record..."
curl -X POST "$BASE_URL/zones/$ZONE/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "@",
"type": "MX",
"value": "mail.example.com.",
"ttl": 3600,
"priority": 10
}'
# 4. Update an A record
echo -e "\n\n4. Updating A record (changing IP)..."
curl -X PUT "$BASE_URL/zones/$ZONE/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "www",
"type": "A",
"currentValue": "192.0.2.100",
"newValue": "192.0.2.102",
"ttl": 7200
}'
# 5. Remove specific A record
echo -e "\n\n5. Removing specific A record..."
curl -X DELETE "$BASE_URL/zones/$ZONE/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "www",
"type": "A",
"value": "192.0.2.101"
}'
# 6. Verify with dig
echo -e "\n\n6. Verifying DNS records..."
dig @localhost www.$ZONE +short
dig @localhost $ZONE MX +short
echo -e "\n\nDone!"
Python: Dynamic Record Management¶
#!/usr/bin/env python3
import requests
from typing import Optional
BASE_URL = "http://localhost:8080/api/v1"
TOKEN = "your-secret-token"
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
class RecordManager:
"""Manage individual DNS records dynamically."""
def __init__(self, zone_name: str):
self.zone_name = zone_name
self.base_path = f"{BASE_URL}/zones/{zone_name}/records"
def add_record(self, name: str, record_type: str, value: str,
ttl: int = 3600, priority: Optional[int] = None):
"""Add a DNS record to the zone."""
data = {
"name": name,
"type": record_type,
"value": value,
"ttl": ttl
}
if priority is not None:
data["priority"] = priority
response = requests.post(self.base_path, headers=headers, json=data)
if response.status_code == 201:
print(f"✓ Added {record_type} record: {name} -> {value}")
return response.json()
else:
print(f"✗ Failed to add record: {response.text}")
return None
def remove_record(self, name: str, record_type: str, value: str):
"""Remove a specific DNS record."""
data = {
"name": name,
"type": record_type,
"value": value
}
response = requests.delete(self.base_path, headers=headers, json=data)
if response.status_code == 200:
print(f"✓ Removed {record_type} record: {name} ({value})")
return response.json()
else:
print(f"✗ Failed to remove record: {response.text}")
return None
def update_record(self, name: str, record_type: str,
current_value: str, new_value: str, ttl: int = 3600):
"""Update an existing DNS record."""
data = {
"name": name,
"type": record_type,
"currentValue": current_value,
"newValue": new_value,
"ttl": ttl
}
response = requests.put(self.base_path, headers=headers, json=data)
if response.status_code == 200:
print(f"✓ Updated {record_type} record: {name} {current_value} -> {new_value}")
return response.json()
else:
print(f"✗ Failed to update record: {response.text}")
return None
# Example usage
manager = RecordManager("example.com")
# Add web servers
manager.add_record("web1", "A", "192.0.2.10")
manager.add_record("web2", "A", "192.0.2.11")
manager.add_record("web3", "A", "192.0.2.12")
# Add round-robin DNS
manager.add_record("www", "A", "192.0.2.10")
manager.add_record("www", "A", "192.0.2.11")
manager.add_record("www", "A", "192.0.2.12")
# Update one server
manager.update_record("web1", "A", "192.0.2.10", "192.0.2.20")
# Remove a failed server from rotation
manager.remove_record("www", "A", "192.0.2.11")
# Add CNAME
manager.add_record("app", "CNAME", "web1.example.com.")
Multi-Zone Setup¶
Create Multiple Related Zones¶
#!/usr/bin/env python3
import requests
import json
BASE_URL = "http://localhost:8080/api/v1"
TOKEN = "your-secret-token"
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def create_zone(domain, records):
"""Create a DNS zone with records."""
zone_data = {
"zoneName": domain,
"zoneType": "primary",
"zoneConfig": {
"ttl": 3600,
"soa": {
"primaryNs": f"ns1.{domain}.",
"adminEmail": f"admin.{domain}.",
"serial": 1,
"refresh": 3600,
"retry": 1800,
"expire": 604800,
"negativeTtl": 86400
},
"records": records
}
}
response = requests.post(
f"{BASE_URL}/zones",
headers=headers,
json=zone_data
)
if response.status_code == 201:
print(f"✓ Created zone: {domain}")
else:
print(f"✗ Failed to create {domain}: {response.text}")
return response
# Create main domain
create_zone("example.com", [
{"name": "@", "type": "NS", "value": "ns1.example.com."},
{"name": "@", "type": "A", "value": "192.0.2.1"},
{"name": "ns1", "type": "A", "value": "192.0.2.10"},
{"name": "ns2", "type": "A", "value": "192.0.2.11"},
{"name": "www", "type": "A", "value": "192.0.2.20"},
{"name": "mail", "type": "A", "value": "192.0.2.30"},
{"name": "@", "type": "MX", "value": "mail.example.com.", "priority": 10},
{"name": "@", "type": "TXT", "value": "v=spf1 mx -all"}
])
# Create development subdomain
create_zone("dev.example.com", [
{"name": "@", "type": "NS", "value": "ns1.example.com."},
{"name": "@", "type": "A", "value": "192.0.2.100"},
{"name": "api", "type": "A", "value": "192.0.2.101"},
{"name": "web", "type": "A", "value": "192.0.2.102"}
])
# Create staging subdomain
create_zone("staging.example.com", [
{"name": "@", "type": "NS", "value": "ns1.example.com."},
{"name": "@", "type": "A", "value": "192.0.2.200"},
{"name": "api", "type": "A", "value": "192.0.2.201"},
{"name": "web", "type": "A", "value": "192.0.2.202"}
])
# List all zones
response = requests.get(f"{BASE_URL}/zones", headers=headers)
print(f"\nTotal zones: {len(response.json()['zones'])}")
print("Zones:", response.json()['zones'])
Kubernetes Automation¶
GitOps Zone Management¶
# zone-creator-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: create-dns-zones
spec:
template:
spec:
serviceAccountName: bindcar-client
containers:
- name: zone-creator
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
set -e
# Get service account token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
BINDCAR_URL="http://bindcar-service:8080/api/v1"
# Function to create zone
create_zone() {
DOMAIN=$1
echo "Creating zone: $DOMAIN"
curl -X POST "$BINDCAR_URL/zones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"zoneName\": \"$DOMAIN\",
\"zoneType\": \"master\",
\"zoneConfig\": {
\"ttl\": 3600,
\"soa\": {
\"primaryNs\": \"ns1.$DOMAIN.\",
\"adminEmail\": \"admin.$DOMAIN.\",
\"serial\": 1,
\"refresh\": 3600,
\"retry\": 1800,
\"expire\": 604800,
\"negativeTtl\": 86400
},
\"records\": [
{\"name\": \"@\", \"type\": \"NS\", \"value\": \"ns1.$DOMAIN.\"},
{\"name\": \"@\", \"type\": \"A\", \"value\": \"192.0.2.1\"}
]
}
}"
}
# Create zones from list
create_zone "prod.example.com"
create_zone "staging.example.com"
create_zone "dev.example.com"
echo "All zones created successfully!"
restartPolicy: OnFailure
---
# ServiceAccount with permissions
apiVersion: v1
kind: ServiceAccount
metadata:
name: bindcar-client
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: bindcar-client-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: bindcar-api-user
subjects:
- kind: ServiceAccount
name: bindcar-client
Zone Backup CronJob¶
# zone-backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: dns-zone-backup
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
serviceAccountName: bindcar-client
containers:
- name: backup
image: curlimages/curl:latest
volumeMounts:
- name: backup
mountPath: /backup
command:
- /bin/sh
- -c
- |
set -e
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
BINDCAR_URL="http://bindcar-service:8080/api/v1"
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Get list of zones
ZONES=$(curl -s "$BINDCAR_URL/zones" \
-H "Authorization: Bearer $TOKEN" | \
jq -r '.zones[]')
# Backup each zone
for ZONE in $ZONES; do
echo "Backing up $ZONE..."
curl -s "$BINDCAR_URL/zones/$ZONE" \
-H "Authorization: Bearer $TOKEN" \
> "$BACKUP_DIR/$ZONE.json"
done
echo "Backup completed: $BACKUP_DIR"
ls -lh "$BACKUP_DIR"
volumes:
- name: backup
persistentVolumeClaim:
claimName: dns-backup-pvc
restartPolicy: OnFailure
Reverse DNS Setup¶
Create PTR Records for IP Ranges¶
#!/bin/bash
# create-ptr-zones.sh
TOKEN="your-secret-token"
BASE_URL="http://localhost:8080/api/v1"
# Function to create reverse DNS zone
create_reverse_zone() {
local SUBNET=$1 # e.g., "192.0.2"
local ZONE_NAME="${SUBNET##*.}.${SUBNET%.*}.in-addr.arpa"
echo "Creating reverse zone: $ZONE_NAME"
curl -X POST "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"zoneName\": \"$ZONE_NAME\",
\"zoneType\": \"master\",
\"zoneConfig\": {
\"ttl\": 3600,
\"soa\": {
\"primaryNs\": \"ns1.example.com.\",
\"adminEmail\": \"admin.example.com.\",
\"serial\": 1,
\"refresh\": 3600,
\"retry\": 1800,
\"expire\": 604800,
\"negativeTtl\": 86400
},
\"records\": [
{\"name\": \"@\", \"type\": \"NS\", \"value\": \"ns1.example.com.\"}
]
}
}"
}
# Function to add PTR record
add_ptr_record() {
local IP=$1
local HOSTNAME=$2
local SUBNET="${IP%.*}"
local LAST_OCTET="${IP##*.}"
local ZONE_NAME="${SUBNET##*.}.${SUBNET%.*}.in-addr.arpa"
echo "Adding PTR: $IP -> $HOSTNAME"
curl -X POST "$BASE_URL/zones/$ZONE_NAME/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$LAST_OCTET\",
\"type\": \"PTR\",
\"value\": \"$HOSTNAME.\",
\"ttl\": 3600
}"
}
# Create reverse zones for IP ranges
create_reverse_zone "192.0.2"
create_reverse_zone "192.0.3"
# Add PTR records using dynamic updates
add_ptr_record "192.0.2.1" "ns1.example.com"
add_ptr_record "192.0.2.10" "web1.example.com"
add_ptr_record "192.0.2.20" "mail.example.com"
High Availability DNS Setup¶
Primary-Secondary Zone Replication¶
Configure zone transfers between primary and secondary DNS servers for high availability:
#!/bin/bash
# create-ha-zone.sh
TOKEN="your-secret-token"
BASE_URL="http://localhost:8080/api/v1"
# Secondary DNS server IPs
SECONDARY_IPS=("10.244.2.101" "10.244.2.102")
echo "Creating HA zone with automatic replication..."
curl -X POST "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"zoneName": "example.com",
"zoneType": "primary",
"zoneConfig": {
"ttl": 3600,
"soa": {
"primaryNs": "ns1.example.com.",
"adminEmail": "admin.example.com.",
"serial": 1,
"refresh": 3600,
"retry": 600,
"expire": 604800,
"negativeTtl": 86400
},
"nameServers": ["ns1.example.com.", "ns2.example.com."],
"nameServerIps": {
"ns1.example.com.": "10.244.1.101",
"ns2.example.com.": "10.244.2.101"
},
"records": [
{
"name": "@",
"type": "A",
"value": "192.0.2.1"
},
{
"name": "www",
"type": "A",
"value": "192.0.2.10"
}
],
"alsoNotify": ["10.244.2.101", "10.244.2.102"],
"allowTransfer": ["10.244.2.101", "10.244.2.102"]
}
}'
echo -e "\n\nZone created with automatic notification to secondary servers:"
echo " - Secondary 1: 10.244.2.101"
echo " - Secondary 2: 10.244.2.102"
echo ""
echo "Zone transfers are allowed from these IPs"
echo "Secondaries will be notified when the zone changes"
Python Helper for Multi-Primary Setup¶
#!/usr/bin/env python3
"""
Create primary zones with automatic secondary notifications in Kubernetes.
"""
import requests
from kubernetes import client, config
# Load Kubernetes config
config.load_incluster_config()
v1 = client.CoreV1Api()
BINDCAR_URL = "http://bindcar-service:8080/api/v1"
with open("/var/run/secrets/kubernetes.io/serviceaccount/token") as f:
TOKEN = f.read().strip()
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def get_secondary_ips(namespace="default", label_selector="app=bind9,role=secondary"):
"""Get IPs of all secondary BIND9 pods."""
pods = v1.list_namespaced_pod(namespace, label_selector=label_selector)
return [pod.status.pod_ip for pod in pods.items if pod.status.pod_ip]
def create_ha_zone(domain, records, primary_ns_ip):
"""Create a zone configured for high availability."""
# Discover secondary servers
secondary_ips = get_secondary_ips()
if not secondary_ips:
print("⚠️ Warning: No secondary servers found")
zone_data = {
"zoneName": domain,
"zoneType": "primary",
"zoneConfig": {
"ttl": 3600,
"soa": {
"primaryNs": f"ns1.{domain}.",
"adminEmail": f"admin.{domain}.",
"serial": 1,
"refresh": 3600,
"retry": 600,
"expire": 604800,
"negativeTtl": 86400
},
"nameServers": [f"ns1.{domain}.", f"ns2.{domain}."],
"nameServerIps": {
f"ns1.{domain}.": primary_ns_ip,
f"ns2.{domain}.": secondary_ips[0] if secondary_ips else "127.0.0.2"
},
"records": records,
"alsoNotify": secondary_ips,
"allowTransfer": secondary_ips
}
}
response = requests.post(
f"{BINDCAR_URL}/zones",
headers=headers,
json=zone_data
)
if response.status_code == 201:
print(f"✓ Created HA zone: {domain}")
print(f" Primary NS: {primary_ns_ip}")
print(f" Secondaries: {', '.join(secondary_ips) if secondary_ips else 'none'}")
else:
print(f"✗ Failed to create {domain}: {response.text}")
return response
# Example: Create production zone with HA
create_ha_zone(
domain="prod.example.com",
records=[
{"name": "@", "type": "A", "value": "192.0.2.1"},
{"name": "www", "type": "A", "value": "192.0.2.10"},
{"name": "api", "type": "A", "value": "192.0.2.20"},
],
primary_ns_ip="10.244.1.101"
)
Kubernetes StatefulSet with Zone Transfers¶
# bind9-ha-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: bind9-primary
spec:
selector:
app: bind9
role: primary
ports:
- name: dns-tcp
port: 53
protocol: TCP
- name: dns-udp
port: 53
protocol: UDP
- name: rndc
port: 953
---
apiVersion: v1
kind: Service
metadata:
name: bind9-secondary
spec:
selector:
app: bind9
role: secondary
ports:
- name: dns-tcp
port: 53
protocol: TCP
- name: dns-udp
port: 53
protocol: UDP
---
# Primary BIND9 server
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: bind9-primary
spec:
serviceName: bind9-primary
replicas: 1
selector:
matchLabels:
app: bind9
role: primary
template:
metadata:
labels:
app: bind9
role: primary
spec:
containers:
- name: bind9
image: ubuntu/bind9:latest
ports:
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 53
name: dns-udp
protocol: UDP
- containerPort: 953
name: rndc
volumeMounts:
- name: zones
mountPath: /var/cache/bind
- name: rndc-key
mountPath: /etc/bind/rndc.key
subPath: rndc.key
- name: bindcar
image: ghcr.io/firestoned/bindcar:latest
ports:
- containerPort: 8080
name: api
volumeMounts:
- name: zones
mountPath: /var/cache/bind
- name: rndc-key
mountPath: /etc/bind/rndc.key
subPath: rndc.key
volumes:
- name: zones
emptyDir: {}
- name: rndc-key
secret:
secretName: rndc-key
---
# Secondary BIND9 servers
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: bind9-secondary
spec:
serviceName: bind9-secondary
replicas: 2
selector:
matchLabels:
app: bind9
role: secondary
template:
metadata:
labels:
app: bind9
role: secondary
spec:
containers:
- name: bind9
image: ubuntu/bind9:latest
ports:
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 53
name: dns-udp
protocol: UDP
volumeMounts:
- name: zones
mountPath: /var/cache/bind
- name: config
mountPath: /etc/bind/named.conf.local
subPath: named.conf.local
volumes:
- name: zones
emptyDir: {}
- name: config
configMap:
name: bind9-secondary-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: bind9-secondary-config
data:
named.conf.local: |
// Secondary zones will be automatically added via zone transfers
// from primary server
Dynamic Zone Updates¶
Service Discovery Integration¶
#!/usr/bin/env python3
"""
Update DNS zones based on Kubernetes service discovery.
"""
import requests
from kubernetes import client, config
# Load k8s config
config.load_incluster_config()
v1 = client.CoreV1Api()
# bindcar configuration
BINDCAR_URL = "http://bindcar-service:8080/api/v1"
with open("/var/run/secrets/kubernetes.io/serviceaccount/token") as f:
TOKEN = f.read().strip()
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def get_service_ips(namespace="default"):
"""Get all service ClusterIPs."""
services = v1.list_namespaced_service(namespace)
return {
svc.metadata.name: svc.spec.cluster_ip
for svc in services.items
if svc.spec.cluster_ip not in [None, "None"]
}
def create_internal_dns_zone():
"""Create internal DNS zone for services."""
services = get_service_ips()
records = [
{"name": "@", "type": "NS", "value": "ns1.cluster.local."}
]
# Add A record for each service
for svc_name, svc_ip in services.items():
records.append({
"name": svc_name,
"type": "A",
"value": svc_ip
})
zone_data = {
"zoneName": "services.cluster.local",
"zoneType": "primary",
"zoneConfig": {
"ttl": 30, # Short TTL for dynamic services
"soa": {
"primaryNs": "ns1.cluster.local.",
"adminEmail": "admin.cluster.local.",
"serial": 1,
"refresh": 60,
"retry": 30,
"expire": 604800,
"negativeTtl": 30
},
"records": records
}
}
# Create or update zone
response = requests.post(
f"{BINDCAR_URL}/zones",
headers=headers,
json=zone_data
)
if response.status_code in [201, 409]: # Created or already exists
print(f"Zone created/updated with {len(services)} services")
# Reload zone to pick up changes
requests.post(
f"{BINDCAR_URL}/zones/services.cluster.local/reload",
headers=headers
)
else:
print(f"Error: {response.text}")
if __name__ == "__main__":
create_internal_dns_zone()
Zone Configuration Updates¶
Modifying Zone Transfer Settings¶
Update also-notify and allow-transfer settings without recreating the zone.
#!/bin/bash
set -e
TOKEN="your-secret-token"
BASE_URL="http://localhost:8080/api/v1"
ZONE="example.com"
# Add secondary DNS servers to also-notify list
echo "Adding secondary servers to also-notify..."
curl -X PATCH "$BASE_URL/zones/$ZONE" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"alsoNotify": ["10.244.2.101", "10.244.2.102"],
"allowTransfer": ["10.244.2.101", "10.244.2.102"]
}'
echo -e "\n\nVerifying zone configuration..."
curl "$BASE_URL/zones/$ZONE/status" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\nNotifying secondary servers..."
curl -X POST "$BASE_URL/zones/$ZONE/notify" \
-H "Authorization: Bearer $TOKEN"
echo -e "\n\nDone!"
Python: Update Zone Transfer Configuration¶
#!/usr/bin/env python3
import requests
import json
BASE_URL = "http://localhost:8080/api/v1"
TOKEN = "your-secret-token"
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def modify_zone_transfer_config(zone_name, also_notify=None, allow_transfer=None):
"""Update zone transfer configuration."""
url = f"{BASE_URL}/zones/{zone_name}"
data = {}
if also_notify is not None:
data["alsoNotify"] = also_notify
if allow_transfer is not None:
data["allowTransfer"] = allow_transfer
if not data:
print("Error: At least one field must be provided")
return None
response = requests.patch(url, headers=headers, json=data)
if response.status_code == 200:
result = response.json()
print(f"✓ Modified zone: {zone_name}")
print(f" Message: {result.get('message')}")
return result
else:
print(f"✗ Failed to modify {zone_name}: {response.text}")
return None
# Example 1: Add secondary servers
print("Example 1: Add secondary servers")
modify_zone_transfer_config(
zone_name="example.com",
also_notify=["10.244.2.101", "10.244.2.102"],
allow_transfer=["10.244.2.101", "10.244.2.102"]
)
# Example 2: Update only also-notify
print("\nExample 2: Update only also-notify")
modify_zone_transfer_config(
zone_name="example.com",
also_notify=["10.244.2.101", "10.244.2.102", "10.244.2.103"]
)
# Example 3: Clear also-notify
print("\nExample 3: Clear also-notify")
modify_zone_transfer_config(
zone_name="example.com",
also_notify=[]
)
# Example 4: IPv6 addresses
print("\nExample 4: IPv6 addresses")
modify_zone_transfer_config(
zone_name="example.com",
also_notify=["2001:db8::1", "2001:db8::2"],
allow_transfer=["2001:db8::1", "2001:db8::2"]
)
Automated Secondary Server Management¶
#!/usr/bin/env python3
import requests
import json
from typing import List
BASE_URL = "http://localhost:8080/api/v1"
TOKEN = "your-secret-token"
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
class ZoneManager:
"""Manage zone transfer configurations."""
def __init__(self, base_url: str, token: str):
self.base_url = base_url
self.headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
def get_secondary_servers(self) -> List[str]:
"""Get list of secondary DNS servers from your infrastructure."""
# In production, this would query your infrastructure
# For example: Kubernetes API, Consul, etcd, etc.
return [
"10.244.2.101",
"10.244.2.102",
"10.244.2.103"
]
def update_zone_secondaries(self, zone_name: str) -> bool:
"""Update zone to use current secondary servers."""
secondaries = self.get_secondary_servers()
if not secondaries:
print(f"No secondary servers found for {zone_name}")
return False
url = f"{self.base_url}/zones/{zone_name}"
data = {
"alsoNotify": secondaries,
"allowTransfer": secondaries
}
response = requests.patch(url, headers=self.headers, json=data)
if response.status_code == 200:
print(f"✓ Updated {zone_name} with {len(secondaries)} secondaries")
return True
else:
print(f"✗ Failed to update {zone_name}: {response.text}")
return False
def sync_all_zones(self) -> None:
"""Update all zones with current secondary servers."""
# Get all zones
response = requests.get(
f"{self.base_url}/zones",
headers=self.headers
)
if response.status_code != 200:
print(f"Failed to list zones: {response.text}")
return
zones = response.json().get("zones", [])
print(f"Found {len(zones)} zones to update")
success_count = 0
for zone in zones:
if self.update_zone_secondaries(zone):
success_count += 1
print(f"\n✓ Updated {success_count}/{len(zones)} zones")
# Usage
if __name__ == "__main__":
manager = ZoneManager(BASE_URL, TOKEN)
# Option 1: Update a specific zone
manager.update_zone_secondaries("example.com")
# Option 2: Sync all zones (useful for automation)
# manager.sync_all_zones()
Monitoring Integration¶
Prometheus Blackbox Exporter¶
# prometheus-blackbox-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: blackbox-config
data:
blackbox.yml: |
modules:
http_2xx:
prober: http
http:
preferred_ip_protocol: ip4
valid_status_codes: [200]
http_health:
prober: http
http:
method: GET
valid_status_codes: [200]
fail_if_not_matches_regexp:
- '"healthy":\s*true'
---
# ServiceMonitor for health checks
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: bindcar-health
spec:
selector:
matchLabels:
app: dns
endpoints:
- port: api
path: /api/v1/health
interval: 30s
---
# PrometheusRule for alerting
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: bindcar-alerts
spec:
groups:
- name: bindcar
interval: 30s
rules:
- alert: BindcarDown
expr: up{job="bindcar"} == 0
for: 5m
annotations:
summary: "bindcar is down"
description: "bindcar has been down for 5 minutes"
- alert: BindcarHighErrorRate
expr: |
rate(http_requests_total{job="bindcar",status=~"5.."}[5m])
/ rate(http_requests_total{job="bindcar"}[5m]) > 0.1
for: 5m
annotations:
summary: "High error rate in bindcar"
description: "More than 10% of requests are failing"
Testing and Validation¶
Integration Test Suite¶
#!/bin/bash
# test-bindcar-integration.sh
set -e
TOKEN="test-token"
BASE_URL="http://localhost:8080/api/v1"
FAILED=0
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
test_case() {
local NAME=$1
shift
echo -n "Testing: $NAME... "
if "$@" > /dev/null 2>&1; then
echo -e "${GREEN}✓${NC}"
else
echo -e "${RED}✗${NC}"
((FAILED++))
fi
}
# Test health endpoint
test_case "Health check" \
curl -f -s "$BASE_URL/health"
# Test readiness endpoint
test_case "Readiness check" \
curl -f -s "$BASE_URL/ready"
# Test authentication required
test_case "Auth required" \
bash -c "! curl -f -s '$BASE_URL/zones'"
# Test zone creation
test_case "Create zone" \
curl -f -s -X POST "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"zoneName": "test.example.com",
"zoneType": "primary",
"zoneConfig": {
"ttl": 3600,
"soa": {
"primaryNs": "ns1.test.example.com.",
"adminEmail": "admin.test.example.com.",
"serial": 1,
"refresh": 3600,
"retry": 1800,
"expire": 604800,
"negativeTtl": 86400
}
}
}'
# Test zone listing
test_case "List zones" \
curl -f -s "$BASE_URL/zones" \
-H "Authorization: Bearer $TOKEN"
# Test zone retrieval
test_case "Get zone" \
curl -f -s "$BASE_URL/zones/test.example.com" \
-H "Authorization: Bearer $TOKEN"
# Test zone reload
test_case "Reload zone" \
curl -f -s -X POST "$BASE_URL/zones/test.example.com/reload" \
-H "Authorization: Bearer $TOKEN"
# Test duplicate creation fails
test_case "Duplicate fails" \
bash -c "! curl -f -s -X POST '$BASE_URL/zones' \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{
\"zoneName\": \"test.example.com\",
\"zoneType\": \"master\",
\"zoneConfig\": {
\"ttl\": 3600,
\"soa\": {
\"primaryNs\": \"ns1.test.example.com.\",
\"adminEmail\": \"admin.test.example.com.\",
\"serial\": 1,
\"refresh\": 3600,
\"retry\": 1800,
\"expire\": 604800,
\"negativeTtl\": 86400
}
}
}'"
# Test zone deletion
test_case "Delete zone" \
curl -f -s -X DELETE "$BASE_URL/zones/test.example.com" \
-H "Authorization: Bearer $TOKEN"
# Test deleted zone not found
test_case "Deleted zone 404" \
bash -c "! curl -f -s '$BASE_URL/zones/test.example.com' \
-H 'Authorization: Bearer $TOKEN'"
# Summary
echo ""
if [ $FAILED -eq 0 ]; then
echo -e "${GREEN}All tests passed!${NC}"
exit 0
else
echo -e "${RED}$FAILED tests failed${NC}"
exit 1
fi
Next Steps¶
- API Reference - Complete API documentation
- Troubleshooting - Common issues and solutions
- Contributing - Contribute your own examples