You deploy a Pod called vpn-gateway, point a test client at its address, and watch the packets vanish. Nothing in the manifest is wrong and the kubelet has no complaint to make. The reason is structural rather than configurational: Kubernetes hands every Pod its own network namespace, so a name, a label, or a shared API namespace changes nothing about where packets go. Deciding how VPN traffic leaves the cluster therefore starts with a question about ownership, not about container images.

Two mechanisms cover most real deployments, and they are not interchangeable. One puts the tunnel inside the workload's own network namespace. The other asks the CNI to steer selected traffic through a designated gateway node. They fail differently, scale differently, and answer to different teams, which is why the container question tends to produce arguments that never settle.

A Pod boundary is a namespace, not a jump host

A Linux network namespace owns its interfaces, addresses, routes and firewall rules, and each Pod receives one. Containers inside that Pod join it, which is exactly why a helper container can rewrite routes that the application then follows. A neighbouring Pod has an identical-looking but entirely separate stack. There is no shared table for a gateway Pod to inherit, and no implicit hand-off of traffic from one namespace to another.

That is also why the gateway-Pod pattern only works once something upstream actually sends packets to it: a Service, a static route on the node, or steering policy in the CNI. An address alone attracts nothing.

What a per-Pod tunnel buys, and what it costs

A tunnel running in the workload namespace gives that workload its own client identity, which many remote-access services require before they will authorize or account for a session. Blast radius stays local too: a broken tunnel takes out one Deployment instead of the shared path used by everything. How it runs depends on the implementation, and this is where people guess wrong.

Setup that only needs kernel state can finish in an init container and exit. A userspace daemon that must stay alive for the Pod's lifetime needs the restartable init container form introduced as a native sidecar in Kubernetes 1.29, and that distinction comes from the tunnel implementation rather than from taste. The cost of this model is duplication: every replica authenticates, receives its own tunnel address, and consumes a remote session slot, so an autoscaling event becomes a capacity event on the other side of the link. Copying a single credential into all replicas does not remove that cost, it removes the audit trail. Capabilities deserve the same scrutiny, since a tunnel that wants NET_ADMIN in its own namespace has no reason to inherit the host-wide authority of a privileged: true example.

When the CNI owns egress instead

If the CNI supports it, an egress gateway selects traffic by workload and destination range, then sends it through chosen nodes under one predictable source address. Cilium's version is the common reference, and its documentation is explicit that the feature needs specific Cilium configuration plus gateway nodes provisioned with the right interfaces and addresses. It also notes an initial period after Pod creation when steering is not yet enforced, which is a design input rather than a footnote.

Read that delay against your own promise. If the rule is that no traffic may leave by another path, a new Pod that transmits during the gap has already broken it, so the startup sequence needs an answer of its own: hold the application until policy applies, or accept the window and document it.

Keep the claim narrow, too. An egress gateway chooses and translates a path. It does not terminate a VPN, and it does not turn the cluster into a client of your remote network without a supported connection behind it.

QuestionPer-Pod tunnelCNI egress gateway
Who holds the client identityEach workloadThe gateway, shared
What scaling changesEnrollment and sessions per replicaThroughput and capacity per node
What the remote side seesDistinct addressesOne translated source
Failure scopeOne DeploymentEvery workload behind the gateway

Translation quietly changes what the remote side sees

Once the gateway rewrites the source, per-Pod addresses are gone, so an allowlist keyed to identity becomes an allowlist keyed to one shared address. That is a trade rather than a defect: fewer rules to maintain, coarser attribution when something misbehaves.

Application-level authentication and request logging have to carry what the network no longer reports. Treat gateway capacity and replacement as shared infrastructure, because a single node reboot now interrupts every workload behind it, and check the return path early: the remote end must know how to reach the translated address, usually through a route or NAT that a person, not a manifest, has to own. Address planning belongs here too, since overlapping cluster and remote ranges break either model and adopting IPv6 does not excuse a conflicting plan.

Decide by ownership, then test the failure you promised

Choose the per-Pod tunnel when the remote service authenticates individual workloads, when only a few Deployments need the link, and when a failure should stay local. Choose the gateway when a network team already owns the path, when many workloads share one destination, and when a stable source address is the whole point of the exercise.

Either way, verify the property you sold rather than the steady state. Create a Pod, let it transmit immediately, and watch the first seconds if the promise was that nothing escapes the tunnel. Scale to five replicas and count the sessions the remote end reports if the promise was per-workload identity. Those two checks expose the limits of the mechanism much faster than a review of the manifest, and they tell you which team owns the next incident.

Winndannde jowitiiɗe heen