docs/tutorials/rfc2136.md
This tutorial describes how to use the RFC2136 with either BIND or Windows DNS.
This section walks through deploying BIND9 and ExternalDNS inside a local Kubernetes cluster using Kind. It provides a self-contained lab environment for testing the RFC2136 provider, including forward and reverse (PTR) DNS zones with TSIG authentication.
After completing this lab, you will have a local Kubernetes cluster running BIND9 and ExternalDNS with:
example.local) for A records49.168.192.in-addr.arpa) for PTR recordsBefore you start, ensure you have:
kind create cluster --config=docs/snippets/tutorials/rfc2136/kind.yaml
Creating cluster "rfc2136-bind9" ...
✓ Ensuring node image (kindest/node:v1.35.1) 🖼
✓ Preparing nodes 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-rfc2136-bind9"
The Kind configuration exposes port 30053 on the host as 5354, allowing DNS queries from your machine against BIND9 running inside the cluster.
The BIND9 manifest deploys a ConfigMap with named.conf, forward and reverse zone files, a Deployment, and Services (ClusterIP + NodePort).
The configuration includes:
example.local): accepts dynamic updates authenticated with the externaldns-key TSIG key49.168.192.in-addr.arpa): accepts dynamic updates and zone transfers with the same TSIG keykubectl apply -f docs/snippets/tutorials/rfc2136/bind9.yaml
kubectl rollout status deployment/bind9 -n bind9
❯❯ deployment "bind9" successfully rolled out
Verify BIND9 is running:
kubectl get pods -n bind9
❯❯ NAME READY STATUS RESTARTS AGE
❯❯ bind9-xxxxxxxxx-xxxxx 1/1 Running 0 30s
Check BIND9 logs for errors:
kubectl logs -n bind9 -l app=bind9 --tail=20
Add the ExternalDNS Helm repository:
helm repo add --force-update external-dns https://kubernetes-sigs.github.io/external-dns/
Install ExternalDNS with the RFC2136 provider configuration:
helm upgrade --install external-dns external-dns/external-dns \
-f docs/snippets/tutorials/rfc2136/values-extdns-rfc2136.yaml \
-n default
❯❯ Release "external-dns" does not exist. Installing it now.
Validate pod status and view logs:
kubectl get pods -l app.kubernetes.io/name=external-dns
kubectl logs deploy/external-dns --tail=50
Or run ExternalDNS from source on the host (requires the NodePort to be accessible):
go run main.go \
--provider=rfc2136 \
--rfc2136-host=127.0.0.1 \
--rfc2136-port=5354 \
--rfc2136-zone=example.local \
--rfc2136-zone=49.168.192.in-addr.arpa \
--rfc2136-tsig-keyname=externaldns-key \
--rfc2136-tsig-secret=96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8= \
--rfc2136-tsig-secret-alg=hmac-sha256 \
--rfc2136-tsig-axfr \
--create-ptr \
--source=crd \
--source=service \
--domain-filter=example.local \
--domain-filter=49.168.192.in-addr.arpa \
--managed-record-types=A \
--managed-record-types=AAAA \
--managed-record-types=CNAME \
--managed-record-types=PTR \
--policy=sync \
--log-level=debug
Apply the test fixtures which include DNSEndpoint CRDs and a LoadBalancer Service:
kubectl apply -f docs/snippets/tutorials/rfc2136/fixtures.yaml
If using Service sources, patch the LoadBalancer to simulate an external IP assignment:
kubectl patch svc nginx-rfc2136 --type=merge \
-p '{"status":{"loadBalancer":{"ingress":[{"ip":"192.168.49.50"}]}}}' \
--subresource=status
❯❯ service/nginx-rfc2136 patched
Wait for ExternalDNS to sync (check the logs for update activity):
kubectl logs deploy/external-dns --tail=30 -f
Query the forward zone for A records:
dig @127.0.0.1 -p 5354 +tcp app.example.local A +short
❯❯ 192.168.49.10
dig @127.0.0.1 -p 5354 +tcp api.example.local A +short
❯❯ 192.168.49.20
dig @127.0.0.1 -p 5354 +tcp svc.example.local A +short
❯❯ 192.168.49.50
Query the reverse zone for PTR records:
dig @127.0.0.1 -p 5354 +tcp -x 192.168.49.10 +short
❯❯ app.example.local.
dig @127.0.0.1 -p 5354 +tcp -x 192.168.49.20 +short
❯❯ api.example.local.
List all records in the reverse zone via AXFR (requires the TSIG key since zone transfers are restricted):
dig @127.0.0.1 -p 5354 +tcp 49.168.192.in-addr.arpa. AXFR \
-y hmac-sha256:externaldns-key:96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8= \
+noall +answer
Alternatively, query from inside the cluster using a debug pod:
kubectl run --rm -it dnsutils --image=infoblox/dnstools --restart=Never
dig +short @bind9.bind9.svc.cluster.local app.example.local
❯❯ 192.168.49.10
dig +short @bind9.bind9.svc.cluster.local -x 192.168.49.10
❯❯ app.example.local.
kind delete cluster --name rfc2136-bind9
To use external-dns with BIND: generate/procure a key, configure DNS and add a deployment of external-dns.
key "externaldns-key" {
algorithm hmac-sha256;
secret "96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8=";
};
tsig-keygen -a hmac-sha256 externaldns or on older distributions
dnssec-keygen -a HMAC-SHA256 -b 256 -n HOST externaldns. You will end up with
a key printed to standard out like above (or in the case of dnssec-keygen in a
file called Kexternaldns......key).If you do not administer your own DNS, skip to RFC provider configuration
Edit your named.conf file (or appropriate included file) and add/change the following.
zone "k8s.example.org" {
type master;
file "/etc/bind/pri/k8s/k8s.zone";
};
zone "k8s.example.org" {
type master;
file "/etc/bind/pri/k8s/k8s.zone";
allow-transfer {
key "externaldns-key";
};
update-policy {
grant externaldns-key zonesub ANY;
};
};
$TTL 60 ; 1 minute
k8s.example.org IN SOA k8s.example.org. root.k8s.example.org. (
16 ; serial
60 ; refresh (1 minute)
60 ; retry (1 minute)
60 ; expire (1 minute)
60 ; minimum (1 minute)
)
NS ns.k8s.example.org.
ns A 123.456.789.012
When using the sync policy, ExternalDNS requires AXFR (zone transfer) to be
explicitly enabled via the --rfc2136-tsig-axfr flag. This is necessary for
ExternalDNS to list all existing DNS records and determine which ones should be
lifecycled.
Without --rfc2136-tsig-axfr, ExternalDNS cannot list records and will act as
if the policy was set to upsert-only. No warning will be provided.
To use external-dns add an ingress or a LoadBalancer service with a host that is part of the domain-filter. For example both of the following would produce A records.
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: svc.example.org
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: ingress.example.org
http:
paths:
- path: /
backend:
serviceName: my-service
servicePort: 8000
The default DNS record TTL (Time-To-Live) is 0 seconds. You can customize this value by setting the annotation external-dns.alpha.kubernetes.io/ttl. e.g., modify the service manifest YAML file above:
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
external-dns.alpha.kubernetes.io/ttl: 60
spec:
...
This will set the DNS record's TTL to 60 seconds.
A default TTL for all records can be set using the the flag with a time in seconds, minutes or hours, such as --rfc2136-min-ttl=60s
There are other annotation that can affect the generation of DNS records, but these are beyond the scope of this tutorial and are covered in the main documentation.
Note: The
--rfc2136-create-ptrflag is removed. Use the generic--create-ptrflag instead.
ExternalDNS can automatically create PTR records for your A/AAAA records. This is a generic feature
that works with any provider, not just RFC2136. For RFC2136 you also need to add the reverse zone to
--rfc2136-zone:
--create-ptr --domain-filter=157.168.192.in-addr.arpa --rfc2136-zone=157.168.192.in-addr.arpa
See Automatic PTR (Reverse DNS) Records for full documentation including annotation overrides and behaviour details.
You may install external-dns and test on a local machine by running:
external-dns --txt-owner-id k8s --provider rfc2136 \
--rfc2136-host=192.168.0.1 --rfc2136-port=53 \
--rfc2136-zone=k8s.example.org \
--rfc2136-tsig-secret=96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8= \
--rfc2136-tsig-secret-alg=hmac-sha256 \
--rfc2136-tsig-keyname=externaldns-key \
--rfc2136-tsig-axfr \
--source ingress --once \
--domain-filter=k8s.example.org --dry-run
In order to use external-dns with your cluster you need to add a deployment with access to your ingress and service resources. The following are two example manifests with and without RBAC respectively.
apiVersion: v1
kind: Namespace
metadata:
name: external-dns
labels:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
namespace: external-dns
rules:
- apiGroups:
- ""
resources:
- services
- pods
- nodes
verbs:
- get
- watch
- list
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- watch
- list
- apiGroups:
- extensions
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
namespace: external-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: external-dns
---
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:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.20.0
args:
- --registry=txt
- --txt-prefix=external-dns-
- --txt-owner-id=k8s
- --provider=rfc2136
- --rfc2136-host=192.168.0.1
- --rfc2136-port=53
- --rfc2136-zone=k8s.example.org
- --rfc2136-zone=k8s.your-zone.org
- --rfc2136-tsig-secret=96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8=
- --rfc2136-tsig-secret-alg=hmac-sha256
- --rfc2136-tsig-keyname=externaldns-key
- --rfc2136-tsig-axfr
- --source=ingress
- --domain-filter=k8s.example.org
apiVersion: v1
kind: Namespace
metadata:
name: external-dns
labels:
name: external-dns
---
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.20.0
args:
- --registry=txt
- --txt-prefix=external-dns-
- --txt-owner-id=k8s
- --provider=rfc2136
- --rfc2136-host=192.168.0.1
- --rfc2136-port=53
- --rfc2136-zone=k8s.example.org
- --rfc2136-zone=k8s.your-zone.org
- --rfc2136-tsig-secret=96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8=
- --rfc2136-tsig-secret-alg=hmac-sha256
- --rfc2136-tsig-keyname=externaldns-key
- --rfc2136-tsig-axfr
- --source=ingress
- --domain-filter=k8s.example.org
While external-dns was not developed or tested against Microsoft DNS, it can be configured to work against it. YMMV.
If you see any error messages which indicate that external-dns was somehow not able to fetch
existing DNS records from your DNS server, this could mean that you forgot about step 3.
DNS with secure updates relies upon a valid Kerberos configuration running within the external-dns container.
At this time, you will need to create a ConfigMap for the external-dns container to use and mount it in your deployment.
Below is an example of a working Kerberos configuration inside a ConfigMap definition. This may be different depending on many factors in your environment:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: null
name: krb5.conf
data:
krb5.conf: |
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
dns_lookup_realm = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
rdns = false
pkinit_anchors = /etc/pki/tls/certs/ca-bundle.crt
default_ccache_name = KEYRING:persistent:%{uid}
default_realm = YOUR-REALM.COM
[realms]
YOUR-REALM.COM = {
kdc = dc1.yourdomain.com
admin_server = dc1.yourdomain.com
}
[domain_realm]
yourdomain.com = YOUR-REALM.COM
.yourdomain.com = YOUR-REALM.COM
In most cases, the realm name will probably be the same as the domain name, so you can simply replace YOUR-REALM.COM with something like YOURDOMAIN.COM.
Once the ConfigMap is created, the container external-dns container needs to be told to mount that ConfigMap as a volume at the default Kerberos configuration location. The pod spec should include a similar configuration to the following:
...
volumeMounts:
- mountPath: /etc/krb5.conf
name: kerberos-config-volume
subPath: krb5.conf
...
volumes:
- configMap:
defaultMode: 420
name: krb5.conf
name: kerberos-config-volume
...
external-dns configurationYou'll want to configure external-dns similarly to the following:
...
- --provider=rfc2136
- --rfc2136-gss-tsig
- --rfc2136-host=dns-host.yourdomain.com
- --rfc2136-port=53
- --rfc2136-zone=your-zone.com
- --rfc2136-zone=your-secondary-zone.com
- --rfc2136-kerberos-username=your-domain-account
- --rfc2136-kerberos-password=your-domain-password
- --rfc2136-kerberos-realm=your-domain.com
- --rfc2136-tsig-axfr # needed to enable zone transfers, which is required for deletion of records.
...
As noted above, the --rfc2136-kerberos-realm flag is completely optional and won't be necessary in many cases.
Most likely, you will only need it if you see errors similar to this: KRB Error: (68) KDC_ERR_WRONG_REALM Reserved for future use.
The flag --rfc2136-host can be set to the host's domain name or IP address.
However, it also determines the name of the Kerberos principal which is used during authentication.
This means that Active Directory might only work if this is set to a specific domain name, possibly leading to errors like this:
KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database.
To fix this, try setting --rfc2136-host to the "actual" hostname of your DNS server.
external-dns configurationYou'll want to configure external-dns similarly to the following:
...
- --provider=rfc2136
- --rfc2136-host=192.168.0.1
- --rfc2136-port=53
- --rfc2136-zone=k8s.example.org
- --rfc2136-zone=k8s.your-zone.org
- --rfc2136-insecure
- --rfc2136-tsig-axfr # needed to enable zone transfers, which is required for deletion of records.
...
If your DNS server does zone transfers over TLS, you can instruct external-dns to connect over TLS with the following flags:
--rfc2136-use-tls Will enable TLS for both zone transfers and for updates.--tls-ca=<cert-file> Is the path to a file containing certificate(s) that can be used to verify the DNS server--tls-client-cert=<client-cert-file> and--tls-client-cert-key=<client-key-file> Set the client certificate and key for mutual verification--rfc2136-skip-tls-verify Disables verification of the certificate supplied by the DNS server.It is currently not supported to do only zone transfers over TLS, but not the updates. They are enabled and disabled together.
This section describes how to configure the RFC2136 provider in ExternalDNS to support multiple DNS servers and load balancing options.
The RFC2136 provider now supports multiple DNS hosts and introduces load balancing options to distribute DNS update requests evenly across available DNS servers. This helps prevent a single server from becoming a bottleneck in environments with multiple DNS servers.
Allow Multiple Hosts for --rfc2136-host
--rfc2136-host command-line option to accept multiple hosts.--rfc2136-host="dns-host-1.yourdomain.com" --rfc2136-host="dns-host-2.yourdomain.com"Introduce Load Balancing Options
--rfc2136-load-balancing-strategy to specify the load balancing strategy.round-robin: Distributes DNS updates evenly across all specified hosts in a round-robin manner.random: Randomly selects a host for each DNS update.disabled (default): Uses the first host in the list as the primary, only moving to the next host if a failure occurs.external-dns \
--provider=rfc2136 \
--rfc2136-host="dns-host-1.yourdomain.com" \
--rfc2136-host="dns-host-2.yourdomain.com" \
--rfc2136-host="dns-host-3.yourdomain.com" \
--rfc2136-load-balancing-strategy="round-robin" \
--rfc2136-port=53 \
--rfc2136-zone=example.com \
--rfc2136-tsig-secret-alg=hmac-sha256 \
--rfc2136-tsig-keyname=example-key \
--rfc2136-tsig-secret=example-secret \
--rfc2136-insecure
extraArgs:
- --rfc2136-host="dns-host-1.yourdomain.com"
- --rfc2136-port=53
- --rfc2136-zone=example.com
- --rfc2136-tsig-secret-alg=hmac-sha256
- --rfc2136-tsig-axfr
env:
- name: "EXTERNAL_DNS_RFC2136_TSIG_SECRET"
valueFrom:
secretKeyRef:
name: rfc2136-keys
key: rfc2136-tsig-secret
- name: "EXTERNAL_DNS_RFC2136_TSIG_KEYNAME"
valueFrom:
secretKeyRef:
name: rfc2136-keys
key: rfc2136-tsig-keyname
kubectl create secret generic rfc2136-keys --from-literal=rfc2136-tsig-secret='xxx' --from-literal=rfc2136-tsig-keyname='k8s-external-dns-key' -n external-dns