Linkerd CNI transparent proxying does not work with gVisor `network=sandbox`
@kflynn is already working on this.
Since Aug 28, 2026.
- Dominant language
- Go
- Stars
- 11.5k
- Forks
- 1.4k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 53
Description
What is the issue?
Linkerd CNI transparent proxy redirection works for an otherwise identical runc workload, but fails when the Pod uses the gVisor runsc RuntimeClass with its default network=sandbox networking mode.
The failure is deterministic and occurs in Linkerd's own linkerd-network-validator.
With runc, the validator's connection to 1.1.1.1:20001 is transparently redirected to the Linkerd outbound proxy on port 4140, the validator exits successfully, and the application starts.
With:
runtimeClassName: gvisor
the same validator connection is not redirected to the Linkerd proxy. It times out after approximately 10 seconds, exits with code 95, and the Pod remains at Init:1/2.
The Linkerd CNI installation itself is healthy and correctly chained behind Cilium. The same workload succeeds when only the RuntimeClass is changed back to the normal runtime.
I have not established whether this is a Linkerd limitation, a gVisor networking limitation, or whether another Linkerd interception mode is expected for this configuration.
How can it be reproduced?
Change only the Pod runtime:
spec:
runtimeClassName: gvisor
Keep the same Linkerd injection and workload configuration.
Observed:
validator attempts 1.1.1.1:20001
expected redirect to Linkerd outbound :4140 does not occur
~10 second timeout
validator exits 95
Pod remains Init:1/2
Removing runtimeClassName: gvisor again makes the otherwise identical workload succeed.
Logs, error output, etc
The relevant behavior is:
runc
linkerd-network-validator
target: 1.1.1.1:20001
transparent redirect: succeeds
outbound proxy: :4140
exit code: 0
workload starts
gVisor network=sandbox
linkerd-network-validator
target: 1.1.1.1:20001
expected transparent redirect does not occur
timeout: approximately 10 seconds
exit code: 95
Pod state: Init:1/2
The test was repeated with the same workload configuration; changing only the RuntimeClass changes the result.
If more detailed validator/proxy/CNI logs would be useful, I can collect and attach them.
output of linkerd check -o short
linkerd-version
‼ cli is up-to-date
is running version 26.6.3 but the latest edge version is 26.8.4
see https://linkerd.io/2/checks/#l5d-version-cli for hints
control-plane-version
‼ control plane is up-to-date
is running version 26.8.2 but the latest edge version is 26.8.4
see https://linkerd.io/2/checks/#l5d-version-control for hints
‼ control plane and cli versions match
control plane running edge-26.8.2 but cli running edge-26.6.3
see https://linkerd.io/2/checks/#l5d-version-control for hints
Environment
Kubernetes distribution: k0s
Primary CNI: Cilium 1.18.5
CNI chain: Cilium -> Linkerd CNI
Linkerd control plane/proxies: edge-26.8.2
Linkerd CLI: edge-26.6.3
Linkerd mode: CNI enabled, nft mode
Linkerd injection: enabled
Container runtime: containerd
gVisor:
runtime: runsc
RuntimeClass: gvisor
networking: network=sandbox
Host OS / kernel:
Alpine Linux v3.22
6.12.103-0-virt
Kubernetes version:
Kubernetes v1.36.3+k0s
gVisor/runsc version:
runsc version release-20260810.0
spec: 1.2.1
Linkerd CNI version:
cr.l5d.io/linkerd/cni-plugin:v1.6.8
The Linkerd CLI/control-plane version difference is known in this environment, but the normal-runtime control workload succeeds with the same cluster configuration.
Possible solution
I do not yet know the correct implementation-level fix.
My working hypothesis is that this is related to the interaction between Linkerd's CNI-installed transparent redirection and gVisor's userspace network stack.
With gVisor network=sandbox, application networking is implemented by gVisor's netstack rather than ordinary application sockets in the Linux Pod network namespace. Linkerd normally relies on transparent iptables/nftables interception to redirect application traffic to its inbound/outbound proxy ports.
Possible outcomes that would solve the problem for users would be:
- support transparent Linkerd interception for gVisor network=sandbox;
- provide/document an interception mode compatible with this networking model; or
- if this combination cannot work, detect or document the incompatibility clearly.
I would be interested to know whether there is already a Linkerd transport/interception mode that avoids relying on the traffic path affected by gVisor netstack.
Additional context
Our use case is running untrusted execution workspaces under gVisor while retaining Linkerd identity and mTLS for communication with trusted surrounding Kubernetes services.
Conceptually:
trusted control plane
|
| Linkerd mTLS
v
execution service
|
v
gVisor-isolated untrusted workspace
We specifically want to keep gVisor's network=sandbox isolation properties.
Using a host-network-stack passthrough mode for runsc would weaken one of the isolation properties for which we are using gVisor, so it is not an equivalent workaround for this use case.
Would you like to work on fixing this bug?
yes
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.