Initial bind-operator: 9 CRDs + controllers #1

Merged
benvin merged 3 commits from benvin/initial-operator into main 2026-07-03 18:42:27 +10:00
Owner

Bootstraps the bind-operator: a Kubernetes operator that manages fleets of BIND9 servers declaratively (controller-runtime, matching forgebot conventions).

Why

Migrates the Puppet-managed BIND estate (3x authoritative masters, 3x only-resolvers, 3x external-dns) to three operator-managed BindClusters in Kubernetes.

Design

  • Fully dynamic delivery: zones/records are applied at runtime via rndc addzone + TSIG nsupdate against the primary pod (ordinal-0). Secondaries replicate via AXFR/IXFR and a catalog zone (no per-zone reconfig).
  • Cluster-wide config (options, controls, ACLs, views, dnssec-policy, response-policy) is rendered into a ConfigMap named.conf (primary/secondary variants selected by pod ordinal) and reloaded with rndc reconfig. The BindCluster controller watches dependent CRs to re-render.
  • TSIG key material is generated by the operator into Secrets; never stored in a CR.

CRDs

  • BindCluster (modes: authoritative / resolver / dynamic), BindZone, BindView, BindTSIGKey
  • BindACL, BindCatalogZone, BindPolicy (RPZ), BindDNSSECPolicy, DNSRecord (external-dns-style)

Changes

  • api/v1alpha1: 9 CRD types + generated deepcopy/CRDs/RBAC
  • internal/controller: 9 reconcilers + shared helpers
  • internal/bind: pod exec, rndc, nsupdate, named.conf rendering, catalog hashing (unit-tested)
  • config/samples: worked examples mapping the three Puppet roles
  • Makefile, Dockerfile.operator, Woodpecker CI (pre-commit/test/build + tag docker push), kind manifests

Test

make test (go test -race), go vet, gofmt -l all clean locally.

Bootstraps the bind-operator: a Kubernetes operator that manages fleets of BIND9 servers declaratively (controller-runtime, matching forgebot conventions). ## Why Migrates the Puppet-managed BIND estate (3x authoritative masters, 3x only-resolvers, 3x external-dns) to three operator-managed `BindCluster`s in Kubernetes. ## Design - **Fully dynamic delivery**: zones/records are applied at runtime via `rndc addzone` + TSIG `nsupdate` against the primary pod (ordinal-0). Secondaries replicate via AXFR/IXFR and a **catalog zone** (no per-zone reconfig). - Cluster-wide config (options, controls, ACLs, views, dnssec-policy, response-policy) is rendered into a ConfigMap `named.conf` (primary/secondary variants selected by pod ordinal) and reloaded with `rndc reconfig`. The BindCluster controller watches dependent CRs to re-render. - TSIG key material is generated by the operator into Secrets; never stored in a CR. ## CRDs - `BindCluster` (modes: authoritative / resolver / dynamic), `BindZone`, `BindView`, `BindTSIGKey` - `BindACL`, `BindCatalogZone`, `BindPolicy` (RPZ), `BindDNSSECPolicy`, `DNSRecord` (external-dns-style) ## Changes - `api/v1alpha1`: 9 CRD types + generated deepcopy/CRDs/RBAC - `internal/controller`: 9 reconcilers + shared helpers - `internal/bind`: pod exec, rndc, nsupdate, named.conf rendering, catalog hashing (unit-tested) - `config/samples`: worked examples mapping the three Puppet roles - Makefile, Dockerfile.operator, Woodpecker CI (pre-commit/test/build + tag docker push), kind manifests ## Test `make test` (go test -race), `go vet`, `gofmt -l` all clean locally.
unkinben added 1 commit 2026-07-03 15:48:29 +10:00
Implements a Kubernetes operator that manages fleets of BIND9 servers
declaratively, using controller-runtime (matching forgebot conventions).

- add BindCluster reconciler: StatefulSet (pod-0 primary, secondaries),
  headless + client Services, rendered named.conf ConfigMap, TSIG keys
  Secret and rndc control Secret; watches dependent CRs to re-render
- add BindTSIGKey reconciler that generates key material into a Secret
- add BindZone/DNSRecord reconcilers using fully-dynamic delivery
  (rndc addzone + TSIG nsupdate against the primary pod)
- add BindCatalogZone reconciler so secondaries auto-provision zones
- add BindPolicy (RPZ), BindDNSSECPolicy, BindView, BindACL reconcilers
- render primary/secondary named.conf variants selected by pod ordinal
- generate CRDs, deepcopy and RBAC; add samples mapping the three Puppet
  roles (authoritative/resolver/external-dns) to three BindClusters
- add Makefile, Dockerfile.operator, Woodpecker CI and kind manifests
unkinben added 1 commit 2026-07-03 17:41:14 +10:00
Uses internetsystemsconsortium/bind9 as the default base image instead of
a self-hosted one, verified against internetsystemsconsortium/bind9:9.20
(runs as root; named/rndc/nsupdate at /usr/sbin,/usr/sbin,/usr/bin).

- project operator config at /etc/bind-operator instead of overmounting
  the image's /etc/bind (keeps bind.keys / base config intact)
- reference named/rndc/nsupdate by absolute path (exec PATH may exclude
  /usr/sbin)
- centralise filesystem + binary paths in internal/bind/consts.go
- default spec.image to internetsystemsconsortium/bind9:9.20
unkinben added 1 commit 2026-07-03 18:32:13 +10:00
Drop redundant dynamic cluster mode
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
e60080efa2
The dynamic mode produced a byte-identical cluster to authoritative:
RFC2136 update capability is a per-zone property (BindZone.dynamicUpdate
+ updateKeyRef render allow-update { key ... }), not a cluster role. The
external-dns tier is just an authoritative cluster whose zones allow
dynamic updates.

- remove BindMode dynamic from the enum and constants
- switch the external-dns sample to mode authoritative
- update README migration mapping
benvin merged commit 53bf0859e6 into main 2026-07-03 18:42:27 +10:00
Sign in to join this conversation.