服务卡片 · 网络与访问
给装不了 WireGuard 的人准备的 Tailscale 子网路由
WireGuard 是这个家自己的隧道,在对搭建者重要的一切方面都更好。但对一位没法加端口转发、也没法在受管控的工作笔记本上安装客户端配置的家人来说,它毫无用处。子网路由是一种让步:一个节点把整个局域网宣告给一个 tailnet,让某位远程家人不需要任何人改路由器设置就能看 Jellyfin。
| 容器镜像 | tailscale/tailscale:v1.80.2 |
|---|---|
| 宿主端口 | :41641/udp |
| 数据目录 | /srv/homelab/tailscale/state |
| 内存预留 | 95 MB |
| CPU 上限 | 0.30 vCPU (cpus: "0.30") |
| 更新节奏 | monthly for the security fixes, and the node key expires after a hundred and eighty days unless expiry is disabled in the admin console |
| ARM 兼容 | arm64 native; the userspace networking mode is not used here because the kernel mode performs far better on ARM |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :41641 | 41641 | udp | 仅局域网 | WireGuard-based transport, outbound only; nothing is forwarded at the router for this |
部署步骤
在管理台创建带标签的一次性认证密钥
可复用密钥就是那种会被复制进脚本、然后复制进仓库的密钥。一次性加标签,意味着节点一上来就已经被 tailnet 的 ACL 限定好了。
run # 管理台 -> Settings -> Keys -> 生成认证密钥 # 可复用:关 临时:关 标签:tag:rack echo "TS_AUTHKEY=tskey-auth-..." >> /srv/homelab/.env启动容器并批准宣告的路由
未批准的路由会安静地什么都不做:节点显示已连接、子网却不可达,除了管理台之外没有任何地方报错。
run sysctl net.ipv4.ip_forward # 子网路由要求宿主机上这个值为 1 cd /srv/homelab/tailscale && docker compose up -d docker exec tailscale tailscale status专门为这个节点关闭密钥过期
一个放在柜子里、没人会看的节点,如果不关掉过期,会在一百八十天后从 tailnet 里静静消失。这是逐节点的设置。
run # 管理台 -> Machines -> 该节点 -> ... -> 关闭密钥过期 docker exec tailscale tailscale status --json | grep -i keyexpiry
两条隧道,以及为什么两条都要
家里用 WireGuard,因为它在我们掌控之中、没有协调服务器、配置放在 git 仓库里。子网路由只为一个人存在:一位工作笔记本只允许装 Tailscale、别的都不行、而她又应该能在不用打电话求助的情况下看到照片库的家人。
两条并存不是为冗余而冗余。它们回答的是不同问题:WireGuard 回答「我怎么到我的机架」,Tailscale 回答「别人怎么在我什么都不用做的情况下到那儿」。
| WireGuard | Tailscale | |
|---|---|---|
| 谁负责配置 | 搭机架的人 | 想访问的人 |
| 链路里有第三方吗 | 没有 | 有协调服务器,但不在数据路径上 |
| 在运营商大内网后面能用吗 | 不能 | 能 |
| 每加一台设备的配置成本 | 一个新的对端和一张二维码 | 无 |
宣告路由就是这整套功能的全部
子网路由把局域网的 CIDR 段宣告给 tailnet,此后 tailnet 里其它设备就能直接访问 192.168.10.20,而它们上面什么都不用装。这就是「一个节点的配置让整个机架可达」的原理。
路由必须在管理台里手工批准。这是个好的默认:被攻陷的节点没法悄悄宣告另一个子网、把流量从自己这里绕一圈。
# 在派上确认宣告了什么
docker exec tailscale tailscale status
docker exec tailscale tailscale ip -4
docker exec tailscale tailscale debug prefs | grep -i route出口节点的问题
把这个节点宣告为出口节点,能让 tailnet 设备把所有流量都从家里出去。它只对一个场景有用:有人在酒店 wifi 上,希望自己的流量从一个熟悉的地址离开。
它同时也是最容易让人烦的设置,因为家里的上行带宽会变成那台设备一切操作的天花板。这里宣告了它,一年大约会用到两次。
密钥过期是那个坑
Tailscale 节点密钥默认一百八十天后过期,过期之后节点会从 tailnet 里静静消失,直到有人登录重新认证。对一个放在柜子里、没人会去看的节点来说,这就是五个月后来自亲戚的一通求助电话。
在管理台里专门为这个节点关闭密钥过期。它是逐节点设置,也是「一个服务」和「一件周期性家务」之间的差别。
# 管理台 -> Machines -> 该节点 -> ... -> 关闭密钥过期
# 然后在派上确认:
docker exec tailscale tailscale status --json | grep -i keyexpirycompose 文件
整份文件放在 /srv/homelab/tailscale-subnet-router/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
tailscale:
image: tailscale/tailscale:v1.80.2
container_name: tailscale
restart: unless-stopped
environment:
TZ: Asia/Shanghai
TS_AUTHKEY: ${TS_AUTHKEY}
TS_STATE_DIR: /var/lib/tailscale
TS_ROUTES: 192.168.10.0/24
TS_EXTRA_ARGS: --advertise-exit-node --advertise-routes=192.168.10.0/24
TS_USERSPACE: "false"
volumes:
- /srv/homelab/tailscale/state:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
- NET_RAW
networks: [rack]
networks:
rack:
external: true加固清单
- 节点密钥设为不过期,并在 Tailscale 管理台里用 ACL 限制哪些 tailnet 用户能访问家里这个子网
- 认证密钥是带标签的、预先授权的、一次性的,存在 env 文件里,而不是默认的可复用密钥
- 路由在管理台里手动批准,节点宣告的子网必须有人同意才会生效
- 容器只带子网路由所需的最小能力,不用 host 网络
备份方案
状态文件里是节点身份和宣告的路由。丢了它意味着要在浏览器里重新认证这个节点,一分钟的事;宣告的路由清单在 compose 文件和 git 里,所以其实什么都没丢。
上线后验证
- 一台从没见过这个局域网的 tailnet 设备能打开 Jellyfin 地址并拿到登录页
- 宣告的路由在管理台里显示为已批准
- 派上的 tailscale status 列出每一台 tailnet 机器,包括离线的
- JSON 状态输出里显示密钥过期已关闭
踩过的坑
- 未批准的路由会安静地什么都不做。节点显示已连接、子网却不可达,而除了管理台之外没有任何地方报错。
- 重装之后复用认证密钥会重新生成节点身份。首次注册用一次性密钥,之后就靠节点自己持久化的状态文件。
- 子网路由要在 tailnet 和局域网之间转发流量,宿主机必须开启 IP 转发。光有容器的能力还不够。
- 宣告了出口节点又忘了它,会把家里的上行链路变成别人的瓶颈。如果宣告了,就该有个写在某处的理由。
硬件与选型问答
- 相比只用 WireGuard,这是不是一种安全上的妥协?
- 这是一次取舍而不是妥协。Tailscale 看得到你的 tailnet 上有哪些设备,并协调它们的密钥;它看不到流量,因为流量是端到端加密的。你放弃的是「建立连接的过程中没有第三方参与」这一点。对一个替代方案是「转发端口加公开登录页」的家庭来说,这显然是改善。
- 为什么不直接在派上装 Tailscale 应用?
- 可以,在专用机器上那样更简单。跑在容器里能让机架的服务保持统一:一切都是同一个仓库里的 compose 文件,备份和升级的故事都一样,宿主机的网络配置也完全不必动。
- Tailscale 出故障会怎样?
- tailnet 设备之间已有的连接继续可用,因为数据路径是直连的,协调服务器只在建立新连接时才需要。家里自己的 WireGuard 隧道则完全不受影响。这正是子网路由只是补充、而不是主路径的原因。