🏠 家庭实验室的四层架构:边缘 / 应用 / 云端 / 接入
这篇是我这套家庭自托管集群的总览。它不是"买台服务器装个 Docker"的入门贴,而是为什么这样分层的复盘 —— 每一层都在解决上一层解决不了的问题。
一、动机:东西越堆越多,然后失控
一开始只是想把照片从手机里挪出来。于是有了一台小主机跑 Immich。后来想有个自己的网盘,又加了 OpenList。再后来要远程访问、要监控、要备份、要证书、要域名……
半年之后我发现:我不知道哪个服务跑在哪台机器上,也不知道某个端口是谁开的。
真正的问题不是"服务太少",而是缺少架构。于是我按职责把设备重新划成四层。
二、四层架构
┌──────────────────────────────┐
公网 ────────────▶│ 接入层 光猫 / SD-WAN 组网 │
└──────────────┬───────────────┘
│ 端口映射(增量式)
┌──────────────▼───────────────┐
│ 边缘层 1.50 Edge Box │
│ nginx / 门户 / 防护 bouncer │
│ 代理客户端 / 流量统计 │
└──────────────┬───────────────┘
│ 内网 TLS 反代
┌──────────────▼───────────────┐
│ 应用层 1.40 App Node │
│ Docker: 照片库 / 网盘 / DNS │
└──────────────────────────────┘
┌──────────────────────────────┐
│ 云端层 VPS(公网入口 / 中继) │
│ 反向代理 / 穿透服务端 / 数据库 │
└──────────────────────────────┘
2.1 边缘层:所有入站流量的唯一入口
边缘盒是一台 ARM 小主机(4 核 / 1GB 级),它承担:
| 职责 | 实现 |
|---|---|
| Web 入口 | nginx,静态托管门户站与内部工具页 |
| TLS 终止 | TrustAsia DV 证书,统一挂 home.***.cn |
| 入侵防护 | CrowdSec bouncer(决策来自应用层的 LAPI) |
| 出站代理 | 本地 SOCKS5 + HTTP 代理,供内网其他主机借用 |
| 流量审计 | vnStat 按日/月统计双网卡 |
| 状态探针 | fcgiwrap + CGI,输出网卡地址等实时信息 |
为什么把入口单独拎出来? 因为它必须是唯一的。一旦有两个入口,你就有了两套证书、两套防火墙规则、两套日志,然后开始不一致。
2.2 应用层:只跑应用,不碰边界
应用节点的职责非常窄:跑容器。上面是 Docker 全家桶 —— 照片库(含 PostgreSQL + Redis)、网盘,以及 DDNS 客户端和 CrowdSec LAPI。
它的网络暴露策略是最小化:容器端口只监听内网,对外的 TLS 反代由边缘层做。这样即使某个应用出了 RCE,攻击者也只拿到一个没有公网身份的内网容器。
2.3 云端层:只有公网身份,不存隐私数据
云端那台 VPS 承担两类工作:
- 公网入口:反向代理 + 内网穿透服务端,把外部请求送回家里。
- 纯服务:递归 DNS、关系库、缓存、远程桌面中继 —— 这些"要么需要固定公网 IP,要么需要独立可用性"的东西。
一条硬规则:隐私数据(尤其照片)绝不上云。 云主机只放可重建的数据。
2.4 接入层:最不该被自动化碰的地方
光猫和 SD-WAN 盒子属于运营商/厂商资产,配置有整体替换的坑(改一条规则可能把整个映射表覆盖掉)。
所以这一层我只做两件事:只增不改(新增映射时严格走 add-only,改前导出、改后校验、失败回滚),以及记录下来(哪条映射对应哪个服务,写进文档而不是记在脑中)。
三、三条关键设计决策
3.1 域名必须统一
站点早期用过 aaa.site 和 bbb.site 两套域名,结果:
- 证书要签两遍
- 反代
server_name要写两组 - 文档要维护两份映射表
- 其中一个域名忘了续费,被抢注后全站 canonical 指向了诈骗页
统一到一套域名后,证书、反代、DNS 记录全部收敛成一条线。
3.2 公网暴露面用"高位端口 + 域名"代替"IP + 默认端口"
有备案要求的网络环境下,80/443 上的未备案域名会被边缘劫持到一个阻断页。应对方式不是放弃,而是把 HTTPS 挪到高位端口,靠域名 + TLS 提供服务。代价是 URL 难看,收益是链路可控。
3.3 服务与数据分离
备份策略按层设计:
- 边缘层:配置 + 证书,改动前必
cp -p备份,带回滚路径 - 应用层:数据库卷 + 对象存储目录,定期快照
- 云端层:可重建,所以只需要配置即代码
四、可观测性:先能看见,才能优化
早期我靠"感觉"判断负载高低,后来改成本地采集 + 静态 JSON 热缓存:
采集进程(应用身份) → 写 /run 下的热缓存 → nginx 读文件返回
关键在于读写分离:HTTP 侧只读缓存,绝不触发采集。这样即使页面被脚本高频轮询,也不会把负载打上去 —— 再叠一层 limit_req 限流,正常用户完全无感。
五、踩过的坑
| 坑 | 现象 | 结论 |
|---|---|---|
| 限流区定义位置 | nginx 起不来 | limit_req_zone 只能在 http{} 内 |
include fastcgi_params | 所有 CGI 请求 404 | 它会用 $document_root$fastcgi_script_name 覆盖 SCRIPT_FILENAME,必须手工列参数 |
| 光猫映射批量改 | 映射表被整体替换 | 必须 add-only 增量 + 校验 + 回滚 |
| 域名续费 | canonical 全指向诈骗站 | 定期检查 whois 到期时间 |
| 容器端口直接对公网 | 暴露面失控 | 应用层端口只在内网可达 |
六、小结
这套架构的核心不是设备数量,而是分层带来的可推理性质:
- 想改证书 → 只动边缘层
- 想加服务 → 只动应用层
- 想改公网路由 → 只动云端层
- 想调接入 → 只动光猫,且只增不改
当你需要同时在心里维护超过三台机器的状态时,架构就不是奢侈品,而是必需品。