如何从五个 VPS 迁移到一个带有 Proxmox 的独立服务器,并停止为托管商付费
TL;DR:将三个托管商处的五个 VPS 组成的碎片化基础设施 → 合并到一台 Ryzen 7 9700X / 64GB ECC / 2× NVMe ZFS-mirror 的独立服务器上,运行 Proxmox VE。内部按角色划分 VM/LXC,使用 NetBird 网状网络代替端口转发,将出口节点放在单独的 VPS 上以绕过封锁和进行 TLS 终止,并使用 PBS 进行备份。资金、控制和精力都得到了提升。本文将详细介绍原因、方法以及我在过程中遇到的坑,以便您避免重蹈覆辙。
我为什么一开始就要这么做
去年年底,我发现自己为三个托管商提供的 五个不同的 VPS 付费。一个用于 Discourse 论坛,第二个用于基于 PHP 和 MySQL 的遗留论坛,第三个用于计费和 Telegram 机器人,第四个用于 VPN/代理,第五个用于静态文件和各种小工具。此外,n8n、Vaultwarden 和几个互相监控的 Uptime Kuma 也在那里运行(还能怎样呢)。
存在的问题:
- 费用。 总计约等于一台具有相同配置的优质独立服务器的价格(但那里是共享的,这里是独立的)。
- 没有任何隔离。 如果一个客户的机器人开始耗尽内存,我的论坛就会开始卡顿。还在同一个服务器上。太棒了。
- 备份 — 麻烦。 每个 VPS 都有自己的 rsync-cron 定时任务发送到 S3,备份保留策略各不相同,没有增量备份,恢复起来简直是一场冒险。
- 网络乱成一锅粥。 通过 Cloudflare Tunnels 转发的 SSH 端口,通过手动在两台 VPS 之间建立的 WireGuard,配置分散在三个地方。每次添加新机器时,集成都要花上一天时间。
- 更新的灾难。 在五台不同的 Ubuntu/Debian 上,版本和软件包各不相同,升级必须手动进行,而且不能同时进行。每半年我都会英勇地完成一次“更新一切,不破坏任何东西”的任务。
- CPU 和 RAM 闲置。 每个 VPS 都为“峰值”预留了资源,但平均使用率仅为 15-20%。钱白白浪费了。
总而言之,这是一个经典案例,基础设施的增长遵循“哦,还需要另一个服务 — 买个 VPS”的原则。是时候进行整合了。
我选择了什么以及为什么
硬件
我选择了一台 独立服务器:
- CPU: AMD Ryzen 7 9700X (8 核 / 16 线程, Zen 5)
- RAM: 64 GB DDR5 ECC 5200 MHz
- 磁盘: 2× NVMe 512 GB enterprise → ZFS mirror
- 网络: 1 Gbps unmetered
- 价格: 大约是同一家 OVH 两台中等 VPS 的价格
虚拟化平台
Proxmox VE 9.x。 我也考虑过其他替代方案,但简而言之:
- 纯 Docker — 没有隔离,没有正常的快照,只能通过 compose 方式备份,磁盘是一个统一的垃圾场。
- VMware ESXi — 不再免费,Broadcom 的许可政策很精彩,谢谢。
- XCP-ng — 还行,但我个人非常熟悉 Proxmox,而且社区更活跃。
- Kubernetes — 不,我一个人,没必要造一架飞机去趟趟商店。
Proxmox 提供了我需要的一切:KVM 虚拟机,用于需要隔离的所有场景(Docker、特定内核、不同发行版);LXC 容器,用于节省资源(数据库、监控、小型服务);内置 PBS 进行备份;开箱即用的 ZFS;快照;实时迁移(如果将来有第二台独立服务器);以及一个可以通过鼠标操作的 Web 界面,当我不想在终端里敲命令时很有用。
架构:总体图
我先展示最终结果,然后再解释我是如何做到的。
逻辑如下:
- 在独立服务器上 运行 Proxmox。它通过
vmbr0拥有公共 IP 地址Y.Y.Y.Y。 - 内部创建了 第二个桥接网络
vmbr1,使用私有网络10.10.10.0/24。这是“内部 LAN”的模拟 — 所有 VM 和 LXC 都连接到这里,并通过主机的 NAT 出去,它们本身没有公共入站端口。 - 主机本身 只运行三样东西:
Caddy(内部反向代理)、nftables(防火墙)和NetBird(VPN 代理)。主机上没有 Docker,没有应用程序逻辑。主机是神圣不可侵犯的,它的任务是作为虚拟化平台并保持稳定。 - 虚拟机 按角色划分:一个用于论坛,一个用于后台服务和机器人,一个用于 Dokploy 和用户应用程序,依此类推。它们之间通过
10.10.10.x直接可见,对外只能通过主机的 Caddy 访问。 - LXC 容器 — 用于所有“轻量级”服务:多个版本的 Postgres 实例、Proxmox Backup Server、监控仪表板。它们也连接到
vmbr1。 - 单独的出口节点(“Peer Caddy”)—— 这是位于另一家托管商处的一个小型 VPS,网络环境适合我的目标受众。它运行 Caddy,负责终止 TLS(Let's Encrypt)并通过 NetBird 网状网络将 HTTP 代理到独立服务器内部。所有公共域名的 DNS 都指向这个 VPS,而不是独立服务器。
- NetBird 网状网络 连接独立服务器、出口节点和我的笔记本电脑(还有几个尚未迁移完的老服务器)。它基于 SSO 认证的 WireGuard,没有外部开放端口。
为什么通过出口节点进行如此隔离——这是另一个话题,我稍后会回到这里。
Proxmox 安装:我是如何搭建的
订购了独立服务器,第一次启动时,我通过 救援模式/KVM 安装了 Proxmox(OVH 同时提供这两种方式 — KVM 对我来说更方便,可以看到整个过程)。安装过程中:
- ZFS RAID1 在两个 NVMe 上(
ashift=12,立即启用了compression=lz4) - 分区默认,Proxmox 会自行创建
rpool/ROOT、rpool/data和 swap
之后是安装后的基本清理。这是必须的,否则 Proxmox 会弹出订阅错误,并且性能不佳。
1# 1. 移除 enterprise repo (无订阅)
2sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/pve-enterprise.list
3echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
4 > /etc/apt/sources.list.d/pve-no-subscription.list
5apt update && apt full-upgrade -y
6
7# 2. 移除 "No valid subscription" 的 nag 窗口
8sed -Ezi.bak "s/(Ext\.Msg\.show\(\{[^}]+?title: gettext\('No valid sub)/void\(\{ \/\/\1/g" \
9 /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
10systemctl restart pveproxy
11
12# 3. 主机名 / 时区
13hostnamectl set-hostname dedik-waw
14timedatectl set-timezone Europe/Warsaw
SSH 加固
立即进行。不是“稍后”。稍后——永不到来。
1cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
2Port 22222
3PermitRootLogin prohibit-password
4PasswordAuthentication no
5MaxAuthTries 3
6ClientAliveInterval 300
7ClientAliveCountMax 2
8EOF
9
10mkdir -p ~/.ssh
11echo "ssh-ed25519 AAAA... your-key" >> ~/.ssh/authorized_keys
12chmod 600 ~/.ssh/authorized_keys
13systemctl restart sshd
在防火墙中关闭了 22 端口,只留下非标准端口。这不是“通过晦涩来保证安全”,而是减少日志中的背景噪音 — 22 端口在第一个小时就会收到 50,000 次暴力破解尝试,日志里一片混乱。而在 22222 端口——安静,可以真正监控异常。
ZFS — 针对 NVMe 和 64GB RAM 的配置
ZFS 默认会占用一半的 RAM 作为 ARC 缓存。这对于文件服务器来说是正常的,但对于虚拟机需要内存的虚拟化平台来说则完全不合适。我们将 ARC 限制在合理的 16 GB:
1cat > /etc/modprobe.d/zfs.conf << 'EOF'
2options zfs zfs_arc_max=17179869184
3options zfs zfs_arc_min=4294967296
4EOF
5update-initramfs -u
我的内存分配:
- ~16 GB — ZFS ARC
- ~44 GB — VM/LXC
- ~4 GB — 系统、缓冲区、开销
有用的池本身配置:
1zfs set compression=lz4 rpool
2zfs set atime=off rpool
3zfs set xattr=sa rpool
4zfs set dnodesize=auto rpool
5zfs set relatime=on rpool
6
7zfs set recordsize=64K rpool/data # 适用于 VM 磁盘
8zfs set sync=standard rpool/data
9
10zpool set autotrim=on rpool # 对 NVMe 至关重要
compression=lz4 几乎免费地节省了 20-30% 的空间,CPU 开销很小。对于 VM 的块设备,recordsize=64K 是读写性能和写入放大率的最佳折衷。
内部网络和防火墙
创建了第二个桥接网络用于内部通信,并配置了 NAT 出去:
1auto vmbr1
2iface vmbr1 inet static
3 address 10.10.10.1/24
4 bridge-ports none
5 bridge-stp off
6 bridge-fd 0
7 post-up echo 1 > /proc/sys/net/ipv4/ip_forward
8 post-up iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE
9 post-down iptables -t nat -D POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE
现在,vmbr1 桥接网络中的任何 VM/LXC 都会获得一个本地地址 10.10.10.x,并通过主机访问互联网,但从外部无法看到它们。
nftables 配置为 policy drop。只开放必须开放的端口,其余的静默丢弃。最小化配置集:
1table inet filter {
2 chain input {
3 type filter hook input priority 0; policy drop;
4
5 iif lo accept
6 ct state established,related accept
7 ip protocol icmp accept
8 ip6 nexthdr icmpv6 accept
9
10 tcp dport 22222 accept # SSH
11 tcp dport 8006 accept # Proxmox UI (仅限 NetBird 访问,见下文)
12 tcp dport { 80, 443, 8080 } accept # Caddy
13 udp dport 29899 accept # NetBird
14
15 iif vmbr1 accept # 内部网络 — 全部允许
16 iif wt0 accept # NetBird 接口 — 也受信任
17 }
18
19 chain forward {
20 type filter hook forward priority 0; policy drop;
21 iif vmbr1 oif vmbr0 accept
22 ct state established,related accept
23 }
24}
Proxmox UI (端口 8006) 的访问被我通过 CF/防火墙物理上关闭了对互联网的访问,并且我 只通过 NetBird 访问它。公共 IP 地址的界面根本不响应。同样,PBS UI、仪表板和所有管理界面——都只通过网状网络访问。外部只开放真正公共的服务端口。
NetBird 网状网络:为什么不选择经典的 WireGuard
我长期使用普通的 WireGuard,这在服务器数量少于三个时还算正常。之后——管理配置变得很痛苦:每次添加一个 peer 节点都意味着“更新所有节点的配置文件,不要忘记 allowed_ips,重启守护进程,检查是否破坏了到另一个 peer 的路由”。
NetBird 是 WireGuard 之上的管理层。技术上,它底层还是 WireGuard,但是:
- 节点通过信令服务器相互查找,NAT 穿透是自动的
- 认证通过 SSO(我使用的是 Authentik),无需共享密钥
- 访问通过 Web 界面上的策略进行管理,可以创建机器组和它们之间的规则
- 内置 SSH 代理:你可以通过
ssh user@machine.netbird.cloud访问,并获得 SSO 认证(需要 Authentik 的批准)。节点甚至不需要在外部开放 22 端口。
我部署了 NetBird 代理:
- 在 Proxmox 主机上
- 在每个需要被管理员或其他 VM 访问的 VM 上
- 在出口节点上
- 在我的笔记本电脑上
结果是我无需端口转发、防火墙漏洞或额外的堡垒主机,就可以从笔记本电脑通过一条命令访问任何机器。并且没有一个私有 VM 开放 SSH — 它们都只连接到 vmbr1 和网状网络中的 wt0。
虚拟机 vs. 容器:我是如何为自己决定的
一个几乎总是适用的简单规则:
| 何时 | 选择什么 |
|---|---|
| Docker 负载 | VM (Docker 在 LXC 中有时会很顽固,尤其是使用 overlay2) |
| 非 Docker 数据库 | LXC (更轻量,更快,通过 ZFS 直接访问磁盘) |
| 系统服务 (PBS, 监控, 小工具) | LXC |
| 需要自己内核/网络技巧的应用 | VM |
| VPN 节点 (WG, AmneziaWG) | LXC (开销最小) |
最终结果大致如下:
虚拟机 (KVM):
forum— Discourse 标准的 launcher 容器。内部使用 Docker。services— 许多小服务:webhook 机器人、AI 机器人、计费面板、Vaultwarden、Uptime Kuma。所有都使用 Docker compose,每个服务一个文件夹。legacy— 旧的 PHP 论坛 (IPS),原生 PHP-FPM 和 Caddy 在 VM 中运行。我不想打包成 Docker — 太多的遗留问题,不如保持原样。dokploy— Docker Swarm + Dokploy 用于用户应用程序(Next.js, Postgres, Redis),通过 Git 部署。
LXC:
pbs— Proxmox Backup Server (见下文)。pg16,pg17,pg18— 三个独立的、不同主版本的 Postgres。原因:不同的应用程序需要不同的主版本,我不想每次都升级。dashboards— 一个容器,包含 Homarr (启动页面)、Uptime Kuma 和 Beszel hub。这三个都在同一个 compose 文件中,因为它们在逻辑上属于同一类 — 观察。
每个 VM 都分配了静态 IP 10.10.10.x(按范围划分,这样查找更方便),2-8 个 vCPU 内核和 2-8 GB RAM,具体取决于负载。内核数量可以安全地超额分配 — 我为 VM 分配的总内核数超过了物理内核数,而且效果很好,只要没有人同时达到 CPU 瓶颈。
反向代理:双层 Caddy
在 Proxmox 内部,我将 Caddy 安装在 主机上,而不是 VM 中。为什么?因为它需要监听独立服务器的公共端口,并通过 Host-header 路由到正确的 VM,为它启动一个单独的 VM 并进行端口转发没有好处。Caddy 轻量,用 Go 编写,有 systemd 服务单元,配置在一个文件中。
这个主机上的 Caddy 只监听 8080 HTTP 端口,并根据 10.10.10.x 路由到后端。它上面没有任何 TLS。
1:8080 {
2 @forum host forum.example.com www.forum.example.com
3 handle @forum {
4 reverse_proxy http://10.10.10.111:80 {
5 header_up X-Forwarded-Proto https
6 header_up X-Real-IP {header.X-Real-IP}
7 header_up Host {host}
8 }
9 }
10
11 @panel host panel.example.com api.panel.example.com
12 handle @panel {
13 reverse_proxy http://10.10.10.111:8081 {
14 header_up X-Forwarded-Proto https
15 header_up X-Real-IP {header.X-Real-IP}
16 header_up Host {host}
17 }
18 }
19
20 @dokploy host app1.example.com app2.example.com
21 handle @dokploy {
22 reverse_proxy http://10.10.10.114:80 {
23 header_up X-Forwarded-Proto https
24 header_up X-Real-IP {header.X-Real-IP}
25 header_up Host {host}
26 }
27 }
28}
而 TLS 终止和证书 — 在单独的出口节点上,在 Docker 中,遵循相同的原理:
1{
2 email me@example.com
3}
4
5forum.example.com {
6 reverse_proxy http://100.x.y.z:8080 {
7 header_up X-Forwarded-Proto {scheme}
8 header_up X-Forwarded-For {remote_host}
9 header_up X-Real-IP {remote_host}
10 header_up Host {host}
11 }
12}
100.x.y.z — 这是独立服务器的 NetBird 地址。请求通过 HTTPS 到达出口节点,解密,以 HTTP 形式通过 NetBird 网状网络 发送到独立服务器,然后 Proxmox Caddy 根据 Host 匹配并路由到内部 VM。独立服务器和出口节点之间的通信 默认是 HTTP — 我们信任网状网络,这里没有外部流量。
为什么需要出口节点
几个原因:
- 绕过封锁。 OVH 的部分 IP 段在中国被屏蔽。出口节点位于未被屏蔽的托管商处。公共域名的 DNS 指向那里。
- 单一 TLS 终止点。 只有它颁发证书,Let's Encrypt 的挑战只在这里进行。这简化了所有 DNS 验证,并允许独立服务器在 80/443 端口上对互联网完全不可见。
- 额外的隔离层。 如果有人发起 DDoS 攻击,他们攻击的是出口节点,而不是独立服务器。出口节点上的数据很少,重新创建一个只需 10 分钟。
- 分离公共前端和后端。 我可以在独立服务器上安全地更新 Caddy / 进行实验,而与互联网的实际接口是独立且稳定的。
缺点是:每个请求的延迟会增加约 5-15 毫秒,因为多了一个跳跃。对于 Web 来说,这不明显。
此外,对于某些服务,主机上直接运行的 Caddy 在 80/443 端口上运行,无需出口节点 — 用于那些我不想通过出口点路由的域名(例如,状态端点、监控、DNS 本来就指向独立服务器的服务)。这并行运行:主机上的 Caddy 监听(出站节点内部的 :8080)和(来自互联网的直接 :80/:443),路由规则不同。
迁移:如何在几乎不停机的情况下顺利迁移
这是最棘手的部分。五个服务器,大约三十个服务,生产环境的论坛有活跃用户,计费系统有有效订阅,数据库绝对不能丢失。
策略是逐个服务迁移,按“从最不关键到最关键”的顺序进行:
- 首先迁移小型工具和静态网站(如果出现问题,没人会注意到)。
- 然后是机器人和后台服务(也可以忍受一分钟的停机时间)。
- 然后是数据库(事先已验证恢复)。
- 然后是依赖于这些数据库的应用程序。
- 最后是主论坛,进行一次长期的最终同步和 DNS 切换。
迁移通道
首先,我在旧服务器上启用了 NetBird。这立即解决了两个问题:我可以安全地通过 SSH 访问内部网络,并且 rsync 通过 NetBird 上的 WireGuard 进行,不暴露数据到公共互联网。
每个服务的命令大致如下:
1# 从新独立服务器,通过 NetBird IP 连接到旧服务器:
2rsync -avzP --delete \
3 -e "ssh -p 5322" \
4 root@100.76.108.210:/var/discourse/shared/standalone/ \
5 /target/discourse/shared/standalone/
对于数据库 — 备份后恢复,而不是文件复制。切勿“热”复制 Postgres/MySQL 文件,那是找死。
1# 在旧服务器上
2docker exec -it shm-vsem-mysql mysqldump -u root -p shm-vsem | \
3 gzip > /tmp/shm-vsem.sql.gz
4
5# 在新服务器上,通过 NetBird
6scp -P 5322 root@100.76.108.210:/tmp/shm-vsem.sql.gz /tmp/
7zcat /tmp/shm-vsem.sql.gz | docker exec -i shm-vsem-mysql mysql -u root -p shm-vsem
对于 Discourse — 使用原生的 discourse backup / discourse restore 命令,通过 ./launcher enter app。它本身会正确地收集 Postgres 转储 + uploads + 配置文件,并同样地进行恢复。
DNS 切换
在新的位置启动服务并验证其通过内部地址正常工作后 — 我将两套安装并行运行 5-15 分钟,以观察数据同步情况,然后切换 DNS 到新服务器。我提前一天将记录的 TTL 降低到 60-300 秒。
对于 Discourse,用户一直在发帖,我是这样做的:
- 在新位置启动所有东西。
- 进行验证(登录、发帖、上传文件到 S3、搜索)。
- 在旧安装中启用
read_only_mode(Discourse 原生支持)。 - 进行最后的 rsync uploads + 最后的数据库转储。
- 在新服务器上启动,关闭 read-only 模式。
- 切换 DNS。
- 旧服务器上什么都没动,保持一周 — 以防万一。
每个服务的实际停机时间 — 大约两分钟。
过程中的意外情况
pct restore时的权限问题。 LXC 在unprivileged 1模式下,UID 会偏移 100000。如果旧服务器上的文件是 UID 1000 — 新服务器上将是 UID 101000,应用程序将无法识别。解决方案是恢复后执行chown,或者使用--unprivileged 0(如果你明白风险)。- OVH VM 中的 Docker ipv6 有时会出错 — 我通过在 Docker daemon 中禁用 ipv6 (
"ipv6": false) 来解决。 - VM 的时间。 有几次发现主机和 VM 之间的系统时间不一致,导致 TLS 和签名出错。解决方法是在每个 VM 上安装
systemd-timesyncd或chrony,并在 Proxmox 代理中设置host.use-time。 - Discourse 在两个版本之间的迁移。 如果旧服务器上的 Discourse 版本比你在新服务器上安装的版本更新 —
restore会失败。新安装上的版本必须相同或更高。 - Dokploy 的镜像仓库。 当我通过
dokploy-postgres转储迁移 Dokploy 元数据时,数据库中仍然保留了指向旧服务器上的本地 registry 的链接。服务尝试启动时会连接到100.76.117.115:5000并因No such image而失败。解决方案是在 Dokploy UI 中重新构建每个应用程序;本地构建会将镜像推送到新服务器。
备份:PBS 部署在同一台独立服务器上 + 异地备份
关于是否在同一硬件上备份,我曾有过一番激烈的思想斗争。答案是:既要,也要。
级别 1 — 池本身的 ZFS 快照。 几乎免费(写时复制),瞬间创建,瞬间恢复。我保持 rpool/data 每 15 分钟自动快照,保留 24 小时,外加每天保留一周的快照。使用 zfs-auto-snapshot。用于防止“糟糕,我刚刚删除了生产数据库”的情况:
1apt install zfs-auto-snapshot
2# 之后它会自动通过 cron 创建 frequent/hourly/daily/weekly/monthly 快照
级别 2 — Proxmox Backup Server 部署在同一主机上的独立 LXC 容器中。 这是通过 vzdump 对 VM/LXC 进行完整的增量备份,在块级别进行去重。PBS 将快照存储在单独的 ZFS 数据集中,将其视为一个仓库,每个 VM 都有自己的增量链。
在 Proxmox 中设置:Datacenter → Backup → Add。计划:每天凌晨 4 点,保留策略为 keep-daily=7 keep-weekly=4 keep-monthly=3。这些都可以通过鼠标操作。
将 PBS 部署在同一主机上的 LXC 容器中是一个折衷。优点:无需单独的硬件,备份通过 localhost 进行,速度如同本地 SSD,保留/去重效果完美。缺点:如果独立服务器整体损坏 — 备份也会随之丢失。因此,有...
级别 3 — 将 PBS 仓库同步到异地 S3。 PBS 支持 sync job 到 S3 兼容的存储。我每晚将快照上传到 Selectel 的 S3 存储(也可以是 Backblaze、Wasabi、Cloudflare R2 等)。那里保留两天,因为不需要更长的保留期:长期备份有本地 PBS,异地备份是防止“整个数据中心被烧毁”的保险。
级别 4 — 应用程序数据单独备份。 Discourse 本身会进行自己的备份并将其存储在 S3 中(这是其原生功能)。所以即使 PBS 和整个独立服务器都丢失了 — 我仍然拥有 Discourse 在其云端的完整备份。
1# 从 PBS 恢复整个 VM — 实际上只需一条命令:
2pvesm list backup-pbs # 查看可用备份
3qmrestore backup-pbs:backup/vzdump-qemu-201-2026_04_06-04_00_03.vma.zst 999 \
4 --storage local-zfs
5# 一分钟后,你就可以在 VMID 为 999 的 VM 上运行 4 点时的 forum-VM 副本。
这一点至关重要:你没有尝试恢复的备份,就不是备份。我每月进行一次随机 VM 的测试恢复到空的 VMID,检查它是否能启动,应用程序是否正常运行,然后删除。这很无聊,但有一次我正是通过这种方式发现,某个 cron 任务将数据写入了 /tmp/...,而 /tmp 在备份时会被跳过,导致应用程序在恢复后需要手动干预。
监控
我不喜欢在有 15 台机器的情况下收集 Prometheus + Grafana + Alertmanager + Loki 这种大型系统。因此,我选择在单个 LXC 容器中部署三个轻量级工具:
- Beszel — 收集每台 VM 上的代理指标(CPU、RAM、磁盘、网络接口、Docker 容器)。Hub 运行在 LXC 中,代理 — 在每台 VM/LXC 上通过 systemd unit 运行。Beszel 的认证是自有的,通过 PocketBase,这有时不太方便(见下文的“坑”),但总体上还能工作。
- Uptime Kuma — 通过 HTTP/HTTPS/Ping/TCP 进行探测。我将所有公共域名、所有内部服务(通过 NetBird)以及所有网状网络节点的 ping 都设置了进去。告警 — 发送到 Telegram。
- Homarr — 启动页面,包含所有管理界面的链接。这样我就不必记住 PBS UI、Dokploy、Vaultwarden 分别在哪个端口上。只需打开 Homarr 并点击即可。
这三个工具都放在同一个 Docker compose 文件中,运行在同一个 LXC 容器里,只能通过 NetBird 访问。
Beszel 的坑,以便您避免:Beszel 在 PocketBase 中有两个用户表 —
_superusers(用于 CLI/API) 和users(用于 Web 界面)。如果您通过 CLIsuperuser upsert重置密码 — 只会重置_superusers,而您通过users登录 UI 时密码会不匹配。可以通过向/api/collections/users/records/<id>发送 PATCH 请求通过 REST API 来修复。
我最终获得了什么
经过大约三周的迁移,当最后一个服务稳定下来后,我做了总结:
| 参数 | 过去 | 现在 |
|---|---|---|
| 服务器/账单 | 3 家托管商的 5 台 | 1 台独立服务器 + 1 台微型 VPS |
| 月度费用 | ~$X | ~$X/2 |
| 剩余资源 | "好像够用" | 8 vCPU 和 30 GB RAM 剩余 |
| 备份 | 每台 VPS 的 rsync-cron 到 S3 | PBS + 异地备份 + ZFS 快照 |
| 恢复 | "嗯,大概一天" | 从 PBS 恢复 VM 2-5 分钟 |
| SSH 访问 | 5 个不同的端口和密钥 | 一个带 SSO 的 NetBird 网状网络 |
| 服务隔离 | 共享垃圾场 | 每个服务一个 VM/LXC |
| 升级前快照 | "祈祷吧" | qm snapshot 201 pre-update |
| 用于日常任务的 Web 界面 | 还有什么 Web 界面? | Proxmox UI |
最重要的是,它在情感上给予了我——我不再害怕去修改任何东西。任何危险的操作(升级、迁移、实验)现在都始于一个快照,并以 30 秒内完成提交或回滚结束。我开始更频繁地尝试新事物,因为错误的代价降低了数量级。
如果您打算重复,请参考此清单
如果您发现自己有和我一样的症状 — 这是一个简短的清单,按有意义的顺序排列:
- 计算所有 VPS 的总花费,并与 OVH/Hetzner/LeaseWeb 的独立服务器价格进行比较。您会感到惊讶。
- 如果计划使用 ZFS,请务必购买 ECC RAM。不要节省。
- 至少购买两块磁盘组成镜像。单块磁盘不适合生产环境。
- 通过 KVM/IPMI 安装 Proxmox,而不是通过 rescue + debootstrap。这样麻烦更少。
- 立即 设置 SSH 密钥,非标准端口,禁用密码,安装 fail2ban。
- 立即 限制 ZFS ARC。默认情况下,它会吃掉一半的 RAM。
- 创建第二个桥接网络
vmbr1用于内部私有网络和 NAT。所有 VM/LXC 都连接到这里。虚拟机上不应有公共 IP。 - 在开始迁移之前,先搭建好 NetBird (或 Tailscale, 或 Headscale)。这是您的迁移通道和管理访问入口。
- 不要将 Proxmox UI 暴露在外部。只能通过 VPN 网状网络访问。
- 在另一个托管商处放置一个小型出口 VPS 用于 TLS 终止和绕过封锁 — 非必需,但非常方便。价格便宜,收益很多。
- 在独立容器中部署 PBS + 异地 S3 同步。备份必须自动化。
- 至少每月进行一次测试恢复。否则,您的备份只是安慰剂。
- 逐个服务迁移,按关键程度递增。不要试图“在周末搬完所有东西”。
- 提前一天降低 DNS TTL,而不是一小时。
- 为每个服务维护文档:配置文件位置、启动方式、备份方法、恢复方法。半年后,您会感谢自己。
还有哪些需要完成
为了完整性:我的迁移尚未 100% 完成。还剩下:
- 几个旧服务器上的 Remnawave 面板(计划下周末完成)。
- 迁移 Caddy(有自定义插件,目前是临时方案)。
- 旧的 CrowdSec — 单独从头安装,不迁移。
但所有关键服务(论坛、计费、机器人、数据库) — 已经部署在独立服务器上,运行稳定,已备份并监控。
如果您对具体步骤、配置或我遇到的坑有任何疑问,欢迎在评论区提问,我会尽量详细解答。特别想听听您在其他虚拟化平台(XCP-ng、ESXi,或者纯 Docker Swarm)上是如何解决类似问题的 — 也许我遗漏了什么。
祝您在整合基础设施的过程中好运。更少的账单,更多的控制 — 这是值得的。