Files
kea-operator/config/crd/bases/kea.unkin.net_keasubnets.yaml
T
unkinben d3fb5dcd1a
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/test Pipeline failed
Scaffold kea-operator: CRDs, controllers, config rendering, REST API, CI
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
2026-08-02 18:42:46 +10:00

215 lines
8.8 KiB
YAML

---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: keasubnets.kea.unkin.net
spec:
group: kea.unkin.net
names:
kind: KeaSubnet
listKind: KeaSubnetList
plural: keasubnets
shortNames:
- ksn
singular: keasubnet
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.clusterRef
name: Cluster
type: string
- jsonPath: .spec.subnet
name: Subnet
type: string
- jsonPath: .status.phase
name: Phase
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: KeaSubnet is one DHCPv4 subnet declaration referenced to a KeaCluster.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: KeaSubnetSpec defines a single DHCPv4 subnet served by a
KeaCluster.
properties:
bootFileName:
description: BootFileName sets the PXE boot file for this subnet (overridden
by class).
type: string
clientClasses:
description: ClientClasses restricts the subnet to the listed client
classes.
items:
type: string
type: array
clusterRef:
description: |-
ClusterRef selects the owning KeaCluster by name. Empty means every
KeaCluster in the namespace.
type: string
dnsServers:
description: DNSServers is the domain-name-servers option.
items:
type: string
type: array
domainName:
description: DomainName is the per-subnet domain-name option.
type: string
id:
description: |-
ID is the stable Kea subnet id. When zero the operator assigns one
deterministically from the sorted set of subnets.
type: integer
nextServer:
description: NextServer is the TFTP server address for PXE (siaddr
/ next-server).
type: string
optionData:
description: OptionData carries any additional option values for the
subnet.
items:
description: OptionData is a rendered DHCPv4 option value (subnet-
or class-scoped).
properties:
code:
description: Code of the option (alternative to Name).
type: integer
csvFormat:
description: CSVFormat controls whether Data is parsed as CSV
(default true in Kea).
type: boolean
data:
description: Data is the option value(s), comma-separated per
Kea convention.
type: string
name:
description: Name of the option, e.g. "routers", "domain-name-servers".
type: string
space:
description: Space defaults to "dhcp4".
type: string
required:
- data
type: object
type: array
pools:
description: |-
Pools are dynamic ranges, e.g. "198.18.13.200 - 198.18.13.220". A subnet
with no pool is still declared so relayed requests on that network are
serviced (matching, option delivery) without dynamic allocation.
items:
type: string
type: array
routers:
description: Routers is the default-gateway list (routers option).
items:
type: string
type: array
subnet:
description: Subnet is the CIDR, e.g. "198.18.13.0/24".
type: string
validLifetime:
description: ValidLifetime overrides the cluster default lease time
for this subnet.
type: integer
required:
- subnet
type: object
status:
description: KeaSubnetStatus captures observed state.
properties:
assignedID:
description: AssignedID is the subnet id that was rendered into kea
config.
type: integer
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
observedGeneration:
format: int64
type: integer
phase:
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}