zonewarden
zonewarden
zonewarden is a Kubernetes controller that watches services in managed namespaces and syncs DNS records to bindy (bind9 API).
Overview
zonewarden bridges Kubernetes Services to DNS by:
- Watching namespaces labeled with
dns-managed=true - Monitoring services within those namespaces
- Syncing service IPs to BIND9 zones via the bindy API
This enables automatic DNS registration for services, particularly useful for LoadBalancer services in multi-cluster environments.
How It Fits in the Ecosystem
zonewarden completes the automation story:
- Watches Kubernetes Services automatically
- Creates DNS records via bindy operator
- Works across clusters using Linkerd service mesh
- Eliminates manual DNS - No more hand-editing zone files!
graph LR
svc[Kubernetes Service] -->|Watches| zw[zonewarden]
zw -->|Creates DNSZone/ARecord CRs| k8s[Kubernetes API]
k8s -->|Reconciles| bindy[bindy Operator]
bindy -->|Updates| bind9[BIND9 DNS]
style zw fill:#0066cc,color:#fff
style bindy fill:#0052a3,color:#fffSee also:
- bindy Documentation - The operator that zonewarden integrates with
- Ecosystem Overview - Complete architecture
Architecture
zonewarden runs in a workload cluster and communicates with bindy in a central “mothership” cluster.
Usage
1. Label namespaces for DNS management
kubectl label namespace my-app dns-managed=true
2. Create a ServiceDNSConfig
apiVersion: dns.cf.rbccm.com/v1alpha1
kind: ServiceDNSConfig
metadata:
name: default
namespace: my-app
spec:
zoneRef:
name: apps.rbccm.com
namespace: bindy-system
serviceTypes:
- LoadBalancer
recordNameTemplate: "{service}.{namespace}"
3. Deploy services
Any LoadBalancer service in the namespace will automatically get a DNS record:
apiVersion: v1
kind: Service
metadata:
name: my-api
namespace: my-app
spec:
type: LoadBalancer
ports:
- port: 443
selector:
app: my-api
This creates a DNS record: my-api.my-app.apps.rbccm.com
ServiceDNSConfig Spec
| Field | Description | Default |
|---|---|---|
zoneRef.name | Name of the DNSZone CR | (required) |
zoneRef.namespace | Namespace of the DNSZone | (same namespace) |
serviceSelector.matchLabels | Label selector for services | (all services) |
recordNameTemplate | Template for record names | {service}.{namespace} |
recordType | DNS record type (A/CNAME) | A |
serviceTypes | Service types to sync | [LoadBalancer] |
Troubleshooting
zonewarden Not Creating Records
Services are created but DNS records don’t appear in BIND9
Check:
Namespace is labeled:
kubectl get namespace my-app -o yaml | grep dns-managed # Should show: dns-managed: "true"ServiceDNSConfig exists:
kubectl get servicednsconfig -n my-app # Should show at least one configbindy is running:
kubectl get pods -n dns-system # bindy pod should be RunningCheck zonewarden logs:
kubectl logs -n dns-system deployment/zonewarden -f # Look for reconciliation errors
Services Not Detected
New services created but zonewarden doesn’t process them
Verify:
Service type matches config:
kubectl get svc my-service -o yaml | grep "type:" # Should match serviceTypes in ServiceDNSConfigService has external IP:
kubectl get svc my-service # EXTERNAL-IP should not be <pending>Label selector matches:
# If using serviceSelector in config kubectl get svc my-service --show-labels
Cross-Cluster Communication Fails
zonewarden can’t reach bindy API in mothership cluster
Solutions:
Verify Linkerd is installed:
linkerd check # Should pass all checksCheck service mirror:
kubectl get svc -n dns-system | grep bindy # Should show mirrored service from mothershipTest connectivity:
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \ curl http://bindy.dns-system.svc.cluster.local
Architecture Details
Multi-Cluster Setup
zonewarden enables DNS automation across multiple Kubernetes clusters:
graph TB
subgraph "Workload Cluster A"
nsA[Namespace: app-a<br/>dns-managed=true]
svcA[Service: api]
zwA[zonewarden]
nsA --> svcA
svcA -->|Watches| zwA
end
subgraph "Workload Cluster B"
nsB[Namespace: app-b<br/>dns-managed=true]
svcB[Service: web]
zwB[zonewarden]
nsB --> svcB
svcB -->|Watches| zwB
end
subgraph "Mothership Cluster"
bindy[bindy Operator]
bind9[BIND9 Cluster]
bindy -->|Manages| bind9
end
zwA -->|Linkerd mTLS| bindy
zwB -->|Linkerd mTLS| bindy
style zwA fill:#0066cc,color:#fff
style zwB fill:#0066cc,color:#fff
style bindy fill:#0052a3,color:#fffRecord Naming
zonewarden uses templates to generate DNS record names:
Default template: {service}.{namespace}
Examples:
- Service
apiin namespaceproduction→api.production.example.com - Service
webin namespacestaging→web.staging.example.com
Custom template: {service}-{namespace}-cluster-a
Result:
- Service
apiin namespaceprod→api-prod-cluster-a.example.com
Common Patterns
Pattern 1: Per-Namespace DNS Zones
Create separate zones for each namespace:
---
# Namespace configuration
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
dns-managed: "true"
---
# Zone for this namespace
apiVersion: bindy.firestoned.io/v1alpha1
kind: DNSZone
metadata:
name: production-zone
namespace: dns-system
spec:
zoneName: production.example.com
---
# Auto-register services
apiVersion: dns.cf.rbccm.com/v1alpha1
kind: ServiceDNSConfig
metadata:
name: auto-dns
namespace: production
spec:
zoneRef:
name: production-zone
namespace: dns-system
recordNameTemplate: "{service}"
Pattern 2: Selective Service Registration
Only register services with specific labels:
apiVersion: dns.cf.rbccm.com/v1alpha1
kind: ServiceDNSConfig
metadata:
name: public-services
namespace: production
spec:
zoneRef:
name: public-zone
serviceSelector:
matchLabels:
expose: "external"
serviceTypes:
- LoadBalancer
Then label services to expose:
apiVersion: v1
kind: Service
metadata:
name: api
labels:
expose: "external" # Will be registered
spec:
type: LoadBalancer
Pattern 3: Multi-Region with Geo-DNS
Use different record names per region:
# US region
apiVersion: dns.cf.rbccm.com/v1alpha1
kind: ServiceDNSConfig
metadata:
name: us-services
namespace: apps
spec:
zoneRef:
name: global-zone
recordNameTemplate: "{service}.us"
---
# EU region
apiVersion: dns.cf.rbccm.com/v1alpha1
kind: ServiceDNSConfig
metadata:
name: eu-services
namespace: apps
spec:
zoneRef:
name: global-zone
recordNameTemplate: "{service}.eu"
Result:
api.us.example.com→ US cluster IPapi.eu.example.com→ EU cluster IP
Next Steps
- Install bindy first - Required before zonewarden
- Ecosystem Overview - Understand the complete architecture
- GitHub Repository - Source code (architecture only currently)