Tools: + k3s in a 2‑node homelab: why I use Tailscale ONLY for the control plane Tailscale

Tools: + k3s in a 2‑node homelab: why I use Tailscale ONLY for the control plane Tailscale

Context

Hardware

The problem: accessing the cluster from anywhere without exposing the LAN

The counterintuitive decision: Tailscale only for the control plane

The limits of this choice

What’s next DevOps career switch in progress, minimal 2‑node k3s homelab in La Celle-Saint-Cloud. I publicly document every architecture decision — both to clarify my own choices and to show a future employer that I know how to operate a Kubernetes cluster in real conditions. Today’s topic: how to connect 2 k3s nodes without hacking together a site-to-site VPN, without custom router tables, and without paying for a cloud. Two heterogeneous machines: Server node: old Ivy Bridge desktop, Intel i7-3770K (2012), 32 GB RAM, RTX 2060 12 GB for future PyTorch workloads. Ethernet connection to a 1 Gb switch. Worker node: GEEKOM A8 Mini PC, AMD Ryzen 7 8745HS (Zen 4, 8c/16t, 28 W TDP), 32 GB RAM, 1 TB NVMe SSD. Ethernet on the same switch. Laptop: HP Pavilion Gaming running Ubuntu 24.04, my main workstation. Both nodes run Ubuntu 24.04.4 LTS, kernel 6.17, k3s v1.35.4, containerd runtime 2.2.3. Intentionally asymmetric topology: the server gets the GPU for ML, the worker remains a general-purpose compute node for CI/CD and stateless apps. When I work from home, my laptop is on the same LAN as both nodes — easy. But as soon as I move (coffee shop, trip), I want to be able to: Three serious options: Manual site-to-site VPN like WireGuard: homegrown config, key generation, peer propagation, MTU tuning. Feasible but time‑consuming. Publicly exposed bastion: one node accessible via public SSH, the rest behind it. Increases the attack surface, not interested. Managed mesh VPN: Tailscale, Twingate, NetBird. Tailscale is free up to 100 personal devices, based on WireGuard under the hood, and installs in 5 minutes. On each machine (server, worker, laptop): The --ssh flag enables SSH managed by Tailscale (authentication via Tailscale ACLs rather than scattered SSH keys). The direct 192.168.1.x:41641 is important: on the same LAN, Tailscale detects peers and does not go through DERP relays. Latency and bandwidth = native LAN. Off the LAN, it routes via DERP (Tailscale-hosted) with a bit more latency but still usable. Immediate bonus: MagicDNS resolves ml-hellomichka and hellomichka-a8 everywhere, no more memorizing IPs. The classic “Tailscale + k3s” homelab mistake you see online: forcing all Kubernetes traffic (Flannel CNI, kubelet, etcd) to go through Tailscale via --flannel-iface=tailscale0 and --node-ip=100.x.x.x. In my setup, I chose not to do that. The proof in kubectl get nodes -o wide: the INTERNAL-IP values are my LAN IPs (192.168.1.52, 192.168.1.103), not my Tailscale IPs (100.x.x.x). My two nodes are physically on the same 1 Gb switch. The Kubernetes data plane (pod‑to‑pod traffic, kubelet‑to‑API‑server) benefits from native throughput, without WireGuard overhead or reduced MTU. If Tailscale goes down or Tailscale’s control plane has an incident (rare but it happens), my cluster keeps running. The decoupling of the admin control plane / K8s data plane is intentional. Tailscale remains useful for what it’s best at: giving me encrypted access to my cluster from anywhere, without hacks. Concretely, from my laptop, my ~/.kube/config points to https://100.117.114.43:6443. Port 6443 on the server is exposed only on the tailscale0 interface. Nobody on the internet can reach the API server. To be honest, this setup has limitations you need to be aware of: If I add a third node outside the LAN (at a friend’s house, on a cloud), I will have to switch to --flannel-iface=tailscale0 because the nodes will no longer be able to reach each other directly. No encryption of intra‑cluster network traffic. For a personal homelab, that’s fine. For multi‑site production, it is not acceptable. MagicDNS does not mix with the cluster’s CoreDNS. My pods resolve their services via Kubernetes CoreDNS, my laptop resolves hostnames via MagicDNS. No cross‑pollution, but this needs to be understood. Article 2 planned for next week: deploying Traefik with cert-manager to expose a service in HTTPS via an external domain, without ugly NodePorts or cloud LoadBalancers. The real question: how to publish my homelab apps on the internet cleanly when you have a dynamic residential IP. The GitHub repo accompanying this series is coming — I did not want to publish it before having a first article setting the stage. To follow the series: free Substack subscription. For technical feedback: LinkedIn or GitHub (link in the bio). Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">curl -fsSL https://tailscale.com/-weight: 500;">install.sh | sh -weight: 600;">sudo tailscale up --ssh -weight: 500;">curl -fsSL https://tailscale.com/-weight: 500;">install.sh | sh -weight: 600;">sudo tailscale up --ssh -weight: 500;">curl -fsSL https://tailscale.com/-weight: 500;">install.sh | sh -weight: 600;">sudo tailscale up --ssh - Server node: old Ivy Bridge desktop, Intel i7-3770K (2012), 32 GB RAM, RTX 2060 12 GB for future PyTorch workloads. Ethernet connection to a 1 Gb switch. - Worker node: GEEKOM A8 Mini PC, AMD Ryzen 7 8745HS (Zen 4, 8c/16t, 28 W TDP), 32 GB RAM, 1 TB NVMe SSD. Ethernet on the same switch. - Laptop: HP Pavilion Gaming running Ubuntu 24.04, my main workstation. - Run -weight: 500;">kubectl get pods from anywhere - SSH into the nodes to debug - Without opening port 6443 or port 22 on my home router - Manual site-to-site VPN like WireGuard: homegrown config, key generation, peer propagation, MTU tuning. Feasible but time‑consuming. - Publicly exposed bastion: one node accessible via public SSH, the rest behind it. Increases the attack surface, not interested. - Managed mesh VPN: Tailscale, Twingate, NetBird. Tailscale is free up to 100 personal devices, based on WireGuard under the hood, and installs in 5 minutes. - My two nodes are physically on the same 1 Gb switch. The Kubernetes data plane (pod‑to‑pod traffic, kubelet‑to‑API‑server) benefits from native throughput, without WireGuard overhead or reduced MTU. - If Tailscale goes down or Tailscale’s control plane has an incident (rare but it happens), my cluster keeps running. The decoupling of the admin control plane / K8s data plane is intentional. - Tailscale remains useful for what it’s best at: giving me encrypted access to my cluster from anywhere, without hacks. - If I add a third node outside the LAN (at a friend’s house, on a cloud), I will have to switch to --flannel-iface=tailscale0 because the nodes will no longer be able to reach each other directly. - No encryption of intra‑cluster network traffic. For a personal homelab, that’s fine. For multi‑site production, it is not acceptable. - MagicDNS does not mix with the cluster’s CoreDNS. My pods resolve their services via Kubernetes CoreDNS, my laptop resolves hostnames via MagicDNS. No cross‑pollution, but this needs to be understood.