This guide walks through deploying ESDDNS as a Kubernetes operator with a LoadBalancer service for automatic DNS synchronization.
k8s/
├── base/ # Base configuration
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ ├── serviceaccount.yaml
│ ├── clusterrole.yaml
│ ├── clusterrolebinding.yaml
│ ├── daemon-deployment.yaml # Kopf operator DaemonSet
│ ├── service-deployment.yaml # Flask web service
│ ├── service.yaml # LoadBalancer service
│ ├── secrets.yaml
│ └── configmap.yaml
│
├── overlays/
│ ├── development/ # Dev environment overrides
│ │ ├── kustomization.yaml
│ │ ├── daemon-dev-patch.yaml
│ │ └── service-dev-patch.yaml
│ │
│ └── production/ # Prod environment overrides
│ ├── kustomization.yaml
│ ├── daemon-prod-patch.yaml
│ └── service-prod-patch.yaml
│
└── monitoring/ # Prometheus monitoring
├── prometheus-servicemonitor.yaml
└── prometheus-rules.yaml
kubectl configured with cluster accesskustomize CLI (v5.0+)Update the ConfigMap with your domain information:
# Edit configuration
kubectl kustomize k8s/overlays/production | \
yq eval '.[] | select(.kind == "ConfigMap") | .data."target-domain" = "yourdomain.com"'
Or directly edit: k8s/base/configmap.yaml
# Create namespace
kubectl create namespace esddns
# Create secret with your Gandi API key
kubectl create secret generic esddns-gandi-credentials \
--from-literal=api-key=$GANDI_API_KEY \
-n esddns
For sealed-secrets (production recommended):
# Install sealed-secrets controller first
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml
# Create and seal secret
kubectl create secret generic esddns-gandi-credentials \
--from-literal=api-key=$GANDI_API_KEY \
-n esddns \
--dry-run=client \
-o yaml | kubeseal -o yaml > sealed-secret.yaml
kubectl apply -f sealed-secret.yaml
# Build Docker image
docker build -f k8s/Dockerfile -t youregistry.azurecr.io/esddns:latest .
# Push to registry
docker push youregistry.azurecr.io/esddns:latest
# Update image references in kustomization
sed -i 's|esddns:latest|youregistry.azurecr.io/esddns:latest|g' \
k8s/base/daemon-deployment.yaml \
k8s/base/service-deployment.yaml
# Generate manifests
kubectl kustomize k8s/overlays/development > esddns-dev.yaml
# Review generated manifests
cat esddns-dev.yaml | less
# Deploy
kubectl apply -f esddns-dev.yaml
# Monitor deployment
kubectl get pods -n esddns-dev -w
kubectl logs -n esddns-dev -l app=esddns-operator -f
# Generate manifests
kubectl kustomize k8s/overlays/production > esddns-prod.yaml
# Review manifests
cat esddns-prod.yaml | less
# Deploy
kubectl apply -f esddns-prod.yaml
# Verify all components are running
kubectl get all -n esddns
# Get DaemonSet status
kubectl get daemonset -n esddns-system esddns-operator-daemon
kubectl describe daemonset -n esddns-system esddns-operator-daemon
# Check operator logs
kubectl logs -n esddns-system -l app=esddns-operator -f --all-containers=true
# Get Deployment status
kubectl get deployment -n esddns-system esddns-service
kubectl describe deployment -n esddns-system esddns-service
# Check service logs
kubectl logs -n esddns-system -l app=esddns-service -f
# Get LoadBalancer external IP
kubectl get svc -n esddns-system esddns-service
kubectl describe svc -n esddns-system esddns-service
# Test service endpoint
EXTERNAL_IP=$(kubectl get svc -n esddns-system esddns-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://$EXTERNAL_IP/
curl http://$EXTERNAL_IP/raw
# Port-forward to Prometheus metrics
kubectl port-forward -n esddns-system daemonset/esddns-operator-daemon 8080:8080
# In another terminal
curl http://localhost:8080/metrics
Key environment variables from ConfigMap and Secrets:
API_KEY # Gandi.net API key (from secret)
TARGET_DOMAIN_FQDN # Domain to manage (from configmap)
RECORD_NAME_ROOT # Root record name, typically "@" (from configmap)
RECORD_TYPE_A # DNS record type, always "A" (hardcoded)
RECORD_TTL # DNS TTL in seconds (from configmap)
KOPF_LOG_LEVEL # Logging level: DEBUG, INFO, WARNING
# Edit ConfigMap
kubectl edit configmap esddns-config -n esddns
# Or patch it
kubectl patch configmap esddns-config -n esddns \
--patch='{"data":{"target-domain":"newdomain.com"}}'
# Rolling restart pods to pick up changes
kubectl rollout restart daemonset/esddns-operator-daemon -n esddns
kubectl rollout restart deployment/esddns-service -n esddns
# Delete old secret
kubectl delete secret esddns-gandi-credentials -n esddns
# Create new secret
kubectl create secret generic esddns-gandi-credentials \
--from-literal=api-key=$NEW_API_KEY \
-n esddns
# Restart pods
kubectl rollout restart daemonset/esddns-operator-daemon -n esddns
kubectl rollout restart deployment/esddns-service -n esddns
ServiceMonitor resources are defined in k8s/monitoring/prometheus-servicemonitor.yaml
Install with:
kubectl apply -f k8s/monitoring/prometheus-servicemonitor.yaml
Alert rules are defined in k8s/monitoring/prometheus-rules.yaml
Available alerts:
ESDDNSDNSUpdateFailures - DNS updates failingESDDNSNoRecentUpdates - No updates in 20+ minutesESDDNSOperatorDown - Operator not respondingESDDNSServiceDown - Web service not respondingESDDNSLoadBalancerPending - LoadBalancer IP not assignedInstall with:
kubectl apply -f k8s/monitoring/prometheus-rules.yaml
esddns_dns_updates_total # Successful DNS updates
esddns_dns_update_failures_total # Failed DNS updates
esddns_dns_update_duration_seconds # DNS update time
esddns_last_dns_update_timestamp # Last update time
esddns_current_wan_ip_info # Current WAN IP
esddns_wan_ip_changes_total # IP change events
esddns_state_in_sync # Sync status (1=yes, 0=no)
esddns_service_health # Service health (1=up, 0=down)
# Check pod events
kubectl describe pod -n esddns-system -l app=esddns-operator
# Check logs
kubectl logs -n esddns-system -l app=esddns-operator --previous
# Check resource availability
kubectl describe nodes
kubectl top nodes
# Check operator logs for DNS errors
kubectl logs -n esddns-system -l app=esddns-operator | grep -i dns
# Verify API key is set
kubectl get secret esddns-gandi-credentials -n esddns -o jsonpath='{.data.api-key}' | base64 -d
# Verify domain configuration
kubectl get configmap esddns-config -n esddns -o jsonpath='{.data.target-domain}'
# Test Gandi API manually
POD=$(kubectl get pod -n esddns-system -l app=esddns-operator -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it $POD -n esddns-system -- python -c "
from api.dns_manager import DomainManagement
dm = DomainManagement()
print(dm.get_all_domains())
"
Check cloud provider status:
# AWS
kubectl get svc -n esddns-system esddns-service -o jsonpath='{.status.loadBalancer}'
# GCP
gcloud compute forwarding-rules list --filter="name:esddns*"
# Azure
az network lb list --resource-group YOUR_RG
Review and adjust resource limits:
# Edit DaemonSet
kubectl edit daemonset esddns-operator-daemon -n esddns-system
# Edit Deployment
kubectl edit deployment esddns-service -n esddns-system
Typical production values:
# Update image
kubectl set image daemonset/esddns-operator-daemon \
esddns-operator=youregistry.azurecr.io/esddns:1.0.1 \
-n esddns-system
# Monitor rollout
kubectl rollout status daemonset/esddns-operator-daemon -n esddns-system
# For deployments
kubectl set image deployment/esddns-service \
esddns-web=youregistry.azurecr.io/esddns:1.0.1 \
-n esddns-system
kubectl rollout status deployment/esddns-service -n esddns-system
# Rollback daemon
kubectl rollout undo daemonset/esddns-operator-daemon -n esddns-system
# Rollback service
kubectl rollout undo deployment/esddns-service -n esddns-system
# Remove from production
kubectl delete -f esddns-prod.yaml
# Remove namespace and all resources
kubectl delete namespace esddns-system
# Remove monitoring components
kubectl delete -f k8s/monitoring/
For issues or questions:
kubectl logs -n esddns-system -l app=esddns-operator -f