defaults: built-in baseline is dual-stack (IPv6 + IPv4), not IPv6-only
Build flock Image / build (push) Has been cancelled

BuiltinFamilyDefaults() now returns {WantV6: true, WantV4: true}. Pods
that want a single family explicitly opt out via the
flock.fritzlab.net/ipv4 (or ipv6) annotation, or the operator narrows
the default at the node level via NodeConfig.Spec.Defaults.

Annotation precedence is unchanged: pod annotation > NodeConfig defaults
> built-in baseline. Tests updated to reflect the new baseline; the
"opt out of v4" path now has explicit coverage.

Docs updated:
  - NodeConfig.Spec.Defaults Go doc + CRD descriptions reflect the new
    baseline and its overrides
  - README opening framing softened from "IPv6-first" to "dual-stack,
    IPv6-friendly"; example pods + spec.defaults table flipped to
    treat dual-stack as the default and v6/v4-only as overrides
  - README NetworkPolicy line in the comparison table flipped to
    "yes (nftables)" since v1 enforcement shipped
  - Limitations note about IPv4-only destinations rewritten — every
    pod has v4 by default now, so the question is whether your IPv4
    pool is routable beyond your network

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Donavan Fritz
2026-04-25 10:07:48 -05:00
parent a7dc7bf1f4
commit a6202a36bd
7 changed files with 76 additions and 46 deletions
+26 -22
View File
@@ -2,8 +2,9 @@
A small, opinionated Kubernetes CNI built around three ideas:
1. **IPv6-first.** Every pod gets a globally routable IPv6 address. IPv4 is
per-pod opt-in for legacy clients.
1. **Dual-stack, IPv6-friendly.** Every pod gets a globally routable IPv6
address by default. IPv4 is also enabled by default; either family can
be turned off per-node or per-pod when you really mean to.
2. **No tunnels, no NAT.** Pod addresses are the real packets on the wire.
Each node speaks BGP to its upstream router and advertises its own
per-node prefix. The pod network is just the LAN, plus host routes.
@@ -127,7 +128,7 @@ spec:
- 192.0.2.0/24 # IPv4 pool, used only when a pod opts in.
defaults:
ipv6: true # Optional. Built-in baseline if omitted.
ipv4: false # Optional. Built-in baseline if omitted.
ipv4: true # Optional. Built-in baseline if omitted.
bgp:
asn: 65101 # This node's local ASN.
peers:
@@ -143,13 +144,13 @@ spec:
on this node — i.e. when the pod has no explicit `flock.fritzlab.net/ipv6`
or `flock.fritzlab.net/ipv4` annotation. Pod annotations always override.
If you omit `spec.defaults` (or any individual field inside it) flock
falls back to its built-in baseline of **IPv6 on, IPv4 off**.
falls back to its built-in baseline of **dual-stack (IPv6 on, IPv4 on)**.
| Goal | `spec.defaults` |
|---------------------------|----------------------------------------|
| IPv6-only (the default) | omit, or `{ ipv6: true, ipv4: false }`|
| Dual-stack by default | `{ ipv6: true, ipv4: true }` |
| IPv4-only (legacy node) | `{ ipv6: false, ipv4: true }` |
| Goal | `spec.defaults` |
|-----------------------------------|----------------------------------------|
| Dual-stack (the default) | omit, or `{ ipv6: true, ipv4: true }` |
| IPv6-only node | `{ ipv6: true, ipv4: false }` |
| IPv4-only (legacy node) | `{ ipv6: false, ipv4: true }` |
A NodeConfig that resolves to "neither family" is rejected at allocation
time, so misconfiguring both to false will surface as an error on the
@@ -183,7 +184,7 @@ behaviour.
### Example pods
Default IPv6-only — no annotations needed:
Default dual-stack — no annotations needed:
```yaml
apiVersion: v1
@@ -192,15 +193,15 @@ metadata:
name: minimal
```
Dual-stack on a node whose default is IPv6-only:
IPv6 only — opt out of the default v4 allocation:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: legacy-client
name: v6-only
annotations:
flock.fritzlab.net/ipv4: "true"
flock.fritzlab.net/ipv4: "false"
```
Operator-friendly addressing — `fnv(namespace) | fnv(pod) | random`
@@ -227,7 +228,6 @@ spec:
template:
metadata:
annotations:
flock.fritzlab.net/ipv4: "true"
flock.fritzlab.net/anycast: "2001:db8:a::53, 192.0.2.53"
spec:
containers:
@@ -274,28 +274,32 @@ annotation and the pod gets a normal allocation.
| | flock | Calico | Cilium |
|--------------------------|-----------------------------|------------------------------|------------------------------|
| Default address family | IPv6 | IPv4 | dual |
| Default address family | dual (IPv6+IPv4) | IPv4 | dual |
| BGP | yes (BIRD) | yes | optional |
| Overlay (VXLAN/IPIP) | never | optional | yes (geneve) or native |
| NAT in datapath | never | masquerade by default | masquerade by default |
| Anycast pod addressing | first-class | manual | optional, via service mesh |
| eBPF datapath | no | optional | yes |
| NetworkPolicy | not yet | yes (Felix) | yes (eBPF) |
| NetworkPolicy | yes (nftables) | yes (Felix) | yes (eBPF) |
| Cluster size target | small (< 100 nodes) | thousands | thousands |
| Operational surface area | low (1 DaemonSet, 1 CRD) | medium | high |
| Production-ready | alpha | yes | yes |
flock is not trying to compete with Calico or Cilium. The right answer
for most clusters is one of those two — flock exists for clusters where
every node already speaks BGP, the operator wants to think in IPv6-first
terms, and per-pod anycast is something they actually want to use rather
than work around.
every node already speaks BGP, the operator wants real (no NAT) IPv6
addressing on every pod, and per-pod anycast is something they actually
want to use rather than work around.
## Limitations and non-goals
- No NetworkPolicy enforcement yet (planned).
- No NAT, no masquerade, no SNAT-egress. If your pods need to reach a
legacy IPv4-only destination, give them an IPv4 address explicitly.
- NetworkPolicy supports `networking.k8s.io/v1` (ingress + egress, all
three peer types, numeric ports + port ranges). Named ports and
AdminNetworkPolicy are not yet implemented.
- No NAT, no masquerade, no SNAT-egress. Pods reach the wider internet
using their real cluster-routable addresses; if your IPv4 pool isn't
routable beyond your network, those pods can't reach v4-only hosts on
the public internet without help from your border router.
- No multi-cluster, no peering across clusters.
- Linux-only datapath.
- IPAM is per-node — there's no global allocator and no IP mobility.