在缺乏公网 IPv4 的家庭宽带环境下,自托管往往面临两难:内网 NAS 拥有充沛的存储与计算资源,但在外网通常只能依赖速度受限的官方中继;而云端低配 VPS(如 2C2G)虽具公网 IP,却常因跑满多种服务而内存吃紧。
本文记录一次完整的拓扑重构过程:利用 Tailscale 将终端设备、家庭 NAS 与云服务器接入同一虚拟局域网(tailnet),促成点对点直连穿透;将核心生产服务下沉至 NAS Docker 运行,云服务器降级为边缘反向代理与 TLS 终结网关。
整个方案演进可分为三个主要阶段:
| 阶段 | 重点目标 | 关键动作 |
|---|---|---|
| 基础设施组网 | 建立私网连接并打通直连 | 全端接入 tailnet,NAS 配置裸仓 Git,光猫配置静态 UDP 映射触发 NAT 直连 |
| 拓扑与边界验证 | 明确暴露面与访问路径 | 云服务器接入 tailnet,评估并拆除管理面公网暴露,收敛为隧道代理架构 |
| 服务回迁与收敛 | 生产负载下沉与边缘轻量化 | 任务系统与博客迁入 NAS Docker,数据完整平移,云端仅保留轻量代理与出口服务 |
1. 基础设施组网:突破 NAT 中继瓶颈
1.1 群晖 DSM 接入 Tailscale
群晖 NAS(DSM 7.x)部署 Tailscale 时,套件中心常无官方最新包。推荐直接从官方分发源获取对应架构的 .spk 安装包,并在安装前校验 sha256。
# 登录群晖配置免 root 运维权限并接入网络
sudo /var/packages/Tailscale/target/bin/tailscale set --operator=$USER
sudo /var/packages/Tailscale/target/bin/tailscale up
--operator 允许非 root 用户执行 tailscale status、netcheck 等诊断命令,方便后续运维。
1.2 DERP 中继向 P2P 直连升级
初次入网后,离家访问通常回退至 DERP(Designated Encrypted Relay for Packets)中继。实测在跨运营商及复杂 NAT 场景下:
- DERP 中继: RTT 约 420ms,传输吞吐约 17KB/s,
git ls-remote耗时约 7.7s; - 排查症结: 光猫(电信网关)默认未开启 UPnP,且多层 NAT 对对称型打洞存在抑制。
优化方案: 登录光猫后台,配置一条静态端口映射:外网 UDP 41641 → NAS 内网 IP 的 UDP 41641(Tailscale 默认协商监听端口)。
注意: 严禁直接开启 DMZ 主机模式,否则会导致 NAS 的管理端口(5000/5001)与 SMB 等基础服务完全裸露于公网。此外,
tailscale netcheck的PortMapping项仅指示 UPnP/NAT-PMP 协商状态,对路由器手动静态映射不会打标,应直接通过传输 RTT 与tailscale status的direct状态来验证。
配置生效后,在外网环境下验证网络吞吐与延迟,直连链路建立成功:
- P2P 直连: RTT 降低至 42ms,吞吐提升至 2.1MB/s(提升约 125 倍),
git ls-remote耗时缩短至 0.55s。
1.3 搭建内网 Git 裸仓库
DSM 自带 Git,可直接作为私有 Git 服务端:
# 创建裸仓库
mkdir -p /volume1/git/project.git
cd /volume1/git/project.git && git init --bare
关键配置要点:
- 用户主目录服务: 必须在 DSM「控制面板 → 用户与群组 → 高级设置」中启用「用户家目录服务」,否则无法挂载
~/.ssh/authorized_keys。 - 目录权限安全检查: DSM 的 sshd 对权限要求严格:家目录应为
0711,.ssh目录为0700,authorized_keys为0600,权限过宽会导致免密登录静默失败。 - 传输协议选择: DSM 默认未开启 SFTP 子系统,会导致
scp报错,但 Git over SSH 依托git-upload-pack协议交互,不受 SFTP 限制。
2. 拓扑设计:划分生产与管理面的安全边界
当云端 VPS 与 NAS 处于同一 tailnet 后,两者网络延迟可稳定在 10ms 级别,具备了构建“边缘网关 + 内网后端”的物理条件。
管理面与服务面隔离
在架构验证初期,曾测试通过云端 Nginx 反代直接暴露 DSM 控制台。但在公网暴露短时间内,即在日志中监测到针对 HTTPS 端口的批量目录扫描。随后迅速调整策略并确立了隔离规范:
- 管理面不出门: DSM Web 管理后台、SSH 及存储管理接口仅允许在内网或 tailnet 内部直连访问,完全不对公网开放。
- 服务面走边缘反代: 公网仅开放云端边缘节点的 80/443 端口,用于公共 Web 服务与 API 流量的入口。
- 跨节点隧道转发: 云端 Nginx 负责 TLS 证书终结,并将流量通过 Tailscale 分配的内网 IP 转发至 NAS 上的具体应用容器。
3. 生产回迁:应用迁移与轻量化收敛
3.1 边缘 Nginx 收敛为纯代理管道
迁移前,云端 Nginx 往往兼顾静态文件托管与本地服务路由。迁移后,云端配置精简为仅保留 SSL 终结与隧道转发规则:
server {
listen 443 ssl http2;
server_name tasks.example.com;
ssl_certificate /etc/letsencrypt/live/tasks.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tasks.example.com/privkey.pem;
location / {
proxy_pass http://100.x.y.z:8080; # NAS 的 tailnet IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
静态资产打包及具体路由全部下沉到 NAS 内部的容器处理,云服务器解耦为无状态转发网关。
3.2 部署流水线迁移
自动部署脚本沿用“状态 Marker”机制(通过 .deployed-commit 记录已通过健康检查的 Commit 哈希)。仅当拉取最新代码并顺利通过本地构建及容器健康检查后,才刷新状态标,防止部署中断导致的服务假死。
在发布链路方面,将 NAS 上的裸仓库配置为工作流源端,定时检查提交并在本地构建生效,全程无需向外部代码托管平台高频推拉:
3.3 数据一致性与 SQLite WAL 迁移
针对使用 SQLite 驱动的轻量应用(如自建任务系统),在服务迁移过程中需特别注意其并发写入机制:
- SQLite 在开启 WAL(Write-Ahead Logging)模式时,数据会分散在
app.db、app.db-wal及app.db-shm三个文件中。 - 停止源端容器服务后,必须同时迁移上述三份文件,或在停服后通过 CLI 执行
PRAGMA wal_checkpoint(TRUNCATE);触发数据刷盘,再进行冷迁移与校验。
完成数据迁移并下线云端历史容器后,云端 VPS 内存占用稳定在 360MB 左右,磁盘负载显著释放。
4. 关键踩坑与工程实践
下表归纳了本次网络与容器迁移过程中的典型技术障碍与对应解法:
| 现象 / 错误信息 | 根本原因 | 解决方案 |
|---|---|---|
容器绑定 Tailscale IP 报错 cannot assign requested address |
群晖官方 Tailscale 运行在 userspace-networking 模式,宿主机无 tailscale0 虚拟网卡 |
容器监听 0.0.0.0,由 Tailscale 在用户态转发至本地服务端口 |
| 局域网内设备走 Tailscale 访问 NAS 延迟异常高(>300ms) | 光猫未开启 NAT Loopback(Hairpin),导致同一内网流量绕行外网公网 IP 反弹 | 本地局域网设备配置 hosts 或内网 DNS 解析,优先走内网直连,移动端走 Tailscale |
| 覆盖配置后 NAS Docker 镜像与容器全量丢失 | 群晖配置主文件为 dockerd.json,且需保留 data-root 及 storage-driver: btrfs 原生参数 |
针对性增补 registry-mirrors 项,采用 JSON 属性合并而非覆盖写入 |
| 容器重新构建部署后 Nginx 反代报错 502 Bad Gateway | Nginx 在启动加载阶段对 upstream 域名仅做一次静态 DNS 解析,容器重建分配新 IP 后未更新 | 配置 resolver 127.0.0.11 valid=10s; 并使用变量形式进行动态代理转发 |
| 自建检测脚本探活端口全量超时,判定服务异常 | 最小化基础镜像未安装 nc,普通用户执行 ping 缺乏 Raw Socket 权限返回假阴性 |
编写巡检工具前需先行在已知开放端口进行基础工具可用性自检 |
Userspace Networking 模式的安全边界认知
群晖等嵌入式设备使用用户态网络堆栈(Userspace Networking)时,会产生以下衍生边界:
- 源 IP 丢失: 经由 Tailscale 转发至宿主机的流量,其源地址会被重写为 localhost。
- 防火墙联动失效: 宿主机层面基于来源 IP 的防火墙规则无法有效甄别具体 Tailscale 节点。
- 监听面扩展: 容器端口若绑定为
0.0.0.0,不仅对 Tailscale 内部开放,也会直接向当前家庭局域网暴露出访问端口,在组网设计中需纳入考量。
5. 架构适用性与权衡
完成家庭自托管架构迁移后,需客观评估其适用边界:
- 适合场景: 私有知识库、个人任务看板、轻量级博客与私有 Git 仓库。此类服务读写频次具备明显个人属性,吞吐要求适配家宽上传上限,通过 P2P 隧道可获得出色的响应体验。
- 不适用场景: 对高可用性有严苛要求的核心生产系统,或具有突发公网大带宽下载需求的业务。家庭环境的 SLA 受限于市电供电、光猫稳定性及运营商链路质量,缺乏云机房的多路冗余保障。
- 灾备注意事项: 生产环境回迁后,数据持久化责任由云厂商转向自建节点。同机跨卷复制并不等同于完整灾备,建议通过独立冷备介质、异地加密备份等手段建立完善的容灾备份闭环。
参考资料
- Tailscale — 官方架构文档与原理
- Tailscale — Synology NAS 部署指南
- Docker — Daemon 配置文件结构与镜像源设置
- Nginx — 反向代理与动态 DNS 解析指令参考