主机重启恢复设计
目标
任意单节点重启不得使其余节点永久离线;主节点恢复后,Headscale、Tailnet、Nomad、系统云盘和控制台应自动恢复,无需人工改 hosts、临时反代或手工启动容器。
根因
原路径让 MM 的 mesh.yohan.fun 经 Tailnet 回源主节点 100.64.0.1:39200。主节点重启时 Tailnet 地址尚未恢复,MM 无法回源 Headscale;其他节点又必须访问 MM 上的 mesh.yohan.fun 才能恢复 Tailnet,构成控制面循环依赖。MM 的 BaoTa Nginx 也未作为独立 systemd 单元启用,重启后可能不启动。
选型
| 方案 | 可维护性 | 部署复杂度 | 性能 | 学习成本 | 扩展性 | 结论 |
|---|---|---|---|---|---|---|
| 仅给现有服务增加 Tailnet 等待 | 中 | 低 | 无影响 | 低 | 低 | 不能打破回源循环,不采用 |
| 新建第二套 Headscale | 低 | 高 | 增加同步与故障面 | 高 | 低 | 会产生控制面状态分叉,不采用 |
| MM 直连主节点 bootstrap 加 Tailnet 就绪加载器 | 高 | 中 | 仅启动期一条控制面回源 | 中 | 高 | 采用 |
| 失败后无限重启 Nginx、Nomad 或容器 | 低 | 低 | 增加无效重试和日志噪声 | 低 | 低 | 不采用 |
运行契约
- MM 是唯一公网入口,BaoTa Nginx 使用独立 systemd 单元自启动。
- MM 对
mesh.yohan.fun在启动期经主节点公网 bootstrap 回源;该 bootstrap 仅允许 MM 公网 IP,只代理 Headscale 协议和经 A-Auth 管理员校验的 Headplane/admin,不暴露 Nomad、Docker 或业务端口。 - 主节点 BaoTa Nginx 先运行 Tailnet listener 清理器、再做配置校验并启动 bootstrap;
100.64.0.1缺失时必须移除私有39200配置,不能让残留监听器阻断公网 bootstrap。 - 主节点若已保存的 Tailnet 状态明确为
NeedsLogin或NoState,才由 root-only 恢复服务向本机 Headscale 申请不可复用、5 分钟有效的一次性注册键;恢复服务每次启动都强制执行tailscale set --accept-dns=false --accept-routes=false,注册显式保留既有 Exit Node 宣告并使用--accept-dns=false,系统级应用、主机默认路由和主机 DNS 不得由 Tailnet 接管。密钥仅存在进程内存,不写入项目、日志或长期配置。其他状态不得自动重注册,保留给故障调查。 - Tailnet 地址
100.64.0.1出现后,加载私有39200回源配置并 reload,然后启动 Nomad 和 Runtime。Runtime 恢复时必须检查 Docker 专用网络附着;若此前因 Tailnet 端口绑定失败而丢失网络端点,重新附着固定私网地址后再验证 loopback 健康端点。主节点与 101/103 的 Nomad 均只等待 Tailnet IPv4 出现后启动;不等待已退役的数据库、文件系统或预拉取镜像。Cloudreve 位于 dd,按其自身容器健康恢复,不作为 Worker 启动前置条件。
验收
受控重启时每台机器逐台执行,确认 Tailnet 注册、SSH、Docker 健康、Nomad、mesh.yohan.fun、Console、系统云盘和告警恢复;不得同时重启多个节点。主节点演练还必须覆盖“Tailnet listener 配置残留但地址未出现”的 bootstrap 降级路径、NeedsLogin/NoState 的一次性恢复路径,以及重注册后 accept-dns=false、accept-routes=false、宿主公网默认路由和宿主公网 DNS 仍有效。
已知恢复入口
节点的 root SSH 公钥仍限制为主节点 Tailnet 来源。该限制不应因演练而绕过;若某个 Worker 已启动但 Tailnet 尚未恢复,需在该 Worker 的本机终端执行 systemctl restart tailscaled,然后由主节点继续验收。不得为了这类单机故障而放开密码登录或移除来源限制。