Commit Graph

1 Commits

Author SHA1 Message Date
Michal
842408c0d9 labsim: prove k3s CAN be converted to dual-stack in place
k3s documents that dual-stack "cannot be enabled on an existing cluster". Rather
than accept that for a 145-day-old production cluster, build both shapes and
diff them. dualstack-lab.sh builds a single-node IPv4 cluster and a native
dual-stack one, takes a reflink copy of the IPv4 disk so a failed conversion
costs 90 seconds to undo, converts in place, and diffs the results.

Result: the conversion works. etcd data is never touched.

The documented blocker is real but narrower than stated. Cilium reports it
exactly -- `required IPv6 PodCIDR not available` -- because node.spec.podCIDRs
is assigned at join and is immutable, and the Kubernetes IPAM controller will
not add a second family later. That objection only holds while Cilium runs
ipam=kubernetes and therefore reads that field. Switching to cluster-pool IPAM
moves pod CIDR allocation into the CiliumNode CRD, where the operator hands out
both families on a cluster that was born IPv4-only.

Sequence that works, in order:
  1. k3s unit gains --cluster-cidr/--service-cidr/--node-ip with both families.
     k3s validates the two CIDRs together and refuses to start on a mismatch
     ("must share the same IP version"), so a partial edit crash-loops rather
     than coming up half-configured. That is the safe failure mode.
  2. The ServiceCIDR object picks up the IPv6 range on restart -- this is
     upstream's supported "single-to-dual-stack preserving the primary
     ServiceCIDR" path, and existing Services keep their IPv4 addresses.
  3. Cilium to ipam=cluster-pool with an IPv6 pool, then DELETE the CiliumNode
     so the operator reallocates; it will not add a family to an existing one.
  4. Expect a deadlock here: the agent will not go ready without a pod CIDR, so
     the node keeps the node.cilium.io/agent-not-ready taint, so the new
     operator that would assign the CIDR cannot schedule. Remove the taint by
     hand once to break it.

Verified on the converted cluster: pod with 10.42.0.125 AND fd00:42::4843, and
a PreferDualStack Service holding 10.43.122.115 AND fd00:43::72a3.

The only field that still differs from a native build is node.spec.podCIDRs,
which stays IPv4-only -- immutable, and unused once Cilium owns IPAM. CiliumNode
podCIDRs and ServiceCIDR are identical to the native cluster.

Not yet answered: this is one node. Whether a 3-server etcd cluster converts as
cleanly, and what a rejoining agent does, is the next experiment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
2026-08-24 22:46:27 +01:00