Skip to content

Commit 1cd507a

Browse files
committed
chore: init repo
0 parents  commit 1cd507a

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed

README.md

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
# Datum DNS Webhook Provider for ExternalDNS
2+
3+
This is a webhook provider for
4+
[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) that manages DNS
5+
records through Datum Cloud DNS custom resources (`DNSZone` and `DNSRecordSet`).
6+
7+
## Overview
8+
9+
The Datum DNS webhook provider enables ExternalDNS to manage DNS records by
10+
creating and updating Kubernetes custom resources rather than directly
11+
interacting with a DNS provider API. This approach allows for:
12+
13+
- **Declarative DNS management** through Kubernetes CRDs
14+
- **Multi-tenancy** support via namespace isolation
15+
- **Audit trails** through Kubernetes event logs
16+
- **RBAC integration** for fine-grained access control
17+
18+
## Architecture
19+
20+
```
21+
ExternalDNS → Datum DNS Webhook → DNSRecordSet CRs → Datum DNS Operator → DNS Provider
22+
```
23+
24+
The webhook provider:
25+
1. Watches `DNSZone` resources to discover managed domains
26+
2. Converts ExternalDNS endpoints to `DNSRecordSet` custom resources
27+
3. Provides domain filtering based on available zones
28+
4. Tracks ownership through labels to prevent conflicts
29+
30+
## Installation
31+
32+
### Prerequisites
33+
34+
- Kubernetes cluster with ExternalDNS installed
35+
- Datum DNS CRDs installed (`DNSZone` and `DNSRecordSet`)
36+
- Appropriate RBAC permissions (see below)
37+
38+
### Building from Source
39+
40+
```bash
41+
# Build the binary
42+
task build
43+
44+
# Run tests
45+
task test
46+
47+
# Build Docker image
48+
task docker
49+
```
50+
51+
### Running Locally
52+
53+
```bash
54+
go run main.go \
55+
--owner-id=my-external-dns \
56+
--namespace=default \
57+
--kubeconfig=~/.kube/config \
58+
--log-level=debug
59+
```
60+
61+
## Configuration
62+
63+
### Command-Line Flags
64+
65+
| Flag | Description | Default | Required |
66+
|------|-------------|---------|----------|
67+
| `--owner-id` | Unique identifier for this ExternalDNS instance | - | Yes |
68+
| `--namespace` | Specific namespace to watch (empty = all namespaces) | - | No |
69+
| `--namespace-label-selector` | Label selector to filter namespaces | - | No |
70+
| `--kubeconfig` | Path to kubeconfig file (empty = in-cluster config) | - | No |
71+
| `--port` | HTTP server port for webhook API | 8888 | No |
72+
| `--bind-address` | Address to bind the webhook HTTP server | 127.0.0.1 | No |
73+
| `--metrics-port` | Port for Prometheus metrics and health checks | 8080 | No |
74+
| `--log-level` | Log level (debug, info, warn, error) | info | No |
75+
| `--dry-run` | Dry-run mode: do not make actual DNS changes | false | No |
76+
77+
### Namespace Watching Modes
78+
79+
The webhook provider supports three namespace watching modes:
80+
81+
1. **All Namespaces** (default): Watches all namespaces in the cluster
82+
```bash
83+
--owner-id=my-external-dns
84+
```
85+
86+
2. **Specific Namespace**: Watches only a single namespace
87+
```bash
88+
--owner-id=my-external-dns --namespace=production
89+
```
90+
91+
3. **Label Selector**: Watches namespaces matching a label selector
92+
```bash
93+
--owner-id=my-external-dns --namespace-label-selector=dns-managed=true
94+
```
95+
96+
## Usage with ExternalDNS
97+
98+
### ExternalDNS Configuration
99+
100+
Configure ExternalDNS to use the webhook provider:
101+
102+
```yaml
103+
apiVersion: apps/v1
104+
kind: Deployment
105+
metadata:
106+
name: external-dns
107+
spec:
108+
template:
109+
spec:
110+
containers:
111+
- name: external-dns
112+
image: registry.k8s.io/external-dns/external-dns:v0.15.0
113+
args:
114+
- --source=service
115+
- --source=ingress
116+
- --provider=webhook
117+
- --webhook-provider-url=http://datum-dns-webhook:8888
118+
- --txt-owner-id=my-external-dns
119+
```
120+
121+
### Webhook Provider Deployment
122+
123+
```yaml
124+
apiVersion: apps/v1
125+
kind: Deployment
126+
metadata:
127+
name: datum-dns-webhook
128+
namespace: external-dns
129+
spec:
130+
replicas: 1
131+
selector:
132+
matchLabels:
133+
app: datum-dns-webhook
134+
template:
135+
metadata:
136+
labels:
137+
app: datum-dns-webhook
138+
spec:
139+
serviceAccountName: datum-dns-webhook
140+
containers:
141+
- name: webhook
142+
image: datum-dns-webhook:latest
143+
args:
144+
- --owner-id=my-external-dns
145+
- --log-level=info
146+
ports:
147+
- name: webhook
148+
containerPort: 8888
149+
- name: metrics
150+
containerPort: 8080
151+
livenessProbe:
152+
httpGet:
153+
path: /healthz
154+
port: 8080
155+
readinessProbe:
156+
httpGet:
157+
path: /readyz
158+
port: 8080
159+
---
160+
apiVersion: v1
161+
kind: Service
162+
metadata:
163+
name: datum-dns-webhook
164+
namespace: external-dns
165+
spec:
166+
selector:
167+
app: datum-dns-webhook
168+
ports:
169+
- name: webhook
170+
port: 8888
171+
targetPort: 8888
172+
- name: metrics
173+
port: 8080
174+
targetPort: 8080
175+
```
176+
177+
## RBAC Requirements
178+
179+
The webhook provider requires the following permissions:
180+
181+
```yaml
182+
apiVersion: v1
183+
kind: ServiceAccount
184+
metadata:
185+
name: datum-dns-webhook
186+
namespace: external-dns
187+
---
188+
apiVersion: rbac.authorization.k8s.io/v1
189+
kind: ClusterRole
190+
metadata:
191+
name: datum-dns-webhook
192+
rules:
193+
# Watch and list namespaces (for namespace filtering)
194+
- apiGroups: [""]
195+
resources: ["namespaces"]
196+
verbs: ["get", "list", "watch"]
197+
# Manage DNSZone resources
198+
- apiGroups: ["dns.datum.net"]
199+
resources: ["dnszones"]
200+
verbs: ["get", "list", "watch"]
201+
# Manage DNSRecordSet resources
202+
- apiGroups: ["dns.datum.net"]
203+
resources: ["dnsrecordsets"]
204+
verbs: ["get", "list", "watch", "create", "update", "delete"]
205+
---
206+
apiVersion: rbac.authorization.k8s.io/v1
207+
kind: ClusterRoleBinding
208+
metadata:
209+
name: datum-dns-webhook
210+
roleRef:
211+
apiGroup: rbac.authorization.k8s.io
212+
kind: ClusterRole
213+
name: datum-dns-webhook
214+
subjects:
215+
- kind: ServiceAccount
216+
name: datum-dns-webhook
217+
namespace: external-dns
218+
```
219+
220+
## How It Works
221+
222+
### DNSZone Discovery
223+
224+
The webhook watches `DNSZone` resources to determine which domains it should
225+
manage:
226+
227+
```yaml
228+
apiVersion: dns.datum.net/v1alpha1
229+
kind: DNSZone
230+
metadata:
231+
name: example-com
232+
namespace: production
233+
spec:
234+
domainName: example.com
235+
dnsZoneClassName: cloudflare
236+
```
237+
238+
### DNSRecordSet Creation
239+
240+
When ExternalDNS requests a DNS record, the webhook creates a `DNSRecordSet`:
241+
242+
```yaml
243+
apiVersion: dns.datum.net/v1alpha1
244+
kind: DNSRecordSet
245+
metadata:
246+
name: www-example-com-a-abc123
247+
namespace: production
248+
labels:
249+
external-dns.io/owner: my-external-dns
250+
external-dns.io/resource: www.example.com
251+
external-dns.io/record-type: A
252+
external-dns.io/managed-by: datum-cloud-webhook
253+
spec:
254+
dnsZoneRef:
255+
name: example-com
256+
recordType: A
257+
records:
258+
- name: www
259+
ttl: 300
260+
a:
261+
content: 192.0.2.1
262+
```
263+
264+
### Ownership Tracking
265+
266+
Records are labeled with the `owner-id` to prevent conflicts between multiple
267+
ExternalDNS instances. The webhook will only modify records it owns.
268+
269+
## Metrics
270+
271+
Prometheus metrics are exposed on the metrics port (default: 8080) at
272+
`/metrics`:
273+
274+
| Metric | Type | Labels | Description |
275+
|--------|------|--------|-------------|
276+
| `datum_dns_zones_discovered` | Gauge | `namespace` | Number of DNSZone resources discovered |
277+
| `datum_dns_recordsets_managed` | Gauge | `namespace`, `record_type` | Number of DNSRecordSet resources managed |
278+
| `datum_dns_operations_total` | Counter | `operation`, `status` | Total number of DNS operations |
279+
| `datum_dns_translation_errors_total` | Counter | `error_type` | Total number of endpoint translation errors |
280+
| `datum_dns_ownership_conflicts_total` | Counter | - | Total number of ownership conflicts |
281+
| `datum_dns_http_requests_total` | Counter | `method`, `path`, `status` | Total number of HTTP requests |
282+
| `datum_dns_http_request_duration_seconds` | Histogram | `method`, `path` | HTTP request duration |
283+
284+
## Health Checks
285+
286+
- **Liveness**: `GET /healthz` on metrics port (always returns 200 OK)
287+
- **Readiness**: `GET /readyz` on metrics port (returns 200 OK if zones
288+
discovered or within 30s grace period)
289+
290+
## API Endpoints
291+
292+
The webhook implements the ExternalDNS webhook protocol:
293+
294+
| Endpoint | Method | Description |
295+
|----------|--------|-------------|
296+
| `/` | GET | Returns domain filter (content-type negotiation) |
297+
| `/records` | GET | Returns current DNS records |
298+
| `/records` | POST | Applies DNS record changes |
299+
| `/adjustendpoints` | POST | Adjusts endpoints before planning |
300+
301+
## Development
302+
303+
### Running Tests
304+
305+
```bash
306+
# Run unit tests
307+
task test
308+
309+
# Run with coverage
310+
go test -v -race -coverprofile=coverage.out ./...
311+
go tool cover -html=coverage.out
312+
```
313+
314+
### E2E Testing
315+
316+
End-to-end tests validate the complete integration with ExternalDNS and the
317+
Datum DNS Operator in a KIND cluster.
318+
319+
**Prerequisites:**
320+
- Docker
321+
- [Task](https://taskfile.dev)
322+
- [Chainsaw](https://kyverno.github.io/chainsaw/)
323+
324+
**Quick Start:**
325+
326+
```bash
327+
# Setup E2E environment and run tests
328+
task dev:setup
329+
task test:e2e
330+
331+
# Cleanup when done
332+
task e2e:cleanup
333+
```
334+
335+
**Available E2E Tasks:**
336+
337+
- `task dev:setup` - Create complete E2E environment
338+
- `task test:e2e` - Run E2E tests
339+
- `task e2e:logs` - View component logs
340+
- `task e2e:diag` - Collect diagnostics
341+
- `task e2e:cleanup` - Destroy environment
342+
343+
See `e2e/README.md` for detailed E2E testing documentation.
344+
345+
### Linting
346+
347+
```bash
348+
task lint
349+
```
350+
351+
## Troubleshooting
352+
353+
### No Zones Discovered
354+
355+
If the webhook reports no zones discovered:
356+
357+
1. Check that `DNSZone` resources exist in watched namespaces
358+
2. Verify RBAC permissions allow listing zones
359+
3. Check namespace filtering configuration
360+
361+
### Ownership Conflicts
362+
363+
If you see ownership conflict errors:
364+
365+
1. Ensure each ExternalDNS instance has a unique `owner-id`
366+
2. Check for manually created `DNSRecordSet` resources with conflicting labels
367+
3. Verify the correct `owner-id` is set in both ExternalDNS and the webhook
368+
369+
### Records Not Created
370+
371+
If DNS records are not being created:
372+
373+
1. Enable debug logging: `--log-level=debug`
374+
2. Check webhook logs for translation errors
375+
3. Verify the domain matches a `DNSZone`
376+
4. Ensure RBAC permissions allow creating `DNSRecordSet` resources
377+
378+
## License
379+
380+
This project is licensed under the Apache License 2.0 - see the original
381+
ExternalDNS project for details.
382+
383+
## Contributing
384+
385+
Contributions are welcome! Please ensure:
386+
387+
- All tests pass
388+
- Code is formatted with `gofmt`
389+
- Linting passes with `golangci-lint`
390+
- Commits follow conventional commit format

0 commit comments

Comments
 (0)