Skip to content

Commit 97bdfae

Browse files
committed
feat: implement dns controllers
1 parent f8a7d02 commit 97bdfae

113 files changed

Lines changed: 8521 additions & 1894 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Publish Docker Image
2+
3+
on:
4+
# Trigger on push
5+
push:
6+
# Trigger on all pull requests
7+
pull_request:
8+
# Trigger when a release is published
9+
release:
10+
types: ['published']
11+
12+
jobs:
13+
publish-container-image:
14+
permissions:
15+
id-token: write
16+
contents: read
17+
packages: write
18+
attestations: write
19+
uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.5.1
20+
with:
21+
image-name: dns-operator
22+
secrets: inherit
23+
24+
publish-kustomize-bundles:
25+
permissions:
26+
id-token: write
27+
contents: read
28+
packages: write
29+
uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.5.1
30+
with:
31+
bundle-name: ghcr.io/datum-cloud/dns-operator-kustomize
32+
bundle-path: config
33+
secrets: inherit

.github/workflows/test-e2e.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
bin/
1212
vendor/
13+
dist/
14+
dev/
1315
.go-version
16+
config/**/charts
1417

1518
# Test binary, built with `go test -c`
1619
*.test

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ linters:
2626
rules:
2727
- name: comment-spacings
2828
- name: import-shadowing
29+
gocyclo:
30+
min-complexity: 70
2931
exclusions:
3032
generated: lax
3133
rules:

LICENSE

Lines changed: 0 additions & 661 deletions
This file was deleted.

Makefile

Lines changed: 246 additions & 10 deletions
Large diffs are not rendered by default.

PROJECT

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,13 @@ resources:
3434
kind: DNSRecordSet
3535
path: go.miloapis.com/dns-operator/api/v1alpha1
3636
version: v1alpha1
37+
- api:
38+
crdVersion: v1
39+
namespaced: true
40+
controller: true
41+
domain: networking.miloapis.com
42+
group: dns
43+
kind: DNSZoneDiscovery
44+
path: go.miloapis.com/dns-operator/api/v1alpha1
45+
version: v1alpha1
3746
version: "3"

README.md

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,135 @@
1-
# dns-operator
1+
## datum-dns-operator
2+
3+
Kubernetes operator for managing DNS zones and records, with a pluggable backend architecture. This repository provides:
4+
- Custom resources to model zones, recordsets, and zone classes
5+
- Controllers for two runtime roles:
6+
- "downstream" agent that programs a DNS backend (PowerDNS supported)
7+
- "replicator" that mirrors resources from an upstream cluster to a downstream cluster and synthesizes status
8+
- Kustomize overlays to deploy either role
9+
10+
### CRDs
11+
- **`DNSZoneClass`** (cluster-scoped)
12+
- `spec.controllerName`: selects backend controller (e.g., "powerdns")
13+
- `spec.nameServerPolicy`: currently supports `Static` with `servers: []`
14+
- `spec.defaults.defaultTTL`: optional default TTL for zones
15+
16+
- **`DNSZone`** (namespaced)
17+
- `spec.domainName`: required zone FQDN (e.g., `example.com`)
18+
- `spec.dnsZoneClassName`: optional reference to a `DNSZoneClass`
19+
- `status.nameservers`: authoritative nameservers (derived from class policy)
20+
- `status.conditions`: `Accepted`, `Programmed`
21+
22+
- **`DNSRecordSet`** (namespaced)
23+
- `spec.dnsZoneRef`: `LocalObjectReference` to a `DNSZone` in the same namespace
24+
- `spec.recordType`: one of `A, AAAA, CNAME, TXT, MX, SRV, CAA, NS, SOA, PTR, TLSA, HTTPS, SVCB`
25+
- `spec.records[]`: owners with typed fields per record type (or `raw` strings). TTL per-owner optional.
26+
- `status.conditions`: `Accepted`, `Programmed`
27+
28+
### Controllers and Roles
29+
30+
- **Downstream role** (`--role=downstream`)
31+
- `DNSZoneReconciler`: when `DNSZone.spec.dnsZoneClassName` references a class with `controllerName: powerdns`, ensures the zone exists in PowerDNS and honors static nameserver policy.
32+
- `DNSRecordSetReconciler`: for PowerDNS-backed zones, applies recordsets to PDNS using an authoritative mode that REPLACEs desired owners and DELETEs extraneous owners of the same type. Requeues while the zone is not ready.
33+
34+
- **Replicator role** (`--role=replicator`)
35+
- Multicluster manager discovers one or many upstream clusters (single-cluster or Milo discovery) and mirrors `DNSZone`/`DNSRecordSet` into a configured downstream cluster using a mapped-namespace strategy.
36+
- `DNSZoneReplicator`:
37+
- Mirrors upstream `spec` into a downstream shadow object
38+
- Ensures an operator-managed upstream `DNSRecordSet` named `soa` exists (typed SOA targeting `@`) for PowerDNS-backed zones
39+
- Updates upstream `status`: sets `Accepted=True` and currently treats `Programmed=True` optimistically; fills `status.nameservers` from `DNSZoneClass` when `Static` policy is set
40+
- `DNSRecordSetReplicator`:
41+
- Mirrors upstream `spec` into a downstream shadow object
42+
- Updates upstream `status`: `Accepted` reflects `DNSZone` presence; `Programmed=True` once downstream shadow ensured
43+
44+
### Backends
45+
- **PowerDNS (Authoritative)**
46+
- Enabled when `DNSZoneClass.spec.controllerName: powerdns`
47+
- The downstream agent uses environment variables to connect:
48+
- `PDNS_API_URL` (default `http://127.0.0.1:8081`)
49+
- `PDNS_API_KEY` or `PDNS_API_KEY_FILE`
50+
- Recordset translation supports typed fields for all declared RR types and sensible normalization of names and quoting for TXT/targets.
51+
52+
### Deployment Overlays
53+
54+
- `config/agent/`
55+
- Namespace: `dns-agent-system`
56+
- Runs the operator with `--role=downstream`
57+
- Merges a `pdns` sidecar container into the controller Deployment to run PowerDNS alongside the manager
58+
- Mounts a shared `emptyDir` to exchange an auto-generated API key, and sets `PDNS_API_KEY_FILE` in the manager
59+
- Provides a `Service` exposing PDNS ports 53/udp, 53/tcp, and 8081/tcp
60+
- ConfigMap `server-config` wired to `--server-config`
61+
62+
- `config/overlays/replicator/`
63+
- Namespace: `dns-replicator-system`
64+
- Runs the operator with `--role=replicator`
65+
- Requires a Secret `downstream-kubeconfig` containing key `kubeconfig` to target the downstream cluster
66+
- ConfigMap `server-config` sets discovery mode (defaults to `single`) and points `downstreamResourceManagement.kubeconfigPath` to `/downstream/kubeconfig`
67+
68+
### Quickstart: Agent with embedded PowerDNS
69+
1. Install CRDs and default manifests:
70+
- `kubectl apply -k config/agent`
71+
2. Create a `DNSZoneClass` for PowerDNS with static nameservers, for example:
72+
```yaml
73+
apiVersion: dns.networking.miloapis.com/v1alpha1
74+
kind: DNSZoneClass
75+
metadata:
76+
name: powerdns
77+
spec:
78+
controllerName: powerdns
79+
nameServerPolicy:
80+
mode: Static
81+
static:
82+
servers: ["ns1.example.net.", "ns2.example.net."]
83+
```
84+
3. Create a `DNSZone` and a `DNSRecordSet`:
85+
```yaml
86+
apiVersion: dns.networking.miloapis.com/v1alpha1
87+
kind: DNSZone
88+
metadata:
89+
name: example-com
90+
namespace: default
91+
spec:
92+
domainName: example.com
93+
dnsZoneClassName: powerdns
94+
---
95+
apiVersion: dns.networking.miloapis.com/v1alpha1
96+
kind: DNSRecordSet
97+
metadata:
98+
name: www-a
99+
namespace: default
100+
spec:
101+
dnsZoneRef:
102+
name: example-com
103+
recordType: A
104+
records:
105+
- name: www
106+
a:
107+
content: ["192.0.2.10", "192.0.2.11"]
108+
ttl: 300
109+
```
110+
111+
### Quickstart: Replicator (upstream → downstream)
112+
1. Create Secret on the replicator namespace containing the downstream kubeconfig (`data.kubeconfig`):
113+
```bash
114+
kubectl -n dns-replicator-system create secret generic downstream-kubeconfig \
115+
--from-file=kubeconfig=/path/to/downstream/kubeconfig
116+
```
117+
2. Deploy replicator overlay:
118+
```bash
119+
kubectl apply -k config/overlays/replicator
120+
```
121+
3. Create `DNSZoneClass` (cluster-scoped), `DNSZone` and `DNSRecordSet` on the upstream cluster. The replicator will mirror them into the downstream cluster and update upstream `status` conditions.
122+
123+
### Conditions
124+
- `Accepted`: resource is valid and has required dependencies (e.g., `DNSRecordSet` sees its `DNSZone`)
125+
- `Programmed`: desired state is realized (shadow exists downstream; for downstream agent, recordsets applied to backend)
126+
127+
### Configuration CRD (server config)
128+
- `kind: DNSOperator` (internal config consumed by the binary via `--server-config`)
129+
- `discovery.mode`: `single` or `milo`
130+
- `downstreamResourceManagement.kubeconfigPath`: path inside the Pod to the downstream kubeconfig
131+
132+
### Development
133+
- Build: `make docker-build` (see `Makefile`)
134+
- Generate code/manifests: `make generate` and `make manifests`
135+
- Local e2e: see `test/e2e/chainsaw-test.yaml` and sample manifests under `config/samples/`

0 commit comments

Comments
 (0)