使用 Tailscale 异地组网搭建 K3s 集群

前段时间在家里的小主机上安装了一个单节点的 K3s 作为未来的服务运行环境以及开发环境(以及帕鲁服务器的运行环境),在使用的时候萌生了一个想法——是不是可以用家里的服务器与吃灰的羊毛云服务器一起组成一个 K3s 集群呢?

打开 K3s 文档,可以看到 K3s 直接集成了两种组建「多云」集群的方案

  • 内置的「多云」方案:使用 WireGuard 建立一个 VPN Mesh 供集群使用,这个方案要求每个节点都有一个可以直接访问的唯一 IP(通常是公网 IP)
  • 集成 Tailscale 方案(实验功能):K3s 集成 Tailscale,直接使用 Tailscale 组建底层的 Mesh 网络

这里考虑到还需要接入家里的服务器,所以选择了 Tailscale 方案,从实践上来看这个方案也足够简单,并且看起来能满足我的需求。

准备工作

知识储备

K3s 集成 Tailscale 的方式

K3s 集成 Tailscale 的方案是如何实现的呢?这里稍微看了下代码,发现 K3s 并没有做什么太多的事情,核心代码就是下面这一行:

1
2
3
4
5
6
7
func StartVPN(vpnAuthConfigFile string) error {
// ...
switch authInfo.Name {
case "tailscale":
output, err := util.ExecCommand("tailscale", args)
// ...
}

K3s 只是在启动前根据传入的参数把 Tailscale 的参数构建好,调用 tailscale up 建立 Tailscale 网络,然后使用 tailscale0 网卡来初始化集群即可。

从代码上来看,K3s 目前只支持 Tailscale。同时还从代码中得到一个文档中没有的信息,--vpn-auth 参数还支持设置 extraArgs,通过它可以透传自己希望设置的其它 Tailscale 参数,不得不说还是很贴心的,正好后面我们会用到这个参数。

再说说 Tailscale

Tailscale 的使用方式足够简单,但是功能又足够强大。基于 Tailscale,甚至不需要我们感知任何公网 IP,没有云服务器照样可以组跨地域的集群。

得益于 Tailscale 强大的内网穿透能力,即使是运行在防火墙后面的云服务器,不开放任何端口(仅 22 端口用于 SSH 连接)也可以通过 Tailscale 构建一个私有网络。

组网后,直接 Ping 另外的节点可以看到没有走任何中继服务器,只有一跳直接到达目的地

1
2
$ tailscale ping multicloud-home-nuc
pong from multicloud-home-nuc (100.91.81.112) via 120.244.233.95:5820 in 7ms

Subnet Route 与 Exit Node

通过上面提到的 extraArgs 参数,我们直接利用 Tailscale 特性玩点花活。

首先 Subnet Route 这个特性是 K3s 所依赖的,K3s 需要利用这个配置使得 Pod 间网络是可达的,在部署后,我们还可以通过 tailscale set 命令继续设置新的 Subnet Route,使得网络中的节点可以访问其它网段,比如家里的局域网。

Exit Node 也是一个有趣的特性,我们可以指定一个节点作为 Exit Node,所有 Tailscale 网络中的公共流量都会从这个节点进出。这不妨让我们想到云服务器的网络通常不是很理想,而为云服务器单独配置科学上网也是比较麻烦的事情,这时候如果我们 Tailscale 网络中的某台设备所在的网络的出口上有一个透明代理,那么一切都通达了起来。

服务器准备

  • 大于 1 台的包括但不限于云服务器、物理主机、虚拟机等计算设备。
  • 所有服务器安装 Linux 系统,建议内核版本大于 5.6,不需要安装 WireGuard 组件,我使用的都是 Ubuntu 22.04,内核版本为 5.15

Tailscale 配置

  1. 首先需要登录到 Tailscale,可以用任意第三方账号登录,这里我使用 Github 登录,如果第一次使用 Tailscale,会进入一个使用引导,直接跳过就好。

  2. 登录后,进入到 Settings > Keys 中,生成一个 Auth Key,需要注意的是需要勾选 Reusable 选项以支持多设备复用同一个 Key。

    image-20240131230407598

  3. 配置 autoApprovers,将 Pod 子网添加到自动通过配置中,使得 Pod 网络可以正常互相访问,这需要我们在 Access controls 中添加相应的配置,这里直接使用了默认的 Pod CIDR,中括号中的名字是在 Users 中看到的内容,如我是 microud@github

    1
    2
    3
    4
    5
    6
    "autoApprovers": {
    "routes": {
    "10.42.0.0/16": ["your_account@xyz.com"],
    "2001:cafe:42::/56": ["your_account@xyz.com"],
    },
    },

    设置好后如下

    image-20240131233959433

一切准备就绪,开始搭建我们的集群了~

搭建集群

做好前面的准备后搭建集群就很简单了,几条命令即可,本章节的内容可以当一个操作手册供参考。

安装 Tailscale

执行如下命令在所有节点安装 Tailscale

1
curl -fsSL https://tailscale.com/install.sh | sh

安装完毕后不需要启动,K3s 会根据传入的设备来执行 tailscale up 启动 Tailscale。

初始化 Server 节点

配置 K3s 使用 Tailscale 网络,只需要在执行 K3s 的安装脚本添加如下参数即可

1
--vpn-auth="name=tailscale,joinKey=$AUTH_KEY

通过环境变量设置 AUTH_KEY 后,执行下面的完整的命令

1
2
3
4
5
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - \
--disable=servicelb \
--write-kubeconfig ~/.kube/config \
--write-kubeconfig-mode 666 \
--vpn-auth="name=tailscale,joinKey=$AUTH_KEY"

还可以按需禁用 traefik ingress。

如果是国内的云服务器网络受限的话,可以换用如下的命令来安装

1
2
3
4
5
6
curl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_EXEC="server" sh -s - \
--system-default-registry "registry.cn-hangzhou.aliyuncs.com" \
--disable servicelb \
--write-kubeconfig ~/.kube/config \
--write-kubeconfig-mode 666 \
--vpn-auth "name=tailscale,joinKey=$AUTH_KEY"

Server 节点可以在任意服务器上,无所谓是否有公网 IP。

创建 Agent 节点

由于在 Server 节点未设定 Token,查看 k3s 生成的 token 文件, server: 后的部分即为 Token

1
sudo cat /var/lib/rancher/k3s/server/token

其次查看 Server 节点上 tailscale0 网卡的 IP 地址,拿到 IP 后就可以部署 Agent 了

1
2
3
4
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="agent" sh -s - \
--server https://<tailscale0 IP>:6443 \
--token <token> \
--vpn-auth "name=tailscale,joinKey=$AUTH_KEY"

经过上面的步骤,集群就初步搭建好了,如果还有新的节点可以继续添加 Agent。这里我直接查看一下 Node

1
2
3
4
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
vm-24-17-ubuntu Ready control-plane,master 23m v1.28.5+k3s1
multicloud-home-nuc Ready <none> 20s v1.28.5+k3s1

整点花活

还记得上面提到的 Subnet Routes 与 Exit Node 吧,在这里讲一下具体的操作。

Subnet Routes

K3s 在 Tailscale 启动完毕后,Flannel 会根据 PostStartupCommand 配置来进一步配置 VPN 以确保 Pod 间的网络联通

1
2
3
4
5
{
"Type": "extension",
"PostStartupCommand": "tailscale set --accept-routes --advertise-routes=%Routes%",
"ShutdownCommand": "tailscale down"
}

其中 %Routes% 会被具体替换为 IPv4 或者 IPv6 的地址,具体根据配置决定。

在安装好的节点上执行命令可以看到设置的结果,这也正是我们在前面配置的 autoApprovers

1
2
3
4
$ tailscale status --json | jq ".Self.PrimaryRoutes"
[
"10.42.0.0/24"
]

我们想扩展支持的网段,首先需要在待添加网段中的某个节点上执行如下命令

1
tailscale set --advertise-routes "10.42.0.0/24,192.168.5.0/24"

这次由于我们没有设置 autoApproves,还需要到 Tailscale 后台中通过一下,点击「Edit route setting」可以看到如下设置

image-20240203005308104

通过后就可以在 Tailscale 任意节点中访问这个网段了。

Exit Node

Exit Node 的申请可以直接在 k3s 安装阶段完成比较简单,安装命令调整如下:

1
2
3
4
5
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - \
--disable=servicelb \
--write-kubeconfig ~/.kube/config \
--write-kubeconfig-mode 666 \
--vpn-auth="name=tailscale,joinKey=$AUTH_KEY,extraArgs=--advertise-exit-node"

节点同样只是可以申请成为 Exit Node,启动后我们还需要去 Tailscale Admin 通过一下,依旧是在编辑 Route 菜单项中,勾选 Exit Node 保存即可。

配置好 Exit Node 后,不代表其他节点就会使用,还需要在期望使用的节点上手动设置一下

1
2
3
4
5
6
7
8
# 使用 IP 设置 Exit Node
sudo tailscale set --exit-node=100.91.81.112

# 使用 Host Name 设置 Exit Node
sudo tailscale set --exit-node=multicloud-server

# 取消设置 Exit Node
sudo tailscale set --exit-node=

设置 Exit Node 后,所有的外网流量都会走 Exit Node,如果是云服务器那公网 IP 是不能再用来 SSH 了,不过可以通过 Tailscale 网卡的 IP SSH 到节点再设置取消 Exit Node。

如此操作下来,云服务器也能享受到家庭网络中的科学配置了,可以在必要的时候使用,如果不介意公网 IP 不可用,还可以一直启用。

小结

至此集群就已经搭建好了,在 Tailscale + K3s 场景下还能继续怎么玩呢?我准备继续折腾一下,挖掘一些有趣的内容再分享出来~