diff --git a/docs/snippets/tutorials/rfc2136/bind9.yaml b/docs/snippets/tutorials/rfc2136/bind9.yaml new file mode 100644 index 0000000000..af01c289d2 --- /dev/null +++ b/docs/snippets/tutorials/rfc2136/bind9.yaml @@ -0,0 +1,158 @@ +# kubectl apply -f docs/snippets/tutorials/rfc2136/bind9.yaml +# kubectl delete -f docs/snippets/tutorials/rfc2136/bind9.yaml +--- +apiVersion: v1 +kind: Namespace +metadata: + name: bind9 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: bind-config + namespace: bind9 +data: + named.conf: | + options { + directory "/data"; + listen-on { any; }; + listen-on-v6 { any; }; + allow-query { any; }; + allow-transfer { any; }; + recursion no; + dnssec-validation no; + }; + + key "externaldns-key" { + algorithm hmac-sha256; + secret "96Ah/a2g0/nLeFGK+d/0tzQcccf9hCEIy34PoXX2Qg8="; + }; + + zone "example.local" { + type primary; + file "/data/db.example.local"; + allow-update { key externaldns-key; }; + allow-transfer { key externaldns-key; }; + }; + + zone "49.168.192.in-addr.arpa" { + type primary; + file "/data/db.reverse"; + allow-update { key externaldns-key; }; + allow-transfer { key externaldns-key; }; + }; + db.forward: | + $TTL 86400 + @ IN SOA ns1.example.local. admin.example.local. ( + 2024010101 ; serial + 3600 ; refresh + 1800 ; retry + 604800 ; expire + 86400 ) ; minimum + IN NS ns1.example.local. + ns1 IN A 10.0.0.1 + db.reverse: | + $TTL 86400 + @ IN SOA ns1.example.local. admin.example.local. ( + 2024010101 ; serial + 3600 ; refresh + 1800 ; retry + 604800 ; expire + 86400 ) ; minimum + IN NS ns1.example.local. +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: bind9 + namespace: bind9 +spec: + replicas: 1 + selector: + matchLabels: + app: bind9 + template: + metadata: + labels: + app: bind9 + spec: + securityContext: + runAsUser: 0 + fsGroup: 0 + initContainers: + - name: init-zones + image: busybox:1.36 + command: ["sh", "-c"] + args: + - | + cp /config/named.conf /data/named.conf + cp /config/db.forward /data/db.example.local + cp /config/db.reverse /data/db.reverse + chmod -R 777 /data + volumeMounts: + - name: config + mountPath: /config + - name: bind-data + mountPath: /data + containers: + - name: bind9 + image: internetsystemsconsortium/bind9:9.21 + command: ["named", "-g", "-u", "root", "-c", "/data/named.conf"] + ports: + - containerPort: 53 + protocol: TCP + - containerPort: 53 + protocol: UDP + volumeMounts: + - name: bind-data + mountPath: /data + readinessProbe: + tcpSocket: + port: 53 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: config + configMap: + name: bind-config + - name: bind-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: bind9 + namespace: bind9 +spec: + selector: + app: bind9 + ports: + - name: dns-tcp + port: 53 + targetPort: 53 + protocol: TCP + - name: dns-udp + port: 53 + targetPort: 53 + protocol: UDP +--- +apiVersion: v1 +kind: Service +metadata: + name: bind9-nodeport + namespace: bind9 +spec: + type: NodePort + selector: + app: bind9 + ports: + - name: dns-tcp + port: 53 + targetPort: 53 + nodePort: 30053 + protocol: TCP + - name: dns-udp + port: 53 + targetPort: 53 + nodePort: 30053 + protocol: UDP diff --git a/docs/snippets/tutorials/rfc2136/fixtures.yaml b/docs/snippets/tutorials/rfc2136/fixtures.yaml new file mode 100644 index 0000000000..eb8e30c1ae --- /dev/null +++ b/docs/snippets/tutorials/rfc2136/fixtures.yaml @@ -0,0 +1,38 @@ +# kubectl apply -f docs/snippets/tutorials/rfc2136/fixtures.yaml +# kubectl delete -f docs/snippets/tutorials/rfc2136/fixtures.yaml +--- +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: test-a-records + namespace: default +spec: + endpoints: + - dnsName: app.example.local + recordTTL: 300 + recordType: A + targets: + - 192.168.49.10 + - dnsName: api.example.local + recordTTL: 300 + recordType: A + targets: + - 192.168.49.20 +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-rfc2136 + namespace: default + labels: + svc: test-svc + annotations: + external-dns.alpha.kubernetes.io/hostname: svc.example.local +spec: + type: LoadBalancer + ports: + - port: 80 + name: http + targetPort: 80 + selector: + app: nginx diff --git a/docs/snippets/tutorials/rfc2136/kind.yaml b/docs/snippets/tutorials/rfc2136/kind.yaml new file mode 100644 index 0000000000..3a4ce79b2d --- /dev/null +++ b/docs/snippets/tutorials/rfc2136/kind.yaml @@ -0,0 +1,34 @@ +# ref: https://kind.sigs.k8s.io/docs/user/quick-start/ +# https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings + +# kind create cluster --config=docs/snippets/tutorials/rfc2136/kind.yaml +# kind delete cluster --name rfc2136-bind9 +# kubectl cluster-info --context kind-rfc2136-bind9 +# kubectl get nodes -o wide +--- +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: rfc2136-bind9 +networking: + apiServerAddress: 127.0.0.1 + apiServerPort: 6443 +nodes: +- role: control-plane + image: kindest/node:v1.35.1 + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 30053 # BIND9 DNS NodePort + hostPort: 5354 # exposed on host + listenAddress: "0.0.0.0" + protocol: TCP + - containerPort: 30053 + hostPort: 5354 + listenAddress: "0.0.0.0" + protocol: UDP +- role: worker + image: kindest/node:v1.35.1 diff --git a/docs/snippets/tutorials/rfc2136/values-extdns-rfc2136.yaml b/docs/snippets/tutorials/rfc2136/values-extdns-rfc2136.yaml new file mode 100644 index 0000000000..ad88d316bf --- /dev/null +++ b/docs/snippets/tutorials/rfc2136/values-extdns-rfc2136.yaml @@ -0,0 +1,45 @@ +# ref: https://github.com/kubernetes-sigs/external-dns/blob/master/charts/external-dns/values.yaml +provider: + name: rfc2136 + +extraArgs: + - --rfc2136-host=bind9.bind9.svc.cluster.local + - --rfc2136-port=53 + - --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 + - --rfc2136-create-ptr + - --managed-record-types=A + - --managed-record-types=AAAA + - --managed-record-types=CNAME + - --managed-record-types=PTR + +txtOwnerId: external-dns + +domainFilters: + - example.local + - 49.168.192.in-addr.arpa + +sources: + - service + - ingress + - crd + +policy: sync + +logLevel: debug +interval: 10s + +rbac: + create: true + +resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi diff --git a/docs/tutorials/rfc2136.md b/docs/tutorials/rfc2136.md index 8757654c02..f344816e58 100644 --- a/docs/tutorials/rfc2136.md +++ b/docs/tutorials/rfc2136.md @@ -2,6 +2,210 @@ This tutorial describes how to use the RFC2136 with either BIND or Windows DNS. +## Deploying BIND9 on Kubernetes with Kind + +This section walks through deploying BIND9 and ExternalDNS inside a local Kubernetes cluster using [Kind](https://kind.sigs.k8s.io/). It provides a self-contained lab environment for testing the RFC2136 provider, including forward and reverse (PTR) DNS zones with TSIG authentication. + +### TL;DR + +After completing this lab, you will have a local Kubernetes cluster running BIND9 and ExternalDNS with: + +- A forward zone (`example.local`) for A records +- A reverse zone (`49.168.192.in-addr.arpa`) for PTR records +- TSIG-authenticated dynamic DNS updates +- ExternalDNS automatically creating and managing DNS records + +### Notes + +- BIND9 runs inside the cluster for demonstration purposes. +- For production deployments, use an external BIND server with proper security hardening. +- The zones and TSIG key are preconfigured — adapt them to your environment. + +### Prerequisites + +Before you start, ensure you have: + +- [Kind](https://kind.sigs.k8s.io/) installed +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) and [`helm`](https://helm.sh/) +- [`dig`](https://linux.die.net/man/1/dig) for DNS queries (optional, for verification) + +### 1. Create the Kind cluster + +```sh +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. + +### 2. Deploy BIND9 + +The BIND9 manifest deploys a ConfigMap with `named.conf`, forward and reverse zone files, a Deployment, and Services (ClusterIP + NodePort). + +The configuration includes: + +- **Forward zone** (`example.local`): accepts dynamic updates authenticated with the `externaldns-key` TSIG key +- **Reverse zone** (`49.168.192.in-addr.arpa`): accepts dynamic updates and zone transfers with the same TSIG key + +```sh +kubectl apply -f docs/snippets/tutorials/rfc2136/bind9.yaml + +kubectl rollout status deployment/bind9 -n bind9 + +❯❯ deployment "bind9" successfully rolled out +``` + +Verify BIND9 is running: + +```sh +kubectl get pods -n bind9 + +❯❯ NAME READY STATUS RESTARTS AGE +❯❯ bind9-xxxxxxxxx-xxxxx 1/1 Running 0 30s +``` + +Check BIND9 logs for errors: + +```sh +kubectl logs -n bind9 -l app=bind9 --tail=20 +``` + +### 3. Deploy ExternalDNS with Helm + +Add the ExternalDNS Helm repository: + +```sh +helm repo add --force-update external-dns https://kubernetes-sigs.github.io/external-dns/ +``` + +Install ExternalDNS with the RFC2136 provider configuration: + +```sh +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: + +```sh +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): + +```sh +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 \ + --rfc2136-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 +``` + +### 4. Create test DNS records + +Apply the test fixtures which include DNSEndpoint CRDs and a LoadBalancer Service: + +```sh +kubectl apply -f docs/snippets/tutorials/rfc2136/fixtures.yaml +``` + +If using Service sources, patch the LoadBalancer to simulate an external IP assignment: + +```sh +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): + +```sh +kubectl logs deploy/external-dns --tail=30 -f +``` + +### 5. Verify DNS records + +Query the forward zone for A records: + +```sh +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: + +```sh +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): + +```sh +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: + +```sh +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. +``` + +### 6. Cleanup + +```sh +kind delete cluster --name rfc2136-bind9 +``` + ## Using with BIND To use external-dns with BIND: generate/procure a key, configure DNS and add a