blog.anqou.net
rss
author
tags

Tailscale K8s Operator で作る Ingress に同一 LAN から direct connection を張れない

自宅の LAN 内に常時起動しているマシンを置いて k3s をインストールし、自宅ネットワークに閉じた諸々をサーブしようと計画しています。基本的には自宅 LAN 内の別のマシンからアクセスするのですが出先からアクセスしたい時もあるので、 k3s 上に立ち上げた Pod に Tailscale(tailnet) 経由でアクセスできるようになっていると便利です。

Tailscale Kubernetes Operator を使うと、 Ingress リソースを経由して Pod を tailnet に公開できます。これを使って無事 Argo CD を tailnet に公開するところまではできたのですが、マシンを置いている自宅 LAN と同じ LAN につながっているマシンからは、通信が必ず DERP 経由になってしまい direct connection にならないことに気づきました:

$ tailscale ping argocd
pong from argocd (100.X.X.X) via DERP(tok) in 23ms
pong from argocd (100.X.X.X) via DERP(tok) in 23ms
...
pong from argocd (100.X.X.X) via DERP(tok) in 23ms
pong from argocd (100.X.X.X) via DERP(tok) in 22ms
direct connection not established

面白いことに、自宅 LAN 以外の異なるネットワークにつながったマシンからだと direct connection を張ることができます:

$ tailscale ping argocd
pong from argocd (100.X.X.X) via (自宅の公開 IP アドレス):37366 in 99ms

また、自宅 LAN 内でも、k3s のホストで動いている Tailscale クライアントには direct connection を張れます。つまり:

自宅 LAN(192.168.1.0/24)のルーター(203.0.113.1)
├─ サーバーマシン(192.168.1.1, 100.X.X.X)
│   └─ k3s 上の Pod(10.40.0.1, 100.Y.Y.Y)
└─ マシン 1(192.168.1.2, 100.Z.Z.Z)

外部ネットワーク
└─ マシン 2(100.A.A.A)

のような状況で:

には direct connection を通せるのに、マシン 1 から Pod には通せないということです。

Tailscale の issue を漁ると、CNI プラグインの動きによっては Pod への direct connection が張れないという 似たような症状の issue を見つけたので、 k3s のデフォルトの Flannel をやめて Calico や Cilium を試してみたのですが、状況は改善しませんでした。

正直お手上げだったので NixOS の設定ファイルと Kubernetes のマニフェストを GLM-5.2 に渡して調査させたところ、以下のような原因ではないかと推測できました。

そもそも Tailscale は UDP ホールパンチを使って direct connection を形成します。そのため、マシン M1 と M2 が direct connection で通信するためには、M1 と M2 が自分の IP アドレスを取得し、それを互いに広告した上で対向の IP アドレスにアクセスする必要があります。つまり M1 から M2 へ direct connection を張るためには M1 から M2 の IP アドレスへ直接アクセスできなければなりません。

上記のネットワークでマシン 1 からサーバーマシンへアクセスする場合、サーバーマシンは自らのプライベート IP アドレス(192.168.1.1)を広告できるため、direct connection でつなぐことができます。

しかしマシン 1 から Pod へアクセスする場合、Pod が広告できるのは Kubernetes クラスタ内でしか使えない Pod の IP アドレス(10.40.0.1)か、STUN で手に入るルーターの公開 IP アドレス(203.0.113.1)のみです。前者はどう頑張ってもマシン 1 からは使えないので後者でアクセスするしかなく、そのためにはマシン 1 から自宅ルーターへアクセスし、折り返して Pod へアクセスするようなヘアピン NAT が必要になります。

実際、マシン 1 から tailscale ping を送りながら tcpdump で UDP の接続を確認すると、ルーターが持つ公開 IP アドレスへパケットを出している様子を確認することができました:

$ sudo tcpdump -ni eno1 -n 'udp and host 192.168.1.2'
22:58:28.380747 IP 192.168.1.2.41641 > 203.0.113.1.37366: UDP, length 124

ところで自宅のルーターは UniFi Express なのですが、これはヘアピン NAT に対応していないらしいです:

https://community.ui.com/questions/No-hairpinning-NAT-loopback-on-UXG-Lite/c0b75e65-0d6a-44b5-b843-ad2cab622696

ということで、自宅のネットワーク構成では同一 LAN 内から Tailscale で direct connection を繋ぐことは無理そうです。仕方がないので Peer Relay などを導入してみようかなと思います。