External-DNS Integration¶
This guide covers integrating the Cloudflare Tunnel Gateway Controller with external-dns for automatic DNS record management.
Overview¶
The controller sets status.addresses on the Gateway with the tunnel CNAME (TUNNEL_ID.cfargotunnel.com). When external-dns is configured with Gateway API source, it automatically creates DNS records for your HTTPRoute hostnames.
Prerequisites¶
- Cloudflare Tunnel Gateway Controller installed
- external-dns installed and configured for Cloudflare
- Cloudflare API token with DNS edit permissions
external-dns Configuration¶
Helm Values¶
# external-dns values.yaml
provider:
name: cloudflare
env:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-dns-token
key: api-token
sources:
- gateway-httproute
- gateway-grpcroute
extraArgs:
- --gateway-namespace=cloudflare-tunnel-system
- --gateway-label-filter=app.kubernetes.io/name=cloudflare-tunnel-gateway-controller
API Token Permissions¶
The external-dns API token needs:
| Scope | Permission | Access |
|---|---|---|
| Zone | DNS | Edit |
| Zone | Zone | Read |
Separate Tokens
Use separate API tokens for the tunnel controller (Tunnel Edit) and external-dns (DNS Edit) following the principle of least privilege.
HTTPRoute Annotations¶
Add annotations to HTTPRoute for DNS configuration:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
annotations:
# Enable Cloudflare proxy (orange cloud)
external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
# Custom TTL (seconds)
external-dns.alpha.kubernetes.io/ttl: "300"
spec:
parentRefs:
- name: cloudflare-tunnel
namespace: cloudflare-tunnel-system
hostnames:
- app.example.com
rules:
- backendRefs:
- name: my-service
port: 80
Common Annotations¶
| Annotation | Description | Example |
|---|---|---|
external-dns.alpha.kubernetes.io/cloudflare-proxied | Enable Cloudflare proxy | "true" |
external-dns.alpha.kubernetes.io/ttl | DNS record TTL | "300" |
external-dns.alpha.kubernetes.io/target | Override CNAME target | custom.example.com |
external-dns.alpha.kubernetes.io/hostname | Override hostname | override.example.com |
How It Works¶
sequenceDiagram
participant User
participant K8s as Kubernetes
participant Ctrl as Controller
participant ExtDNS as external-dns
participant CF as Cloudflare
User->>K8s: Create HTTPRoute
K8s->>Ctrl: Watch event
Ctrl->>CF: Update tunnel config
Ctrl->>K8s: Update Gateway status.addresses
K8s->>ExtDNS: Watch Gateway/HTTPRoute
ExtDNS->>CF: Create DNS CNAME record
Note over CF: app.example.com → TUNNEL_ID.cfargotunnel.com - User creates HTTPRoute with hostnames
- Controller updates tunnel configuration in Cloudflare
- Controller sets Gateway
status.addressesto tunnel CNAME - external-dns watches Gateway and HTTPRoute
- external-dns creates CNAME record pointing to tunnel
Verifying DNS Records¶
Check that external-dns created the record:
# Check external-dns logs
kubectl logs --selector app.kubernetes.io/name=external-dns
# Query DNS
dig app.example.com CNAME
# Expected output
# app.example.com. 300 IN CNAME abc123.cfargotunnel.com.
Troubleshooting¶
DNS Record Not Created¶
- Check external-dns logs for errors:
- Verify Gateway has address set:
kubectl get gateway cloudflare-tunnel --namespace cloudflare-tunnel-system \
--output jsonpath='{.status.addresses[*].value}'
- Verify HTTPRoute is accepted:
Proxy Status Not Applied¶
Ensure the annotation is on the HTTPRoute, not the Gateway:
# Correct - annotation on HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
annotations:
external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
Multiple Gateways¶
If you have multiple Gateways, use label filters:
Production Recommendations¶
-
Use Cloudflare Proxy - Always set
cloudflare-proxied: "true"for DDoS protection and caching -
TTL Settings - When using Cloudflare proxy, TTL is managed by Cloudflare (set to "Auto")
-
Separate Zones - Consider separate DNS zones for different environments (prod, staging)
-
Policy Mode - Use
--policy=syncin production for external-dns to clean up stale records
Example: Complete Setup¶
---
# external-dns deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: external-dns
spec:
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.14.0
args:
- --source=gateway-httproute
- --source=gateway-grpcroute
- --provider=cloudflare
- --cloudflare-proxied
- --policy=sync
- --gateway-namespace=cloudflare-tunnel-system
env:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-dns-token
key: api-token
---
# HTTPRoute with DNS annotations
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
annotations:
external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
spec:
parentRefs:
- name: cloudflare-tunnel
namespace: cloudflare-tunnel-system
hostnames:
- app.example.com
- www.example.com
rules:
- backendRefs:
- name: my-service
port: 80