<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mickaël Girondeau </title>
    <description>The latest articles on DEV Community by Mickaël Girondeau  (@hellomichka_78vls).</description>
    <link>https://dev.to/hellomichka_78vls</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2833889%2F5a48c1e5-340d-4bed-bddc-dc1aaf991657.jpg</url>
      <title>DEV Community: Mickaël Girondeau </title>
      <link>https://dev.to/hellomichka_78vls</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hellomichka_78vls"/>
    <language>en</language>
    <item>
      <title>Tailscale + k3s in a 2‑node homelab: why I use Tailscale ONLY for the control plane</title>
      <dc:creator>Mickaël Girondeau </dc:creator>
      <pubDate>Tue, 26 May 2026 17:51:13 +0000</pubDate>
      <link>https://dev.to/hellomichka_78vls/tailscale-k3s-in-a-2-node-homelab-why-i-use-tailscale-only-for-the-control-plane-1khj</link>
      <guid>https://dev.to/hellomichka_78vls/tailscale-k3s-in-a-2-node-homelab-why-i-use-tailscale-only-for-the-control-plane-1khj</guid>
      <description>&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Today’s topic: &lt;strong&gt;how to connect 2 k3s nodes without hacking together a site-to-site VPN, without custom router tables, and without paying for a cloud.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware
&lt;/h3&gt;

&lt;p&gt;Two heterogeneous machines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Server node&lt;/strong&gt;: old Ivy Bridge desktop, Intel i7-3770K (2012), 32 GB RAM, &lt;strong&gt;RTX 2060 12 GB&lt;/strong&gt; for future PyTorch workloads. Ethernet connection to a 1 Gb switch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Worker node&lt;/strong&gt;: 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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Laptop: HP Pavilion Gaming running Ubuntu 24.04, my main workstation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both nodes run &lt;strong&gt;Ubuntu 24.04.4 LTS&lt;/strong&gt;, kernel 6.17, &lt;strong&gt;k3s v1.35.4&lt;/strong&gt;, containerd runtime 2.2.3.&lt;/p&gt;

&lt;p&gt;Intentionally asymmetric topology: the server gets the GPU for ML, the worker remains a general-purpose compute node for CI/CD and stateless apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem: accessing the cluster from anywhere without exposing the LAN
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;kubectl get pods&lt;/code&gt; from anywhere&lt;/li&gt;
&lt;li&gt;SSH into the nodes to debug&lt;/li&gt;
&lt;li&gt;Without opening port 6443 or port 22 on my home router&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three serious options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manual site-to-site VPN&lt;/strong&gt; like WireGuard: homegrown config, key generation, peer propagation, MTU tuning. Feasible but time‑consuming.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publicly exposed bastion&lt;/strong&gt;: one node accessible via public SSH, the rest behind it. Increases the attack surface, not interested.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Managed mesh VPN&lt;/strong&gt;: Tailscale, Twingate, NetBird. Tailscale is free up to 100 personal devices, based on WireGuard under the hood, and installs in 5 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose &lt;strong&gt;Tailscale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On each machine (server, worker, laptop):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh

&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up &lt;span class="nt"&gt;--ssh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--ssh&lt;/code&gt; flag enables SSH managed by Tailscale (authentication via Tailscale ACLs rather than scattered SSH keys).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;direct 192.168.1.x:41641&lt;/code&gt; is important: on the same LAN, Tailscale detects peers and &lt;strong&gt;does not go through DERP relays&lt;/strong&gt;. Latency and bandwidth = native LAN. Off the LAN, it routes via DERP (Tailscale-hosted) with a bit more latency but still usable.&lt;/p&gt;

&lt;p&gt;Immediate bonus: MagicDNS resolves &lt;code&gt;ml-hellomichka&lt;/code&gt; and &lt;code&gt;hellomichka-a8&lt;/code&gt; everywhere, no more memorizing IPs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The counterintuitive decision: Tailscale only for the control plane
&lt;/h3&gt;

&lt;p&gt;The classic “Tailscale + k3s” homelab mistake you see online: forcing &lt;strong&gt;all Kubernetes traffic&lt;/strong&gt; (Flannel CNI, kubelet, etcd) to go through Tailscale via &lt;code&gt;--flannel-iface=tailscale0&lt;/code&gt; and &lt;code&gt;--node-ip=100.x.x.x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In my setup, &lt;strong&gt;I chose not to do that&lt;/strong&gt;. The proof in &lt;code&gt;kubectl get nodes -o wide&lt;/code&gt;: the &lt;code&gt;INTERNAL-IP&lt;/code&gt; values are my LAN IPs (&lt;code&gt;192.168.1.52&lt;/code&gt;, &lt;code&gt;192.168.1.103&lt;/code&gt;), not my Tailscale IPs (&lt;code&gt;100.x.x.x&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;My two nodes are physically on the same 1 Gb switch&lt;/strong&gt;. The Kubernetes data plane (pod‑to‑pod traffic, kubelet‑to‑API‑server) benefits from native throughput, without WireGuard overhead or reduced MTU.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If Tailscale goes down or Tailscale’s control plane has an incident&lt;/strong&gt; (rare but it happens), my cluster keeps running. The decoupling of the admin control plane / K8s data plane is intentional.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tailscale remains useful for what it’s best at&lt;/strong&gt;: giving me encrypted access to my cluster from anywhere, without hacks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concretely, from my laptop, my &lt;code&gt;~/.kube/config&lt;/code&gt; points to &lt;code&gt;https://100.117.114.43:6443&lt;/code&gt;. Port 6443 on the server is exposed &lt;strong&gt;only&lt;/strong&gt; on the &lt;code&gt;tailscale0&lt;/code&gt; interface. Nobody on the internet can reach the API server.&lt;/p&gt;

&lt;h3&gt;
  
  
  The limits of this choice
&lt;/h3&gt;

&lt;p&gt;To be honest, this setup has limitations you need to be aware of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If I add a third node outside the LAN&lt;/strong&gt; (at a friend’s house, on a cloud), I will have to switch to &lt;code&gt;--flannel-iface=tailscale0&lt;/code&gt; because the nodes will no longer be able to reach each other directly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No encryption of intra‑cluster network traffic&lt;/strong&gt;. For a personal homelab, that’s fine. For multi‑site production, it is not acceptable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MagicDNS does not mix with the cluster’s CoreDNS&lt;/strong&gt;. My pods resolve their services via Kubernetes CoreDNS, my laptop resolves hostnames via MagicDNS. No cross‑pollution, but this needs to be understood.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What’s next
&lt;/h3&gt;

&lt;p&gt;Article 2 planned for next week: &lt;strong&gt;deploying Traefik with cert-manager to expose a service in HTTPS via an external domain&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;p&gt;The GitHub repo accompanying this series is coming — I did not want to publish it before having a first article setting the stage.&lt;/p&gt;

&lt;p&gt;To follow the series: free Substack subscription. For technical feedback: LinkedIn or GitHub (link in the bio).&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>homelab</category>
      <category>tailscale</category>
    </item>
  </channel>
</rss>
