从 Cloudflare Tunnel 到 Tailscale:为家庭实验室增加网络后备方案


背景:遭遇 Cloudflare 连通性问题

自己在家维护着一个 Kubernetes 集群,对外暴露了一些服务方便自己在外面随时随地访问,这些服务的监控可以点这里查看:https://status-dev.tomyail.com/

最近发现这些服务的连接性不好,比如在使用 git pull 拉取部署在家里的 gitea 的代码总是失败,必须挂代理才能访问。我使用的是 Cloudflare Tunnel 方案,后来看到一篇文章 部分 Cloudflare 的域名近期在大陆无法访问 才明白,文章提到以 .1 结尾的 IP 地址,估计都是被防火墙特殊照顾的。

通过 nslookup 查看我的域名解析,果然全是这类 IP:

Terminal window
nslookup gitea.tomyail.com
Server: 11.11.11.11
Address: 11.11.11.11#53
Non-authoritative answer:
Name: gitea.tomyail.com
Address: 104.21.32.1
Name: gitea.tomyail.com
Address: 104.21.48.1
Name: gitea.tomyail.com
Address: 104.21.80.1
Name: gitea.tomyail.com
Address: 104.21.64.1
Name: gitea.tomyail.com
Address: 104.21.96.1
Name: gitea.tomyail.com
Address: 104.21.16.1
Name: gitea.tomyail.com
Address: 104.21.112.1

现在也不确定这个屏蔽是暂时的还是永久的,总之得先搞个 Plan B。

方案选择:为什么选择 Tailscale

面对内网穿透方案的选择,我主要在 frp 和 Tailscale 之间纠结了一下。最终选择 Tailscale 的原因如下:

  1. 退可守:与 frp 相比,Tailscale 自带中继服务,可用性更高
  2. 进可攻:如果需要,tailscale 的 derp 中继服务也能自己搭建
  3. Kubernetes 友好:Tailscale 有官方的 Kubernetes Operator,部署更简单

考虑到自己也维护着一个美国的线上集群,所以最终选择了 Tailscale + 美国集群作为入口 的方案。

美国集群在这个架构中扮演着关键的桥梁角色:

  1. 流量转发枢纽:将外部流量通过 Tailscale 网络转发到国内家庭集群
  2. DNS 自动化管理:配合 External-DNS 可以自动声明和管理 DNS 记录,无需手动维护域名解析

网络架构演进

原架构:

外部用户 → Cloudflare(❌) → Cloudflare Tunnel → 家庭集群服务

新架构:

外部用户 → 美国VPS公网IP(✅) → Nginx Ingress → Tailscale网络 → 家庭集群服务

说明:本文不讨论 外部用户 → Tailscale客户端 → 家庭集群服务 这种方案,因为需要每个用户都安装 Tailscale 客户端,不够通用。

技术实现详解

1. 家庭集群部署 Tailscale Operator

1.1 准备工作

首先需要在 Tailscale 控制台创建 OAuth 应用:

  1. 访问 Tailscale OAuth 设置
  2. 创建 OAuth 客户端,权限选择:
    • Devices Core (写权限)
    • Auth Keys (写权限)

1.2 配置 ACL 权限

在 Tailscale 控制台的 ACL 页面添加:

{
"tagOwners": {
"tag:k8s-operator": [],
"tag:k8s": ["tag:k8s-operator"]
},
"acls": [{ "action": "accept", "users": ["*"], "ports": ["*:*"] }]
}

1.3 部署 Operator

---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: &app tailscale
namespace: network
spec:
interval: 30m
chart:
spec:
chart: tailscale-operator
version: 1.84.3
interval: 30m
sourceRef:
kind: HelmRepository
name: tailscale
namespace: flux-system
values:
operatorConfig:
hostname: tailscale
podAnnotations:
reloader.stakater.com/auto: 'true'
apiServerProxyConfig:
mode: 'true'
valuesFrom:
- kind: Secret
name: tailscale-secret
valuesKey: client_id
targetPath: oauth.clientId
- kind: Secret
name: tailscale-secret
valuesKey: client_secret
targetPath: oauth.clientSecret

成功部署后,会在 Tailscale 的 Machines 后台看到一个新机器(我这里定义的名字是 tailscale):

tailscale operator

2. 暴露服务到 Tailscale 网络

对于需要暴露的服务,只需要添加一个注解即可(参考:官方文档):

如果要暴露的服务已经存在,可以通过对象注解的方式将其暴露到 Tailscale 网络。

在服务的 metadata.annotations 下添加注解 tailscale.com/expose,值设为 "true"。注意这里的 "true" 需要加引号,因为注解值必须是字符串类型,不加引号的 true 会被错误地解释为布尔值。

使用这种模式时,Kubernetes 不会直接告诉你 Tailscale 机器名称。你可以在 Tailscale 管理控制台的 Machines 页面查看设备名称。默认情况下,暴露服务的机器名称格式为 <k8s-namespace>-<k8s-servicename>,当然这个名称是可以自定义的

以下配置片段来自我的实际配置

service:
app:
annotations:
tailscale.com/expose: "true"
controller: echo
ports:
http:
port: *port

部署后,服务会自动获得:

  • ✅ Tailscale IP
  • ✅ Tailscale 域名:default-echo.shark-pentatonic.ts.net(其中 shark-pentatonic.ts.nettailnet name,每个人都不一样,并且只有 Tailscale 网络内部能访问)

暴露的服务

如果成功,会在 Tailscale Machines 页面出现一个新节点,这个节点就是对应的 Kubernetes 服务。

3. 美国集群配置

3.1 安装 Tailscale

在美国 VPS 上安装 Tailscale 客户端:

Terminal window
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --authkey=<your-auth-key>
# 验证连接
tailscale status
ping default-prowlarr.shark-pentatonic.ts.net

3.2 配置代理服务

# 美国集群配置
---
apiVersion: v1
kind: Service
metadata:
name: tailscale-echo
spec:
type: ExternalName
externalName: default-echo.shark-pentatonic.ts.net
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tailscale-echo-ingress
annotations:
external-dns.alpha.kubernetes.io/target: 'external-us.tomyail.com'
external-dns.alpha.kubernetes.io/cloudflare-proxied: 'false'
spec:
rules:
- host: echo-tailscale.tomyail.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tailscale-echo
port:
number: 80

问题排查指南

在实施过程中,我遇到了几个典型问题,这里分享一下解决经验:

常见问题及解决方案

1. Tailscale Operator 启动失败

错误现象:

Terminal window
oauth2: cannot fetch token: 401 Unauthorized

解决方案:

必须是 Oauth Client,不是 Auth keys 或者 API access tokens; OAuth clients 生成的 secret 会带上 tskey-client 的前缀

2. 暴露的服务无法访问

错误现象:

Terminal window
curl -v http://default-prowlarr.shark-pentatonic.ts.net # 失败

解决方案:

  • 确认美国 VPS 已正确加入同一个 Tailnet
  • 检查 Tailscale ACL 配置是否允许访问
  • 验证服务是否在 Tailscale 控制台正确显示

总结

通过这次方案升级,成功解决了 Cloudflare 在国内的连通性问题。

不过尴尬的是,截止发文当天,似乎 Cloudflare 的连通性又恢复正常了 😂,所以折腾半天权当给内网穿透加一个后备方案吧。

实际效果对比

Cloudflare Tunnel 恢复后的表现: cloudflare 连接性

Tailscale + VPS 方案的表现: 自建连接性

优化节点和非优化节点的区别:(使用国内优化节点响应可以在1秒以内,否则还是延迟比较高) 延迟统计 从监控数据可以看出:

  • Cloudflare Tunnel:恢复后连通性确实不错,响应时间也比较快
  • Tailscale + VPS:连通性稳定,虽然速度略慢于 Cloudflare(毕竟 VPS 不是国内优化节点),但胜在稳定可控
  • Tailscale + VPS 的入口vps速度很影响响应速度(图三9:12PM-2:48PM 是dmit 入口的延迟,后面为普通线路),因为我国内优化节点的流量不是很多,就切换到非优化节点了

方案优劣对比

方案连通性速度成本可控性维护难度安全性
Cloudflare Tunnel⚠️ 国内不稳定
🌍 国外优秀
🏍️ 国内一般💰 免费❌ 依赖第三方🟢 简单✅ IP 隐藏
Tailscale + VPS✅ 连通性稳定
💡 适合作后备
🟡 取决于vps的速度和derp的速度💰 $5-20/月✅ 完全自控🟡 中等⚠️ IP 暴露

相关资源:

PS:整个方案是通过与 Cursor 的 Claude 4 聊天逐步完善的。AI 提供了整体思路,但具体实施细节不能全信,还需要结合官方文档自己排查解决问题。