Procedures for cluster admins. The design explains why the controller behaves as described here.
A Service pinned by hand to an address of a class's range keeps it while it moves under the ledger, because the driver's pin equals the existing one:
-
Create an
IPAddressClaimin the Service's namespace that names the class and, inspec.addressName, theIPAddressof step 2. The MetalLB driver does not provision for a claim that names an address, so the claim waitsPendingfor that one. Where the optionalspec.addressNameadmission policy is enabled, this takes an allowlisted principal.apiVersion: local.sdn.cozystack.io/v1alpha1 kind: IPAddressClaim metadata: name: web namespace: tenant-a spec: className: public addressName: ip-203-0-113-7
-
Create the
IPAddresspre-bound to it:apiVersion: local.sdn.cozystack.io/v1alpha1 kind: IPAddress metadata: name: ip-203-0-113-7 spec: address: 203.0.113.7 className: public reclaimPolicy: Retain claimRef: {namespace: tenant-a, name: web} source: {fromClass: {}}
The core accepts the pre-binding only if the class and family match the claim and the claim holds no address of that family yet; otherwise the claim records a
PreBindingRefusedevent. -
Annotate the Service with
local.sdn.cozystack.io/ip-address-claim: web. The driver records the association and pins the same address.
The hand-written pin stays on the Service. Where metallb-iad's optional admission policy is enabled, removing it later takes an allowlisted principal. Without that policy, removing it is not gated, and only the driver's conflict detection reports the address going to another Service.
Creating the IPAddress before the claim does not work: an address whose
claimRef names a missing claim is reclaimed at once, Released under
Retain and deleted under Delete.
Claims, addresses and classes carry finalizers that the core removes
(claim-protection, address-protection, class-protection), and the
MetalLB driver adds teardown to the addresses it creates. While objects
remain and a controller is not running, deleting them, or a namespace that
holds claims, hangs. Tear down in this order:
- Remove the claim annotation from Services, or delete the Services.
- Delete every
IPAddressClaim. The core reclaims each address by itsreclaimPolicy. - Delete the
IPAddressobjects left behind (Released,Available). The driver releases each backend reservation. - Delete the
IPAddressClassobjects. Each goes once no address of it is left. - Uninstall the driver, then the core.
helm uninstallleaves the CRDs.
If the controllers are already gone, clear the finalizers by hand, for example:
kubectl get ipaddressclaims.local.sdn.cozystack.io -A \
-o jsonpath='{range .items[*]}{.metadata.namespace} {.metadata.name}{"\n"}{end}' |
while read -r ns name; do
kubectl -n "$ns" patch ipaddressclaims.local.sdn.cozystack.io "$name" \
--type=merge -p '{"metadata":{"finalizers":null}}'
done
for kind in ipaddresses ipaddressclasses; do
kubectl get "$kind.local.sdn.cozystack.io" -o name |
xargs -r -n1 kubectl patch --type=merge -p '{"metadata":{"finalizers":null}}'
doneThe group suffix matters: ipaddresses alone resolves to the Kubernetes
networking.k8s.io kind. Backend state the driver would have released,
such as MetalLB placeholder Services, is then left for you to delete.
A class is held until no IPAddress of it is left, in any phase. Under
reclaimPolicy: Retain a deleted claim leaves its addresses Released,
and a class's releaseDelay returns them to Available rather than
deleting them, so a class that has served claims keeps addresses that hold
it. Delete those IPAddress objects, and the class goes. Until then the
class reports a ClassInUse event naming them.
ipaddress.local.sdn.cozystack.io/held: "true" on a Released address
keeps its class's releaseDelay from returning it to the pool. The
annotation pauses the return, not the delay: removing it from an address
that has been Released for longer than releaseDelay returns the
address on the next pass.
The API is v1alpha1 with no conversion webhook. Helm installs the chart's
crds/ on first install only; make install-crds applies the current
CRDs, and a distribution that replaces CRDs on upgrade does the same. A
field removed or renamed in a later CRD invalidates stored objects that
still set it, so every release states its schema changes:
| release | schema change |
|---|---|
| v0.1.0 | initial IPAddress, IPAddressClaim, IPAddressClass |
| unreleased | IPAddressClass.spec.releaseDelay added; IPAddress gains a core-set Bound condition |