Skip to content

hack: add opt-in NAT64 support to IPv6-only kind clusters - #1275

Draft
Yuan Gao (ygao-g) wants to merge 3 commits into
agent-substrate:mainfrom
ygao-g:kind-ipv6-nat64
Draft

hack: add opt-in NAT64 support to IPv6-only kind clusters#1275
Yuan Gao (ygao-g) wants to merge 3 commits into
agent-substrate:mainfrom
ygao-g:kind-ipv6-nat64

Conversation

@ygao-g

@ygao-g Yuan Gao (ygao-g) commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Do not merge until #958 lands. Its two commits are in the diff below
because a fork branch cannot be based on another pull request; only the last
commit is under review here.

Part of #246.

An IPv6-only kind cluster is usable on a host that has IPv6 egress of its own,
and not on one that does not: external names resolve to addresses nothing can
reach, so anything that pulls an image or calls out times out. Every GitHub
Actions runner is the second kind of host, which is what stops an IPv6-only CI
lane from getting as far as the install.

IPV6_DNS64_PREFIX on hack/create-kind-cluster.sh turns on both halves
together: it deploys the kubernetes-sigs/nat64 agent from
hack/third_party/nat64/ and points cluster DNS through the prefix that agent
translates. Neither half is any use alone — the translator sits unused without
the DNS half, and the synthesized addresses go nowhere without the translator.
It stays opt-in and off by default, because it routes every external name
through the prefix, including names with AAAA records that already work.

The agent runs in the cluster rather than on the host, so nothing here enables
forwarding on the developer's machine or installs a userspace translator. It
translates Pod source addresses only; a hostNetwork pod and containerd use the
node's own dual-stack interfaces and reach IPv4 without it. Its --nat-v4-cidr
default is why the script narrows each node's pod CIDR to a /120.

The manifest is vendored rather than fetched at run time, because upstream ships
no release asset and its install.yaml at tag v0.4.1 still names the v0.2.1
image — the only copy matching the release is on a moving branch. So
hack/third_party/nat64/VERSION pins the upstream commit and the image digest,
and hack/update/nat64.sh regenerates the manifest from that pin. The copy is
therefore reproducible rather than hand-made: re-running the script and
diffing is a drift check. Upstream's LICENSE is vendored alongside it.

It is amd64 only in practice — every published tag ships an x86-64 binary in the
arm64 slot of its image index — so NAT64_IMAGE overrides it for arm64
developers. That covers CI, which is amd64.

#939 is the IPv6-only CI job this exists for. It asks for this setup from
hack/ and reads back what it got, rather than configuring the translator
inline: a job that supplied the very thing it exists to gate would prove
nothing by going green.

commit subject
9084d8eb hack: drop the IPv6 kubeconfig repoint #958
1cb5a78f hack: fix DNS on IPv6-only kind clusters #958
544e519b hack: add opt-in NAT64 support to IPv6-only kind clusters this PR

🤖 Generated with Claude Code

@BenTheElder

Copy link
Copy Markdown
Collaborator

why do we need egress off of the host machine from the test cluster at all?

@BenTheElder

Copy link
Copy Markdown
Collaborator

we push images into the registry / cluster

@ygao-g

Yuan Gao (ygao-g) commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

why do we need egress off of the host machine from the test cluster at all?

Benjamin Elder (@BenTheElder) It's the actor-egress e2e tests, which fetch real external origins:

On an IPv6-only cluster the script rewrote kind's `https://[::1]:PORT`
kubeconfig entry to `https://localhost:PORT` unconditionally. That
breaks any host whose `/etc/hosts` leaves `localhost` off the `::1`
line, including the Ubuntu cloud image Lima runs: `localhost` resolves
v4-only and every later kubectl fails at connect. The rewrite existed
for one case, a macOS client reaching kind inside a Lima VM, where
limactl re-forwards the published port to the host's v4 loopback only.
Running the loop inside the guest reaches `[::1]` directly and avoids
that path entirely.
On a fresh IPv6-only kind cluster nothing resolves from inside a pod, so
the install never completes. CoreDNS inherits the node's IPv4 resolver,
which a v6-only pod cannot reach, and the in-cluster registry has no name
a pod can look up. This gives the cluster its own Corefile, gated on
ipv6: a forward to an IPv6 upstream, overridable with IPV6_DNS_UPSTREAM,
and a kind-registry:53 server block so atelet can pull from its own
network namespace.

The Corefile patch runs kubectl straight after `kind create`, which
returns before the apiserver answers, so the script now waits for the
control plane from inside the node first.
@ygao-g
Yuan Gao (ygao-g) marked this pull request as draft August 28, 2026 22:50
@ygao-g
Yuan Gao (ygao-g) force-pushed the kind-ipv6-nat64 branch 5 times, most recently from 5a52665 to cd207e3 Compare August 29, 2026 16:31
An IPv6-only kind cluster on a host with no IPv6 egress comes up but
cannot reach anything: external names resolve to addresses nothing can
route, so every outbound call from a pod times out. Nothing in the tree
set up the translation such a host needs, so that cluster could not be
brought up by hand at all. IPV6_DNS64_PREFIX now deploys the
kubernetes-sigs/nat64 agent and points cluster DNS through it, and a
probe pod proves the two halves meet before the install can fail
minutes later as a rollout timeout.

Off by default, because translate_all sends traffic through a
translator that a host with working IPv6 egress does not need. The
agent maps each Pod address into an IPv4 pool and refuses a Pod CIDR
wider than /120, so the prefix also narrows what kubeadm hands out.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants