## Why
After v0.1.4 pointed HA peer URLs at per-pod ClusterIP Services, kea-dhcp4 2.6
starts the HA service but then crashes at hook load:
DHCP4_CONFIG_LOAD_FAIL ... Error initializing hooks: CmdHttpListener::run
failed: unable to setup TCP acceptor for listening to the incoming HTTP
requests: bind: Cannot assign requested address
With core multi-threading enabled (Kea 2.6 default), the HA hook opens a
dedicated HTTP listener bound to *this* server's peer url address. That address
is now a per-pod ClusterIP — virtual (kube-proxy DNAT), not assignable on the
pod — so the bind fails. Peers must be reached via ClusterIP, but the local
listener must bind a pod-local address.
## How
- Set the HA relationship's multi-threading block http-dedicated-listener:false
(enable-multi-threading:true). Per Kea 2.6 docs this makes inbound HA traffic
flow through kea-ctrl-agent instead of a hook-owned listener. The ctrl-agent
sidecar already binds 0.0.0.0:8000, and the per-pod Service targetPort 8000
routes ClusterIP:8000 to that container, so remote peers keep reaching this
server at its ClusterIP while nothing binds the virtual address locally.
- Regression tests: rendered config disables the dedicated listener (string +
parsed high-availability.multi-threading assertion); ctrl-agent binds 0.0.0.0
(the pod-local address the CA-mediated route depends on).
Replace the ISC dhcpd PXE-boot VM with a Kea DHCP Kubernetes operator, modelled
on bind-operator. The operator renders kea-dhcp4 config from CRs and runs an HA
pair of kea-dhcp4 + kea-ctrl-agent servers behind an anycast Service.
- add KeaCluster/KeaSubnet/KeaClientClass/KeaAPI CRDs (group kea.unkin.net)
- render deterministic kea-dhcp4.conf + kea-ctrl-agent.conf into a ConfigMap and
roll the StatefulSet via a config-hash annotation; best-effort hot-reload via
the kea-ctrl-agent REST channel
- run HA hot-standby (memfile leases) with stable per-peer DNS identity from a
StatefulSet; expose an anycast LoadBalancer Service for PureLB
- represent the full legacy dhcpd config: 198.18.13-17.0/24 pools, pool-less
198.18.25.0/24, and the Legacy/UEFI-64 PXE arch classes (option 93)
- add the KeaAPI-spawned REST service: Terraform-friendly CRUD over subnet and
client-class CRs (stable IDs, PUT upsert, 404 drift, bearer-token auth)
- add Makefile (patch/minor/major tag targets), distroless operator/api images,
an AlmaLinux+EPEL kea workload image, and woodpecker CI with k8s resources +
serviceAccountName on every step
- unit tests for config rendering, controller reconcile/config-hash, and the API
Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT