Transparently connect Tailscale VPN clients to Kubernetes ClusterIPs using eBPF.
When using Cilium CNI with a Tailscale subnet router, Kubernetes Service IPs (which are virtual IPs) are unreachable due to asymmetric routing. Pod IPs work, but Service IPs do not allow direct connections from Tailscale.
tail-lifter uses eBPF to perform on-the-fly DNAT/SNAT, translating Service IPs to Pod IPs, enabling seamless connectivity over Tailscale.
[Tailscale Client] ⟶ [Service IP] ⟶ [eBPF DNAT] ⟶ [Pod IP] ⟶ [eBPF SNAT] ⟶ [Service IP] ⟶ [Tailscale Client]
- eBPF Program: Attached to the
tailscale0interface, it intercepts and translates traffic between Service IPs and Pod IPs. - Go Controller: Watches Kubernetes Endpoints and updates eBPF maps with
ServiceIP → PodIPmappings.
Prerequisites:
- Kubernetes cluster with Cilium CNI
- Tailscale subnet router configured
Deploy:
# Build and deploy
just image-build
kind load docker-image ghcr.io/ziwon/tail-lifter:latest --name your-cluster
kubectl apply -f deploy/daemonset.yaml
# Advertise your Service IPs via Tailscale
tailscale up --advertise-routes=10.96.0.0/16Test:
# This should now work from any Tailscale client
curl your-service-ip:port- CNI: Cilium (Calico doesn't need this)
- Tailscale: Subnet router mode
- Kubernetes: Service discovery enabled
Apache 2.0 - see LICENSE