跳到正文
Eloxt
文章

Homelab 网络结构总结

基于 PVE、RouterOS、Tailscale、Traefik 与 Caddy 构建兼顾安全、隐私与自动化的 Homelab 网络架构

最开始我是把所有服务都部署在云服务器上的,除了费钱,其实省心省力没啥毛病。后来跑的服务越来越多、越来越重,手头刚好又有了闲置的机器,索性就把服务全部迁回了本地 Homelab,云服务器退居二线,只负责对外访问的网络中转。毕竟在云端兜里掏空撑死了也就买得起 4C6G 这种入门配置,而搬回家里不仅能轻松拉到 8C16G 甚至更高,后续升级硬盘和内存也是丰俭由人。

硬件设备

盘点一下硬件阵容,可以说是相当“勤俭持家”了:2022 年买的 J4125 工控机服役至今,笔记本也是公司淘汰下来的二手旧设备,核心诉求就是省电与省钱。

设备 数量 CPU 内存 网口
J4125 工控机 1 J4125 8G 2.5G Ethernet × 4
笔记本电脑 1 i5-1135G7 8G × 2 1G Ethernet × 1

外加一台腾讯云轻量锐驰型服务器(大峰值带宽、不限流量套餐),专门充当所有对外访问的前置代理。

软件环境

两台物理机均使用 Proxmox Virtual Environment (PVE) 搭建虚拟化平台。部署原则也很明确:除了必须独立内核或系统形态限制只能以虚拟机(VM)安装的服务,其余一律优先使用资源开销极低的 LXC 容器部署。

两台机器的分工如下:

  • J4125 工控机:主攻网络基础设施,负责软路由、透明代理以及轻量级的常驻网络服务。
  • Notebook 笔记本:承担数据库、缓存、NAS 以及主力 Docker 应用容器等较重的负载。
主机 服务 类型 IP
J4125 PVE 10.1.1.254
RouterOS VM 10.1.1.1
sing-box LXC 10.1.1.2
Docker LXC 10.1.1.100
Tailscale LXC 10.1.1.101
Notebook PVE 10.1.1.253
PostgreSQL LXC 10.1.1.201
Valkey LXC 10.1.1.202
Home Assistant OS VM 10.1.1.252
飞牛 (fnOS) VM 10.1.1.250
Docker LXC 10.1.1.200

网络拓扑

整体拓扑结构十分清晰直观:J4125 板载的 4 个 2.5G 物理网口在 PVE 底层分别对应一个 Linux 虚拟网桥(vmbr0 ~ vmbr3),全部桥接给 RouterOS 虚拟机进行统一纳管。其中 vmbr0 作为 WAN 口承接光猫拨号,其余 3 个作为 LAN 口组网;笔记本则通过千兆网口直连工控机的其中一个物理 LAN 口。

J4125 PVE 10.1.1.254RouterOSVM 10.1.1.1vmbr0eth0 WANvmbr1eth1 LANvmbr2eth2 LANvmbr3eth3 LANsing-boxLXC 10.1.1.2DockerLXC 10.1.1.100tailscaleLXC 10.1.1.101Internet腾讯云锐驰代理Notebook PVE 10.1.1.253PostgreSQLLXC 10.1.1.201ValkeyLXC 10.1.1.202Home AssistantVM 10.1.1.252飞牛VM 10.1.1.250DockerLXC 10.1.1.200

外网访问

我家的宽带是有公网 IP 的,理论上最直接的做法就是在 RouterOS 上配置端口映射配合 DDNS,把需要访问的服务端口逐个丢出去。但实际用起来,有几个让我非常难受的地方。

首先,我并不想直接对外暴露家宽的真实公网 IP,因为这能被轻易推算出家里的大致地理位置,也容易沦为全网端口扫描的目标。其次,随着服务数量增多,每个服务都得分配并记住一堆杂乱的高位端口。理论上当然也可以在本地起一个 Nginx 或 Caddy 来做反向代理,但在 80/443 被封锁的前提下,对外依然摆脱不了带端口访问的尴尬,真实 IP 也依然处于裸奔状态。最后,三大运营商之间的跨网互联偶尔也会抽风。

第一阶段:云端反代 + DDNS 白名单回源

为了解决这些痛点,我最初选择在腾讯云服务器上部署 Caddy 来做反向代理,由它来监听公网标准的 80/443 端口并自动申请管理 HTTPS 证书。

在回源链路上,第一版依然使用 DDNS 和本地端口映射,但在 RouterOS 的防火墙中严格限制了入站 IP 白名单,只允许腾讯云服务器的固定公网 IP 访问。这样既隐藏了家里的真实 IP、统一了访问域名与端口,又杜绝了外部随意扫描本地端口的风险。大致逻辑如图:

腾讯云锐驰CaddyHomelab ddns.example.comRouterOS端口映射 来源白名单service1.example.com:8080service1service2.example.com:8081service2service3.example.com:8082service3

但随着服务日常增减,这套方案的维护成本变得不可接受。每次只要新增或调整一个服务,就得经历一套繁琐的连环操作:

  1. 登录 RouterOS 增减 NAT 端口映射;
  2. 登录腾讯云服务器修改 Caddy 配置并 reload;
  3. 在 DNS 解析后台增减子域名的 A 记录;
  4. 本地启动服务容器。

“部署 5 分钟,配网半小时”,极其折腾。所以我又进行了一次改造。

第二阶段:全链路自动化改造

首先是域名解析:直接在 DNS 服务商处配置一条泛域名解析(*.example.com)指向腾讯云服务器的公网 IP,一劳永逸,以后再添新服务无需频繁操作 DNS。

其次是内网回源通道:利用 Tailscale 打通虚拟局域网。在腾讯云上安装 Tailscale 并加入内网网段后,云端即可直接通过内网 IP 与端口访问 Homelab 中的服务,彻底告别 RouterOS 上繁琐的端口映射。虽然 Tailscale 的 WireGuard 封装带来了一丁点性能损耗,但在家宽上行场景下完全可以忽略不计。

最后是最核心的问题:反向代理配置该如何全自动感知?

和 ChatGPT + Gemini 一通左右脑互搏之后,我决定使用 Traefik + traefik-kop 的方案:

  • 多机容器感知:原生 Traefik 只能监听本地宿主机的 Docker Socket,而我的 Docker 容器分散在两台宿主机的 LXC 容器中。
  • 解决方案:在两个 Docker LXC 中都部署轻量的 traefik-kop,由它分别监听各自宿主机的 Docker 容器标签(Labels),解析后统一写入共享的 Valkey(兼容 Redis 协议)配置中心。
  • Traefik 统一入口:在其中一个 Docker LXC 上部署 Traefik,配置使用 Valkey 作为动态配置源(Provider),从而自动汇聚两台机器上的所有服务路由规则。
  • 证书管理:由内网的 Traefik 通过 DNS-01 验证统一申请并自动续期 *.example.com 的通配符证书。

这样一来,腾讯云上的 Caddy 彻底卸下了七层反代与证书管理的负担,只需要做纯粹的四层(Layer 4)流量透传,所有的七层路由与证书处理全部交由内网的 Traefik 自动化完成。

*.example.com腾讯云锐驰Caddy L4Tailscaleproxy_protocol v2HomelabDocker 10.1.1.100Traefikservice1service2Docker 10.1.1.200service3traefik-koptraefik-kopValkeyLXC 10.1.1.202路由配置

不过,由于我在腾讯云上还运行着少量自身的监控服务,且共用了同一个主域名,因此 Caddy 的四层配置需要通过 TLS SNI 做一次分流:

  • 匹配到 a.example.com 和 b.example.com 时,路由至云端本机的 8443 端口处理;
  • 其余泛域名流量全部通过 Tailscale 内网透传到 Homelab 的 Traefik 443 端口。

这里有一点非常关键:必须开启 proxy_protocol v2,否则四层透传后 Traefik 看到的来源 IP 全是云服务器的内网 IP,开启 PROXY Protocol 协议后 Traefik 才能正确还原出客户端真实的公网 IP。

{
    layer4 {
        :443 {
            @local_sni tls sni a.example.com b.example.com
            route @local_sni {
                proxy 127.0.0.1:8443
            }
            @homelab_sni tls sni example.com *.example.com
            route @homelab_sni {
                proxy 10.1.1.100:443 {
                    proxy_protocol v2
                }
            }
        }
    }
}

a.example.com:8443 {
        reverse_proxy 127.0.0.1:8080
}

b.example.com:8443 {
        reverse_proxy 127.0.0.1:8081
}

至此,以后部署新服务时,只需在 docker-compose.yml 中给容器贴上 Traefik 标签,整套反向代理与域名解析便会全自动生效:

labels:
  traefik.enable: "true"
  traefik.http.routers.service-name.rule: "Host(`a.example.com`)"
  traefik.http.services.service-name.loadbalancer.server.port: "8080"
  traefik.http.routers.service-name.entrypoints: websecure
  traefik.http.routers.service-name.tls: "true"
  traefik.http.routers.service-name.service: service-name

DNS 解析与内网回流

由于 Traefik 反代与通配符 HTTPS 证书都在局域网内部,这就给我们带来了一个巨大的好处:实现内网回流(Split-Horizon DNS)。

在局域网内部通过公网域名访问服务时,完全没必要让流量白白去腾讯云公网兜上一圈。只需在路由器上配置静态 DNS 解析,将 *.example.com 直接指向内网 Traefik 所在的服务器 IP(10.1.1.100)即可。

同样地,因为腾讯云上还部署了共用该域名的服务,在 RouterOS 中需要通过静态 DNS 正则规则做一次分流:

  • 属于云端服务的子域名匹配正则后设为 FWD,正常转发给上游公共 DNS 解析回云服务器;
  • 其余 Homelab 服务域名全部直接解析为内网 Traefik 的 IP。
/ip dns static
add regexp="^(a|b|c)[.]example[.]com\$" type=FWD
add address=10.1.1.100 regexp="^[^.]+[.]example[.]com\$" type=A

这样一来,无论身处外网还是内网,访问体验完全一致(同一套域名 + 自动 HTTPS 证书);而且在内网访问时直接跑在局域网内网带宽上,零延迟、不占公网流量,体验丝滑顺畅。

其实还有一些关于 Tailscale 组网细节和 sing-box 透明代理的配置考量,篇幅有限,有空再单独写一篇聊聊吧。